diff --git a/.github/workflows/ci-build-vpn-api-wasm.yml b/.github/workflows/ci-build-vpn-api-wasm.yml new file mode 100644 index 0000000000..c8dae05cdb --- /dev/null +++ b/.github/workflows/ci-build-vpn-api-wasm.yml @@ -0,0 +1,41 @@ +name: ci-build-vpn-api-wasm + +on: + pull_request: + paths: + - 'common/**' + - 'nym-vpn-api/**' + - '.github/workflows/ci-build-vpn-api-wasm.yml' + +jobs: + wasm: + runs-on: arc-ubuntu-22.04 + env: + CARGO_TERM_COLOR: always + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: wasm32-unknown-unknown + override: true + components: rustfmt, clippy + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Install wasm-opt + uses: ./.github/actions/install-wasm-opt + with: + version: '116' + + - name: Install wasm-bindgen-cli + run: cargo install wasm-bindgen-cli + + - name: "Build" + run: make + working-directory: nym-vpn-api/vpn-api-lib-wasm diff --git a/.github/workflows/ci-build-vpn-api.yml b/.github/workflows/ci-build-vpn-api.yml new file mode 100644 index 0000000000..1a31e7fc29 --- /dev/null +++ b/.github/workflows/ci-build-vpn-api.yml @@ -0,0 +1,45 @@ +name: ci-build-vpn-api + +on: + pull_request: + paths: + - 'common/**' + - 'nym-vpn-api/**' + - '.github/workspace/ci-build-vpn-api.yml' + workflow_dispatch: + +jobs: + build: + runs-on: arc-ubuntu-22.04 + env: + CARGO_TERM_COLOR: always + MANIFEST_PATH: "--manifest-path nym-vpn-api/Cargo.toml" + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: ${{ env.MANIFEST_PATH }} --all -- --check + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: ${{ env.MANIFEST_PATH }} --workspace --all-targets + + - name: Clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings diff --git a/nym-vpn-api/Cargo.lock b/nym-vpn-api/Cargo.lock new file mode 100644 index 0000000000..3cbaa09188 --- /dev/null +++ b/nym-vpn-api/Cargo.lock @@ -0,0 +1,5387 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +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 = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58", + "hmac", + "k256", + "rand_core 0.6.4", + "ripemd", + "sha2 0.10.8", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "bip39" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +dependencies = [ + "bitcoin_hashes", + "rand", + "rand_core 0.6.4", + "serde", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9" +dependencies = [ + "digest 0.9.0", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "serdect 0.3.0-rc.0", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "bnum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2 0.10.8", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +dependencies = [ + "serde", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +dependencies = [ + "shlex", +] + +[[package]] +name = "celes" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b9a21273925d7cc9e8a9a5f068122341336813c607014f5ef64f82b6acba58" +dependencies = [ + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cosmos-sdk-proto" +version = "0.22.0-pre" +source = "git+https://github.com/cosmos/cosmos-rust?rev=4b1332e6d8258ac845cef71589c8d362a669675a#4b1332e6d8258ac845cef71589c8d362a669675a" +dependencies = [ + "prost", + "prost-types", + "tendermint-proto", +] + +[[package]] +name = "cosmrs" +version = "0.17.0-pre" +source = "git+https://github.com/cosmos/cosmos-rust?rev=4b1332e6d8258ac845cef71589c8d362a669675a#4b1332e6d8258ac845cef71589c8d362a669675a" +dependencies = [ + "bip32", + "cosmos-sdk-proto", + "ecdsa", + "eyre", + "k256", + "rand_core 0.6.4", + "serde", + "serde_json", + "signature", + "subtle-encoding", + "tendermint", + "tendermint-rpc", + "thiserror", +] + +[[package]] +name = "cosmwasm-crypto" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6aa9f904de106fa16443ad14ec2abe75e94ba003bb61c681c0e43d4c58d2a" +dependencies = [ + "digest 0.10.7", + "ecdsa", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e07de16c800ac82fd188d055ecdb923ead0cf33960d3350089260bb982c09f" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ae2e971fb831d0c4fa3c8c3d2291cdbdd73786a73d65196dbf983d9b2468af" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cadc57fd0825b85bc2f9b972c17da718b9efb4bc17e5935cc2d6036324f853d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e98e19fae6c3f468412f731274b0f9434602722009d6a77432d39c7c4bb09202" +dependencies = [ + "base64 0.21.7", + "bnum", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "serde", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw3" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "cw20", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw4" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24754ff6e45f2a1c60adc409d9b2eb87666012c44021329141ffaab3388fccd2" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle 2.6.1", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect 0.2.0", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array 0.14.7", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect 0.2.0", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.6.1", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getset" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "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 = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle 2.6.1", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime 2.1.0", + "serde", +] + +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.8", + "signature", +] + +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[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 = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "cosmrs", + "cosmwasm-std", + "ecdsa", + "getset", + "nym-compact-ecash", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-time", + "nym-mixnet-contract-common", + "nym-network-defaults", + "nym-node-requests", + "nym-serde-helpers", + "schemars", + "serde", + "serde_json", + "sha2 0.10.8", + "tendermint", + "thiserror", + "time", + "utoipa", +] + +[[package]] +name = "nym-bin-common" +version = "0.6.0" +dependencies = [ + "const-str", + "log", + "pretty_env_logger", + "schemars", + "semver", + "serde", + "tracing-subscriber", + "utoipa", + "vergen", +] + +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "nym-multisig-contract-common", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "cw2", + "cw4", + "nym-contracts-common", + "nym-multisig-contract-common", +] + +[[package]] +name = "nym-compact-ecash" +version = "0.1.0" +dependencies = [ + "bincode", + "bls12_381", + "bs58", + "cfg-if", + "digest 0.9.0", + "ff", + "group", + "itertools 0.12.1", + "nym-network-defaults", + "nym-pemstore", + "rand", + "serde", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + +[[package]] +name = "nym-config" +version = "0.1.0" +dependencies = [ + "dirs", + "handlebars", + "log", + "nym-network-defaults", + "serde", + "toml", + "url", +] + +[[package]] +name = "nym-contracts-common" +version = "0.5.0" +dependencies = [ + "bs58", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", + "thiserror", + "vergen", +] + +[[package]] +name = "nym-credentials" +version = "0.1.0" +dependencies = [ + "bincode", + "bls12_381", + "cosmrs", + "log", + "nym-api-requests", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-contract-common", + "nym-ecash-time", + "nym-network-defaults", + "nym-serde-helpers", + "nym-validator-client", + "serde", + "thiserror", + "time", + "zeroize", +] + +[[package]] +name = "nym-credentials-interface" +version = "0.1.0" +dependencies = [ + "bls12_381", + "nym-compact-ecash", + "nym-ecash-time", + "nym-network-defaults", + "rand", + "serde", + "strum", + "thiserror", + "time", +] + +[[package]] +name = "nym-crypto" +version = "0.4.0" +dependencies = [ + "bs58", + "ed25519-dalek", + "nym-pemstore", + "nym-sphinx-types", + "rand", + "serde", + "serde_bytes", + "subtle-encoding", + "thiserror", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "nym-ecash-contract-common" +version = "0.1.0" +dependencies = [ + "bs58", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-multisig-contract-common", + "thiserror", +] + +[[package]] +name = "nym-ecash-time" +version = "0.1.0" +dependencies = [ + "nym-compact-ecash", + "time", +] + +[[package]] +name = "nym-exit-policy" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "tracing", + "utoipa", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cw-controllers", + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-http-api-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "http 1.1.0", + "nym-bin-common", + "reqwest 0.12.4", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "nym-http-api-common" +version = "0.1.0" +dependencies = [ + "axum", + "bytes", + "colored", + "mime", + "serde", + "serde_json", + "serde_yaml", + "tracing", + "utoipa", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.6.0" +dependencies = [ + "bs58", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-storage-plus", + "humantime-serde", + "log", + "nym-contracts-common", + "schemars", + "serde", + "serde-json-wasm", + "serde_repr", + "thiserror", + "time", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-network-defaults" +version = "0.1.0" +dependencies = [ + "dotenvy", + "log", + "schemars", + "serde", + "url", + "utoipa", +] + +[[package]] +name = "nym-node-requests" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "celes", + "humantime 2.1.0", + "humantime-serde", + "nym-bin-common", + "nym-crypto", + "nym-exit-policy", + "nym-wireguard-types", + "schemars", + "serde", + "serde_json", + "thiserror", + "time", + "utoipa", +] + +[[package]] +name = "nym-pemstore" +version = "0.3.0" +dependencies = [ + "pem", +] + +[[package]] +name = "nym-serde-helpers" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "bs58", + "hex", + "serde", + "time", +] + +[[package]] +name = "nym-sphinx-types" +version = "0.2.0" +dependencies = [ + "sphinx-packet", + "thiserror", +] + +[[package]] +name = "nym-validator-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bip32", + "bip39", + "colored", + "cosmrs", + "cosmwasm-std", + "cw-controllers", + "cw-utils", + "cw2", + "cw3", + "cw4", + "eyre", + "flate2", + "futures", + "itertools 0.13.0", + "log", + "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-compact-ecash", + "nym-config", + "nym-contracts-common", + "nym-ecash-contract-common", + "nym-group-contract-common", + "nym-http-api-client", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", + "nym-serde-helpers", + "nym-vesting-contract-common", + "prost", + "reqwest 0.12.4", + "serde", + "serde_json", + "sha2 0.9.9", + "tendermint-rpc", + "thiserror", + "time", + "tokio", + "url", + "wasmtimer", + "zeroize", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.7.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", + "serde", + "thiserror", +] + +[[package]] +name = "nym-vpn-api" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "bip39", + "bs58", + "cfg-if", + "clap", + "colored", + "dotenv", + "futures", + "humantime 2.1.0", + "nym-bin-common", + "nym-compact-ecash", + "nym-config", + "nym-credentials", + "nym-credentials-interface", + "nym-crypto", + "nym-http-api-common", + "nym-network-defaults", + "nym-validator-client", + "nym-vpn-api-requests", + "rand", + "reqwest 0.12.4", + "serde", + "serde_json", + "sqlx", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "time", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", + "url", + "utoipa", + "utoipa-swagger-ui", + "uuid", + "zeroize", +] + +[[package]] +name = "nym-vpn-api-lib-wasm" +version = "0.1.0" +dependencies = [ + "bs58", + "getrandom", + "js-sys", + "nym-bin-common", + "nym-compact-ecash", + "nym-credentials", + "nym-credentials-interface", + "nym-crypto", + "nym-ecash-time", + "nym-vpn-api-requests", + "serde", + "serde-wasm-bindgen 0.6.5", + "serde_json", + "thiserror", + "time", + "tsify", + "wasm-bindgen", + "wasm-utils", + "zeroize", +] + +[[package]] +name = "nym-vpn-api-requests" +version = "0.1.0" +dependencies = [ + "async-trait", + "nym-credentials", + "nym-credentials-interface", + "nym-http-api-client", + "nym-http-api-common", + "nym-serde-helpers", + "reqwest 0.12.4", + "schemars", + "serde", + "serde_json", + "time", + "tsify", + "utoipa", + "uuid", + "wasm-bindgen", + "wasmtimer", +] + +[[package]] +name = "nym-wireguard-types" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "log", + "nym-config", + "nym-network-defaults", + "serde", + "thiserror", + "x25519-dalek", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "peg" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" + +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pest_meta" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[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 2.0.79", +] + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-native-certs", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.26.0", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.4", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.25.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.6", + "winreg 0.52.0", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle 2.6.1", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.79", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2 0.10.8", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[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 = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.29.1", + "syn 2.0.79", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.7", + "pkcs8", + "serdect 0.2.0", + "subtle 2.6.1", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_derive_internals" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[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 = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.6.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "serdect" +version = "0.3.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a504c8ee181e3e594d84052f983d60afe023f4d94d050900be18062bbbf7b58" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.1", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "sphinx-packet" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabeca95bf5fd0563d6be7ebcb1c6a9fcb135746a0ba9050c47dc68c8607e595" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "ctr", + "curve25519-dalek 4.1.3", + "digest 0.10.7", + "hkdf", + "hmac", + "lioness", + "log", + "rand", + "rand_distr", + "sha2 0.10.8", + "subtle 2.6.1", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.11", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.6.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.25.4", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.6.0", + "byteorder", + "bytes", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array 0.14.7", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.6.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time", + "tracing", + "url", + "urlencoding", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.79", +] + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendermint" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "954496fbc9716eb4446cdd6d00c071a3e2f22578d62aa03b40c7e5b4fda3ed42" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost", + "prost-types", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle 2.6.1", + "subtle-encoding", + "tendermint-proto", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-config" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84b11b57d20ee4492a1452faff85f5c520adc36ca9fe5e701066935255bb89f" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint", + "toml", + "url", +] + +[[package]] +name = "tendermint-proto" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc87024548c7f3da479885201e3da20ef29e85a3b13d04606b380ac4c7120d87" +dependencies = [ + "bytes", + "flex-error", + "prost", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-rpc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdc2281e271277fda184d96d874a6fe59f569b130b634289257baacfc95aa85" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom", + "peg", + "pin-project", + "rand", + "reqwest 0.11.27", + "semver", + "serde", + "serde_bytes", + "serde_json", + "subtle 2.6.1", + "subtle-encoding", + "tendermint", + "tendermint-config", + "tendermint-proto", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "js-sys", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +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.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "hashbrown 0.14.5", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tsify" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" +dependencies = [ + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.28.0", + "syn 2.0.79", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.79", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943e0ff606c6d57d410fd5663a4d7c074ab2c5f14ab903b9514565e59fa1189e" +dependencies = [ + "axum", + "mime_guess", + "regex", + "reqwest 0.12.4", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "8.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" +dependencies = [ + "anyhow", + "cargo_metadata", + "cfg-if", + "regex", + "rustc_version", + "rustversion", + "time", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "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.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.79", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" + +[[package]] +name = "wasm-utils" +version = "0.1.0" +dependencies = [ + "futures", + "gloo-utils 0.2.0", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "web-sys" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44188d185b5bdcae1052d08bcbcf9091a5524038d4572cc4f4f2bb9d5554ddd9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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", + "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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "zip" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.6.0", + "num_enum", + "thiserror", +] diff --git a/nym-vpn-api/Cargo.toml b/nym-vpn-api/Cargo.toml new file mode 100644 index 0000000000..7b08ce1792 --- /dev/null +++ b/nym-vpn-api/Cargo.toml @@ -0,0 +1,62 @@ +[profile.release] +panic = "abort" +opt-level = "s" +overflow-checks = true + +[profile.dev] +panic = "abort" + +[workspace] + +resolver = "2" +members = [ + "vpn-api-server", + "vpn-api-requests", + "vpn-api-lib-wasm" +] + +[workspace.package] +authors = ["Nym Technologies SA"] +repository = "https://github.com/nymtech/nym" +homepage = "https://nymtech.net" +documentation = "https://nymtech.net" +edition = "2021" +license = "GPL-3.0" + +[workspace.dependencies] +async-trait = "0.1.80" +axum = "0.7.5" +anyhow = "1.0.71" +bip39 = "2.0.0" +bs58 = "0.5.1" +colored = "2.1.0" +cfg-if = "1.0.0" +clap = "4.5.4" +dotenv = "0.15.0" +futures = "0.3.30" +humantime = "2.1.0" +thiserror = "1.0.59" +rand = "0.8.5" +reqwest = "0.12.4" +schemars = "0.8.17" +strum = "0.26.3" +strum_macros = "0.26.4" +serde = "1.0.200" +serde_json = "1.0.117" +sqlx = "0.7.4" +tempfile = "3.12.0" +time = "0.3.36" +tracing = "0.1.40" +tsify = "0.4.5" +tokio = "1.37.0" +tokio-util = "0.7.10" +tower = "0.5.0" +tower-http = "0.5.2" +uuid = "1.8.0" +url = "2.5.2" +utoipa = "4.2.0" +utoipa-swagger-ui = "7.0.1" +zeroize = "1.6.0" + +wasm-bindgen = "0.2.93" +wasmtimer = "0.2.0" diff --git a/nym-vpn-api/vpn-api-lib-wasm/Cargo.toml b/nym-vpn-api/vpn-api-lib-wasm/Cargo.toml new file mode 100644 index 0000000000..b5c68c9b47 --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "nym-vpn-api-lib-wasm" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +bs58 = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +time = { workspace = true, features = ["wasm-bindgen"] } +thiserror.workspace = true +zeroize = { workspace = true } + +# wasm-specific deps +getrandom = { version = "0.2", features = ["js"] } +wasm-bindgen = { workspace = true } +js-sys = "0.3.70" +tsify = { workspace = true, features = ["js"] } +serde-wasm-bindgen = "0.6.5" + +# NYM: +nym-bin-common = { path = "../../common/bin-common" } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } +nym-credentials = { path = "../../common/credentials" } +nym-credentials-interface = { path = "../../common/credentials-interface" } +nym-ecash-time = { path = "../../common/ecash-time", features = ["expiration"] } +nym-vpn-api-requests = { path = "../vpn-api-requests", default-features = false, features = ["tsify"] } +wasm-utils = { path = "../../common/wasm/utils" } + diff --git a/nym-vpn-api/vpn-api-lib-wasm/Makefile b/nym-vpn-api/vpn-api-lib-wasm/Makefile new file mode 100644 index 0000000000..2edce6e5fc --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/Makefile @@ -0,0 +1,4 @@ +build: + wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/nym-vpn-api-lib-wasm + wasm-opt -Oz -o ../../dist/wasm/nym-vpn-api-lib-wasm/nym_vpn_api_lib_wasm_bg.wasm ../../dist/wasm/nym-vpn-api-lib-wasm/nym_vpn_api_lib_wasm_bg.wasm + diff --git a/nym-vpn-api/vpn-api-lib-wasm/internal-dev/bootstrap.js b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/bootstrap.js new file mode 100644 index 0000000000..362cdb2431 --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/bootstrap.js @@ -0,0 +1,2 @@ +import("./index.js") + .catch(e => console.error("Error importing `index.js`:", e)); diff --git a/nym-vpn-api/vpn-api-lib-wasm/internal-dev/index.html b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/index.html new file mode 100644 index 0000000000..5cd272596a --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/index.html @@ -0,0 +1,17 @@ + + + + + + + Nym WebAssembly Demo + + + + + +

everything happens in the console

+ + + + \ No newline at end of file diff --git a/nym-vpn-api/vpn-api-lib-wasm/internal-dev/index.js b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/index.js new file mode 100644 index 0000000000..3921411115 --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/index.js @@ -0,0 +1,49 @@ +import init, { NymIssuanceTicketbook } from "@nymproject/nym-vpn-api-lib-wasm"; + +async function main() { + await init(); + + let cryptoData = new NymIssuanceTicketbook({}); + + console.log("getting partial vks"); + const partialVksRes = await fetch("http://localhost:8080/api/v1/ticketbook/partial-verification-keys", { + headers: new Headers({ "Authorization": "Bearer foomp" }) + }); + const partialVks = await partialVksRes.json(); + console.debug(partialVks); + + console.log("getting master vk"); + const masterVkRes = await fetch("http://localhost:8080/api/v1/ticketbook/master-verification-key", { + headers: new Headers({ "Authorization": "Bearer foomp" }) + }); + const masterVk = await masterVkRes.json(); + console.debug(masterVk); + + let request = cryptoData.buildRequestPayload(false); + console.log(request); + + + console.log("getting blinded wallet shares"); + const sharesRes = await fetch("http://localhost:8080/api/v1/ticketbook/obtain?include-coin-index-signatures=true&include-expiration-date-signatures=true", { + method: "POST", + headers: new Headers( + { + "Authorization": "Bearer foomp", + "Content-Type": "application/json" + } + ), + body: request + }); + + const credentialShares = await sharesRes.json(); + console.log(credentialShares); + + console.log("unblinding shares"); + const unblinded = cryptoData.unblindWalletShares(credentialShares, partialVks, masterVk); + + const serialised = unblinded.serialise(); + console.log("serialised:\n", serialised); +} + + +main(); diff --git a/nym-vpn-api/vpn-api-lib-wasm/internal-dev/package.json b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/package.json new file mode 100644 index 0000000000..17032501e8 --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/package.json @@ -0,0 +1,40 @@ +{ + "name": "create-wasm-app", + "version": "0.1.0", + "description": "create an app to consume rust-generated wasm packages", + "main": "index.js", + "bin": { + "create-wasm-app": ".bin/create-wasm-app.js" + }, + "scripts": { + "build": "webpack --config webpack.config.js", + "build:wasm": "cd ../ && make wasm-build", + "start": "webpack-dev-server --port 8001" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/rustwasm/create-wasm-app.git" + }, + "keywords": [ + "webassembly", + "wasm", + "rust", + "webpack" + ], + "author": "Dave Hrycyszyn ", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/nymtech/nym/issues" + }, + "homepage": "https://nymtech.net/docs", + "devDependencies": { + "copy-webpack-plugin": "^11.0.0", + "hello-wasm-pack": "^0.1.0", + "webpack": "^5.70.0", + "webpack-cli": "^4.9.2", + "webpack-dev-server": "^4.7.4" + }, + "dependencies": { + "@nymproject/nym-vpn-api-lib-wasm": "file:../../../dist/wasm/nym-vpn-api-lib-wasm" + } +} diff --git a/nym-vpn-api/vpn-api-lib-wasm/internal-dev/webpack.config.js b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/webpack.config.js new file mode 100644 index 0000000000..d5f839485f --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/webpack.config.js @@ -0,0 +1,33 @@ +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const path = require("path"); + +module.exports = { + performance: { + hints: false, + maxEntrypointSize: 512000, + maxAssetSize: 512000 + }, + entry: { + bootstrap: "./bootstrap.js" + // worker: "./worker.js" + }, + output: { + path: path.resolve(__dirname, "dist"), + filename: "[name].js" + }, + mode: "development", + // mode: 'production', + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + "index.html", + { + from: "../../../dist/wasm/nym-vpn-api-lib-wasm/*.(js|wasm)", + to: "[name][ext]" + } + ] + }) + + ], + experiments: { syncWebAssembly: true } +}; diff --git a/nym-vpn-api/vpn-api-lib-wasm/internal-dev/yarn.lock b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/yarn.lock new file mode 100644 index 0000000000..43e804cb6e --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/internal-dev/yarn.lock @@ -0,0 +1,2253 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@nymproject/nym-vpn-api-lib-wasm@file:../../../dist/wasm/nym-vpn-api-lib-wasm": + version "0.1.0" + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/http-proxy@^1.17.8": + version "1.17.15" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36" + integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ== + dependencies: + "@types/node" "*" + +"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "22.5.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958" + integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg== + dependencies: + undici-types "~6.19.2" + +"@types/qs@*": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.1": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/sockjs@^0.3.33": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.5": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== + dependencies: + "@types/node" "*" + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" + integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== + +"@webpack-cli/info@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" + integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" + integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn@^8.7.1, acorn@^8.8.2: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" + integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== + dependencies: + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.21.10: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +caniuse-lite@^1.0.30001646: + version "1.0.30001653" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz#b8af452f8f33b1c77f122780a4aecebea0caca56" + integrity sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw== + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +colorette@^2.0.10, colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.5.4: + version "1.5.13" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" + integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +enhanced-resolve@^5.17.1: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.7.3: + version "7.13.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" + integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.3: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.11, fast-glob@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-uri@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +follow-redirects@^1.0.0: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hello-wasm-pack@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/hello-wasm-pack/-/hello-wasm-pack-0.1.0.tgz#482a2e3371828056ac35f5b5fec76c0b99dcd530" + integrity sha512-3hx0GDkDLf/a9ThCMV2qG4mwza8N/MCtm8aeFFc/cdBCL2zMJ1kW1wjNl7xPqD1lz8Yl5+uhnc/cpui4dLwz/w== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.3.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" + integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +launch-editor@^2.6.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.1.tgz#3bda72af213ec9b46b170e39661916ec66c2f463" + integrity sha512-elBx2l/tp9z99X5H/qev8uyDywVh0VXAwEbjk8kJhnc5grOFkGh7aW6q55me9xnYbss261XtnUrysZ+XvGbhQA== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.8.1" + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +"mime-db@>= 1.43.0 < 2": + version "1.53.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" + integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.9.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@^5.26.0: + version "5.31.6" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.6.tgz#c63858a0f0703988d0266a82fcbf2d7ba76422b1" + integrity sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +webpack-cli@^4.9.2: + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" + integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.2.0" + "@webpack-cli/info" "^1.5.0" + "@webpack-cli/serve" "^1.7.0" + colorette "^2.0.14" + commander "^7.0.0" + cross-spawn "^7.0.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + webpack-merge "^5.7.3" + +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.7.4: + version "4.15.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" + integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.4" + ws "^8.13.0" + +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.70.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + dependencies: + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.13.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== diff --git a/nym-vpn-api/vpn-api-lib-wasm/src/error.rs b/nym-vpn-api/vpn-api-lib-wasm/src/error.rs new file mode 100644 index 0000000000..7322254064 --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/src/error.rs @@ -0,0 +1,35 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use serde_wasm_bindgen::Error; +use thiserror::Error; +use wasm_utils::wasm_error; + +#[derive(Debug, Error)] +pub enum VpnApiLibError { + #[error("{0}")] + Json(String), + + #[error("[ecash] cryptographic failure: {source}")] + EcashFailure { + #[from] + source: nym_compact_ecash::CompactEcashError, + }, + + #[error("provided invalid ticket type")] + MalformedTicketType, + + #[error("the provided shares and issuers are not from the same epoch! {shares} and {issuers}")] + InconsistentEpochId { shares: u64, issuers: u64 }, + + #[error("failed to recover ed25519 private key from its base58 representation")] + MalformedEd25519Key, +} + +wasm_error!(VpnApiLibError); + +impl From for VpnApiLibError { + fn from(value: Error) -> Self { + VpnApiLibError::Json(value.to_string()) + } +} diff --git a/nym-vpn-api/vpn-api-lib-wasm/src/lib.rs b/nym-vpn-api/vpn-api-lib-wasm/src/lib.rs new file mode 100644 index 0000000000..b4cf50b2b9 --- /dev/null +++ b/nym-vpn-api/vpn-api-lib-wasm/src/lib.rs @@ -0,0 +1,274 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiLibError; +use nym_compact_ecash::scheme::keygen::KeyPairUser; +use nym_compact_ecash::scheme::withdrawal::RequestInfo; +use nym_compact_ecash::{ + aggregate_wallets, issue_verify, withdrawal_request, Base58, BlindedSignature, + VerificationKeyAuth, WithdrawalRequest, +}; +use nym_credentials::{ + AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey, + IssuedTicketBook, +}; +use nym_credentials_interface::TicketType; +use nym_crypto::asymmetric::ed25519; +use nym_ecash_time::{ecash_default_expiration_date, EcashTime}; +use nym_vpn_api_requests::api::v1::ticketbook::models::{ + MasterVerificationKeyResponse, PartialVerificationKeysResponse, TicketbookRequest, + TicketbookWalletSharesResponse, WalletShare, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use time::Date; +use tsify::Tsify; +use wasm_bindgen::prelude::*; +use wasm_utils::console_error; +use zeroize::Zeroizing; + +pub mod error; + +#[derive(Tsify, Debug, Default, Clone, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[serde(rename_all = "camelCase")] +pub struct WalletShares(Vec); + +pub type WalletIssuers = PartialVerificationKeysResponse; + +impl From> for WalletShares { + fn from(shares: Vec) -> Self { + WalletShares(shares) + } +} + +#[derive(Tsify, Debug, Default, Clone, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[serde(rename_all = "camelCase")] +pub struct NymIssuanceTicketbookOpts { + #[tsify(optional)] + pub ticketbook_type: Option, + + // bs58-encoded user secret key used for seeding the ecash crypto keypair generation + // I reiterate, this is a **SECRET** key, not a public key. + #[tsify(optional)] + pub user_secret_key: Option, +} + +#[wasm_bindgen] +#[derive(Debug)] +#[allow(dead_code)] +pub struct NymIssuanceTicketbook { + /// ecash keypair related to the credential + ecash_keypair: KeyPairUser, + + withdrawal_request: WithdrawalRequest, + + ticketbook_type: TicketType, + + expiration_date: Date, + + request_info: Zeroizing, +} + +#[wasm_bindgen] +impl NymIssuanceTicketbook { + #[wasm_bindgen(constructor)] + pub fn new(opts: NymIssuanceTicketbookOpts) -> Result { + let ecash_keypair = match opts.user_secret_key { + None => KeyPairUser::new(), + Some(maybe_sk) => { + let pk = ed25519::PrivateKey::from_base58_string(maybe_sk) + .map(Zeroizing::new) + .map_err(|_| VpnApiLibError::MalformedEd25519Key)?; + let bytes = Zeroizing::new(pk.to_bytes()); + KeyPairUser::new_seeded(&bytes) + } + }; + + let ticketbook_type = match opts.ticketbook_type { + None => TicketType::V1MixnetEntry, + Some(typ) => typ + .parse() + .map_err(|_| VpnApiLibError::MalformedTicketType)?, + }; + + let expiration_date = ecash_default_expiration_date(); + + let (withdrawal_request, request_info) = withdrawal_request( + ecash_keypair.secret_key(), + expiration_date.ecash_unix_timestamp(), + ticketbook_type.encode(), + )?; + + Ok(NymIssuanceTicketbook { + ecash_keypair, + withdrawal_request, + ticketbook_type, + expiration_date, + request_info: Zeroizing::new(request_info), + }) + } + + #[wasm_bindgen(js_name = "buildRequestPayload")] + pub fn build_request_payload(&self, is_freepass_request: bool) -> String { + serde_json::to_string(&TicketbookRequest { + withdrawal_request: self.withdrawal_request.clone().into(), + ecash_pubkey: self.ecash_keypair.public_key(), + expiration_date: self.expiration_date, + ticketbook_type: self.ticketbook_type, + is_freepass_request, + }) + .unwrap() + } + + #[wasm_bindgen(js_name = "getWithdrawalRequest")] + pub fn get_encoded_withdrawal_request(&self) -> String { + self.withdrawal_request.to_bs58() + } + + #[wasm_bindgen(js_name = "getEncodedPublicKey")] + pub fn get_encoded_public_key(&self) -> String { + self.ecash_keypair.public_key().to_bs58() + } + + // + // #[wasm_bindgen(js_name = "unblindShare")] + // pub fn unblind_share(&self, share: UnblindableShare) -> Result { + // let blinded_sig = BlindedSignature::try_from_bs58(share.blinded_share_bs58)?; + // let vk = VerificationKey::try_from_bs58(share.issuer_key_bs58)?; + // + // Ok(blinded_sig + // .unblind(&vk, &self.pedersen_commitments_openings) + // .into()) + // } + // + #[wasm_bindgen(js_name = "unblindWalletShares")] + pub fn unblind_wallet_shares( + self, + shares: JsValue, + issuers: WalletIssuers, + master_key: MasterVerificationKeyResponse, + ) -> Result { + // we couldn't derive all the required abi traits due to crypto types deep in the stack + let shares: TicketbookWalletSharesResponse = serde_wasm_bindgen::from_value(shares)?; + + if shares.epoch_id != issuers.epoch_id { + console_error!( + "the provided shares and issuers are not from the same epoch! {} and {}", + shares.epoch_id, + issuers.epoch_id + ); + return Err(VpnApiLibError::InconsistentEpochId { + shares: shares.epoch_id, + issuers: issuers.epoch_id, + }); + } + + let master_vk = VerificationKeyAuth::try_from_bs58(master_key.bs58_encoded_key)?; + + let mut decoded_keys = HashMap::new(); + for key in issuers.keys { + let vk = VerificationKeyAuth::try_from_bs58(key.bs58_encoded_key)?; + decoded_keys.insert(key.node_index, vk); + } + + let mut partial_wallets = Vec::new(); + for share in shares.shares { + let blinded_sig = BlindedSignature::try_from_bs58(share.bs58_encoded_share)?; + let Some(vk) = decoded_keys.get(&share.node_index) else { + console_error!("received a share from issuer {} but did not receive a corresponding verification key!", share.node_index); + continue; + }; + + match issue_verify( + vk, + self.ecash_keypair.secret_key(), + &blinded_sig, + &self.request_info, + share.node_index, + ) { + Ok(partial_wallet) => partial_wallets.push(partial_wallet), + Err(err) => { + console_error!( + "failed to unblind partial wallet corresponding to index {}: {err}", + share.node_index + ) + } + } + } + + let aggregated_wallet = aggregate_wallets( + &master_vk, + self.ecash_keypair.secret_key(), + &partial_wallets, + &self.request_info, + )?; + + Ok(NymIssuedTicketbook { + inner_ticketbook: IssuedTicketBook::new( + aggregated_wallet.into_wallet_signatures(), + shares.epoch_id, + self.ecash_keypair.into(), + self.ticketbook_type, + self.expiration_date, + ), + master_vk: EpochVerificationKey { + epoch_id: shares.epoch_id, + key: master_vk, + }, + expiration_date_signatures: shares + .aggregated_expiration_date_signatures + .map(|s| s.signatures), + coin_index_signatures: shares + .aggregated_coin_index_signatures + .map(|s| s.signatures), + }) + } +} + +#[wasm_bindgen] +pub struct NymIssuedTicketbook { + inner_ticketbook: IssuedTicketBook, + + master_vk: EpochVerificationKey, + expiration_date_signatures: Option, + coin_index_signatures: Option, +} + +#[wasm_bindgen] +impl NymIssuedTicketbook { + pub fn serialise(self) -> FullSerialisedNymIssuedTicketbook { + let serialised = self + .inner_ticketbook + .begin_export() + .with_master_verification_key(&self.master_vk) + .with_maybe_expiration_date_signatures(&self.expiration_date_signatures) + .with_maybe_coin_index_signatures(&self.coin_index_signatures) + .finalize_export(); + + FullSerialisedNymIssuedTicketbook { + serialisation_revision: serialised.revision, + bs58_encoded_data: bs58::encode(serialised.data).into_string(), + } + } +} + +#[derive(Tsify, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[serde(rename_all = "camelCase")] +pub struct FullSerialisedNymIssuedTicketbook { + pub serialisation_revision: u8, + pub bs58_encoded_data: String, +} + +#[wasm_bindgen(start)] +pub fn main() { + wasm_utils::console_log!("[rust main]: rust module loaded"); + wasm_utils::console_log!( + "vpn-api-lib version used:\n{}", + nym_bin_common::bin_info!().pretty_print() + ); + wasm_utils::console_log!("[rust main]: setting panic hook"); + wasm_utils::set_panic_hook(); +} diff --git a/nym-vpn-api/vpn-api-requests/Cargo.toml b/nym-vpn-api/vpn-api-requests/Cargo.toml new file mode 100644 index 0000000000..51bb324d07 --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "nym-vpn-api-requests" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = { workspace = true } +schemars = { workspace = true, features = ["preserve_order", "uuid1"] } +uuid = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +time = { workspace = true, features = ["serde", "formatting", "parsing"] } +tsify = { workspace = true, optional = true } +reqwest = { workspace = true, features = ["json"] } +wasm-bindgen = { workspace = true, optional = true } + +## openapi: +utoipa = { workspace = true, optional = true } + +nym-credentials = { path = "../../common/credentials" } +nym-credentials-interface = { path = "../../common/credentials-interface" } +nym-http-api-common = { path = "../../common/http-api-common", optional = true } +nym-http-api-client = { path = "../../common/http-api-client" } +nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] } + +[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] +workspace = true +features = ["tokio"] + + +[features] +default = ["query-types"] +query-types = ["nym-http-api-common"] +openapi = ["utoipa"] +tsify = ["dep:tsify", "wasm-bindgen"] diff --git a/nym-vpn-api/vpn-api-requests/src/api/mod.rs b/nym-vpn-api/vpn-api-requests/src/api/mod.rs new file mode 100644 index 0000000000..60df60aa1f --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/api/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod v1; diff --git a/nym-vpn-api/vpn-api-requests/src/api/v1/mod.rs b/nym-vpn-api/vpn-api-requests/src/api/v1/mod.rs new file mode 100644 index 0000000000..2e131231cd --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/api/v1/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +// pub mod bandwidth_voucher; +// pub mod freepass; +pub mod ticketbook; + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct ErrorResponse { + #[cfg_attr(feature = "openapi",schema(value_type = Option, example = "c48f9ce3-a1e9-4886-8000-13f290f34501"))] + pub uuid: Option, + pub message: String, +} diff --git a/nym-vpn-api/vpn-api-requests/src/api/v1/ticketbook/mod.rs b/nym-vpn-api/vpn-api-requests/src/api/v1/ticketbook/mod.rs new file mode 100644 index 0000000000..363be75767 --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/api/v1/ticketbook/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod models; diff --git a/nym-vpn-api/vpn-api-requests/src/api/v1/ticketbook/models.rs b/nym-vpn-api/vpn-api-requests/src/api/v1/ticketbook/models.rs new file mode 100644 index 0000000000..6ce58e81af --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/api/v1/ticketbook/models.rs @@ -0,0 +1,282 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_credentials::ecash::bandwidth::serialiser::signatures::{ + AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, +}; +use nym_credentials_interface::{PublicKeyUser, TicketType, WithdrawalRequest}; +use schemars::gen::SchemaGenerator; +use schemars::schema::Schema; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, DerefMut}; +use time::Date; + +#[cfg(feature = "query-types")] +use nym_http_api_common::Output; + +#[cfg(feature = "tsify")] +use tsify::Tsify; +use uuid::Uuid; + +#[cfg(feature = "tsify")] +use wasm_bindgen::prelude::wasm_bindgen; + +#[derive(JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct PlaceholderJsonSchemaImpl {} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct TicketbookRequest { + /// base58 encoded withdrawal request + pub withdrawal_request: WithdrawalRequestBs58Wrapper, + + /// bs58-encoded **ECASH** public key. + /// this is **NOT** a device key or anything like that. + /// it is derived from user's **SECRET** key! + /// + /// you **MUST** provide a valid value otherwise blacklisting won't work + #[schemars(with = "String")] + #[serde(with = "bs58_ecash")] + pub ecash_pubkey: PublicKeyUser, + + // needs to be explicit in case user creates request at 23:59:59.999, but it reaches vpn-api at 00:00:00.001 + #[schemars(with = "String")] + #[serde(with = "crate::helpers::date_serde")] + pub expiration_date: Date, + + #[schemars(with = "String")] + pub ticketbook_type: TicketType, + + pub is_freepass_request: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct TicketbookAsyncRequest { + #[serde(flatten)] + pub inner: TicketbookRequest, + + /// unique id of the device + pub device_id: String, + /// unique id of the credential + pub credential_id: String, + /// secret used for webhook responses + pub secret: String, +} + +mod bs58_ecash { + use nym_credentials_interface::Base58; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(req: &T, serializer: S) -> Result + where + T: Base58, + { + serializer.serialize_str(&req.to_bs58()) + } + + pub fn deserialize<'de, D: Deserializer<'de>, T>(deserializer: D) -> Result + where + T: Base58, + { + let s = ::deserialize(deserializer)?; + T::try_from_bs58(&s).map_err(serde::de::Error::custom) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "openapi", schema(value_type = String))] +pub struct WithdrawalRequestBs58Wrapper(#[serde(with = "bs58_ecash")] pub WithdrawalRequest); + +impl From for WithdrawalRequest { + fn from(value: WithdrawalRequestBs58Wrapper) -> Self { + value.0 + } +} + +impl From for WithdrawalRequestBs58Wrapper { + fn from(value: WithdrawalRequest) -> Self { + WithdrawalRequestBs58Wrapper(value) + } +} + +impl Deref for WithdrawalRequestBs58Wrapper { + type Target = WithdrawalRequest; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for WithdrawalRequestBs58Wrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +// implementation taken from: https://github.com/GREsau/schemars/pull/207 +impl JsonSchema for WithdrawalRequestBs58Wrapper { + fn is_referenceable() -> bool { + true + } + + fn schema_name() -> String { + "WithdrawalRequestBs58Wrapper".into() + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + // during serialisation we just use bs58 representation + String::json_schema(gen) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "tsify", derive(Tsify))] +#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct CurrentEpochResponse { + pub epoch_id: u64, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "tsify", derive(Tsify))] +#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct PartialVerificationKeysResponse { + pub epoch_id: u64, + pub keys: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "tsify", derive(Tsify))] +#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct PartialVerificationKey { + pub node_index: u64, + pub bs58_encoded_key: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "tsify", derive(Tsify))] +#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct MasterVerificationKeyResponse { + pub epoch_id: u64, + pub bs58_encoded_key: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct DepositResponse { + pub current_deposit_amount: u128, + pub current_deposit_denom: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct AggregatedExpirationDateSignaturesResponse { + #[schemars(with = "PlaceholderJsonSchemaImpl")] + #[cfg_attr(feature = "openapi", schema(value_type = PlaceholderJsonSchemaImpl))] + pub signatures: AggregatedExpirationDateSignatures, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct AggregatedCoinIndicesSignaturesResponse { + #[schemars(with = "PlaceholderJsonSchemaImpl")] + #[cfg_attr(feature = "openapi", schema(value_type = PlaceholderJsonSchemaImpl))] + pub signatures: AggregatedCoinIndicesSignatures, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "tsify", derive(Tsify))] +#[cfg_attr(feature = "tsify", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct WalletShare { + pub node_index: u64, + pub bs58_encoded_share: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct TicketbookWalletSharesResponse { + pub epoch_id: u64, + pub shares: Vec, + pub master_verification_key: Option, + pub aggregated_coin_index_signatures: Option, + pub aggregated_expiration_date_signatures: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct TicketbookWalletSharesAsyncResponse { + pub id: i64, + + // maybe redundant, but could be useful for debugging + pub uuid: Uuid, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct BlindedWalletSharesResponse { + pub id: i64, + pub status: String, + pub device_id: String, + pub credential_id: String, + pub data: Option, + pub error_message: Option, + pub created: String, + pub updated: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct WebhookBlindedSharesResponse { + pub blinded_shares: BlindedWalletSharesResponse, + pub secret: String, +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema, utoipa::IntoParams))] +#[cfg(feature = "query-types")] +#[serde(default, rename_all = "kebab-case")] +pub struct TicketbookObtainQueryParams { + pub output: Option, + + pub include_master_verification_key: bool, + + pub include_coin_index_signatures: bool, + + pub include_expiration_date_signatures: bool, +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema, utoipa::IntoParams))] +#[cfg(feature = "query-types")] +#[serde(default, rename_all = "kebab-case")] +pub struct SharesQueryParams { + pub output: Option, + + pub include_master_verification_key: bool, + + pub include_coin_index_signatures: bool, + + pub include_expiration_date_signatures: bool, +} diff --git a/nym-vpn-api/vpn-api-requests/src/client.rs b/nym-vpn-api/vpn-api-requests/src/client.rs new file mode 100644 index 0000000000..301656db79 --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/client.rs @@ -0,0 +1,172 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::api::v1::ticketbook::models::{ + MasterVerificationKeyResponse, PartialVerificationKeysResponse, TicketbookRequest, + TicketbookWalletSharesResponse, +}; +use async_trait::async_trait; +use nym_http_api_client::{parse_response, HttpClientError, Params, PathSegments, NO_PARAMS}; +use reqwest::IntoUrl; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +pub use nym_http_api_client::Client; +pub type VpnApiClientError = HttpClientError; + +#[allow(dead_code)] +pub struct VpnApiClient { + inner: Client, + bearer_token: String, +} + +pub fn new_client( + base_url: impl IntoUrl, + bearer_token: impl Into, +) -> Result { + Ok(VpnApiClient { + inner: Client::builder(base_url)? + .with_user_agent(format!( + "nym-vpn-api-requests/{}", + env!("CARGO_PKG_VERSION") + )) + .build()?, + bearer_token: bearer_token.into(), + }) +} + +// TODO: do it properly by implementing auth headers on `ApiClient` trait +#[allow(dead_code)] +#[async_trait(?Send)] +pub trait NymVpnApiClient { + async fn simple_get(&self, path: PathSegments<'_>) -> Result + where + T: DeserializeOwned; + + async fn simple_post( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + json_body: &B, + ) -> Result + where + B: Serialize + ?Sized, + for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef; + + async fn get_partial_verification_keys( + &self, + ) -> Result { + self.simple_get(&["/api", "/v1", "/ticketbook", "/partial-verification-keys"]) + .await + } + + async fn get_master_verification_key( + &self, + ) -> Result { + self.simple_get(&["/api", "/v1", "/ticketbook", "/master-verification-key"]) + .await + } + + async fn get_ticketbook_wallet_shares( + &self, + request: &TicketbookRequest, + full_response: bool, + ) -> Result { + let params = vec![("full-response", full_response.to_string())]; + + self.simple_post(&["/api", "/v1", "/ticketbook", "/obtain"], ¶ms, request) + .await + } + // + // async fn get_bandwidth_voucher_blinded_shares( + // &self, + // blind_sign_request: BlindSignRequest, + // ) -> Result; +} + +#[async_trait(?Send)] +impl NymVpnApiClient for VpnApiClient { + async fn simple_get(&self, path: PathSegments<'_>) -> Result + where + T: DeserializeOwned, + { + let req = self + .inner + .create_get_request(path, NO_PARAMS) + .bearer_auth(&self.bearer_token) + .send(); + + // the only reason for that target lock is so that I could call this method from an ephemeral test + // running in non-wasm mode (since I wanted to use tokio) + + #[cfg(target_arch = "wasm32")] + let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), req) + .await + .map_err(|_timeout| HttpClientError::RequestTimeout)??; + + #[cfg(not(target_arch = "wasm32"))] + let res = req.await?; + + parse_response(res, false).await + } + + async fn simple_post( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + json_body: &B, + ) -> Result + where + B: Serialize + ?Sized, + for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef, + { + let req = self + .inner + .create_post_request(path, params, json_body) + .bearer_auth(&self.bearer_token) + .send(); + + // the only reason for that target lock is so that I could call this method from an ephemeral test + // running in non-wasm mode (since I wanted to use tokio) + + #[cfg(target_arch = "wasm32")] + let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), req) + .await + .map_err(|_timeout| HttpClientError::RequestTimeout)??; + + #[cfg(not(target_arch = "wasm32"))] + let res = req.await?; + + parse_response(res, false).await + } + + // async fn get_bandwidth_voucher_blinded_shares( + // &self, + // blind_sign_request: BlindSignRequest, + // ) -> Result { + // let req = self.inner.create_post_request( + // &["/api", "/v1", "/bandwidth-voucher", "/obtain"], + // NO_PARAMS, + // &BandwidthVoucherRequest { blind_sign_request }, + // ); + // + // let fut = req.bearer_auth(&self.bearer_token).send(); + // + // // the only reason for that target lock is so that I could call this method from an ephemeral test + // // running in non-wasm mode (since I wanted to use tokio) + // + // #[cfg(target_arch = "wasm32")] + // let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut) + // .await + // .map_err(|_timeout| HttpClientError::RequestTimeout)??; + // + // #[cfg(not(target_arch = "wasm32"))] + // let res = fut.await?; + // + // parse_response(res, false).await + // } +} diff --git a/nym-vpn-api/vpn-api-requests/src/helpers.rs b/nym-vpn-api/vpn-api-requests/src/helpers.rs new file mode 100644 index 0000000000..5420be21c5 --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/helpers.rs @@ -0,0 +1,40 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use time::format_description::{modifier, BorrowedFormatItem, Component}; + +const DATE_FORMAT: &[BorrowedFormatItem<'_>] = &[ + BorrowedFormatItem::Component(Component::Year(modifier::Year::default())), + BorrowedFormatItem::Literal(b"-"), + BorrowedFormatItem::Component(Component::Month(modifier::Month::default())), + BorrowedFormatItem::Literal(b"-"), + BorrowedFormatItem::Component(Component::Day(modifier::Day::default())), +]; + +pub(crate) mod date_serde { + use crate::helpers::DATE_FORMAT; + use serde::ser::Error; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use time::Date; + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + + Date::parse(&s, DATE_FORMAT).map_err(de::Error::custom) + } + + pub(crate) fn serialize(datetime: &Date, serializer: S) -> Result + where + S: Serializer, + { + // serialize it with human-readable format for compatibility with eclipse and nutella clients + // in the future change it back to rfc3339 + datetime + .format(&DATE_FORMAT) + .map_err(S::Error::custom)? + .serialize(serializer) + } +} diff --git a/nym-vpn-api/vpn-api-requests/src/lib.rs b/nym-vpn-api/vpn-api-requests/src/lib.rs new file mode 100644 index 0000000000..8415c35ba6 --- /dev/null +++ b/nym-vpn-api/vpn-api-requests/src/lib.rs @@ -0,0 +1,84 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] +#![warn(clippy::todo)] +#![warn(clippy::dbg_macro)] + +pub mod api; +pub mod client; +mod helpers; + +macro_rules! absolute_route { + ( $name:ident, $parent:expr, $suffix:expr ) => { + pub fn $name() -> String { + format!("{}{}", $parent, $suffix) + } + }; +} + +pub mod routes { + pub const ROOT: &str = "/"; + pub const API: &str = "/api"; + + pub mod api { + pub const V1: &str = "/v1"; + + absolute_route!(v1_absolute, super::API, V1); + + pub mod v1 { + use super::*; + + pub const SWAGGER: &str = "/swagger"; + pub const TICKETBOOK: &str = "/ticketbook"; + + // define helper functions to get absolute routes + absolute_route!(swagger_absolute, v1_absolute(), SWAGGER); + absolute_route!(ticketbook_absolute, v1_absolute(), TICKETBOOK); + + pub mod ticketbook { + use super::*; + + pub const OBTAIN: &str = "/obtain"; + pub const OBTAIN_ASYNC: &str = "/obtain-async"; + pub const DEPOSIT_AMOUNT: &str = "/deposit-amount"; + pub const MASTER_KEY: &str = "/master-verification-key"; + pub const PARTIAL_KEYS: &str = "/partial-verification-keys"; + pub const CURRENT_EPOCH: &str = "/current-epoch"; + pub const SHARES: &str = "/shares"; + + absolute_route!(obtain_wallet_shares_absolute, ticketbook_absolute(), OBTAIN); + absolute_route!( + obtain_async_wallet_shares_absolute, + ticketbook_absolute(), + OBTAIN_ASYNC + ); + absolute_route!( + current_deposit_amount_absolute, + ticketbook_absolute(), + DEPOSIT_AMOUNT + ); + absolute_route!(master_key_absolute, ticketbook_absolute(), MASTER_KEY); + absolute_route!(partial_keys_absolute, ticketbook_absolute(), PARTIAL_KEYS); + absolute_route!(current_epoch_absolute, ticketbook_absolute(), CURRENT_EPOCH); + absolute_route!(shares_absolute, ticketbook_absolute(), SHARES); + + pub mod shares { + use super::*; + + pub const SHARE_BY_ID: &str = "/:share_id"; + pub const SHARE_BY_DEVICE_AND_CREDENTIAL_ID: &str = + "/device/:device_id/credential/:credential_id"; + + absolute_route!(share_by_id_absolute, shares_absolute(), SHARE_BY_ID); + absolute_route!( + share_by_device_and_credential_id_absolute, + shares_absolute(), + SHARE_BY_DEVICE_AND_CREDENTIAL_ID + ); + } + } + } + } +} diff --git a/nym-vpn-api/vpn-api-server/.gitignore b/nym-vpn-api/vpn-api-server/.gitignore new file mode 100644 index 0000000000..1dc2d107db --- /dev/null +++ b/nym-vpn-api/vpn-api-server/.gitignore @@ -0,0 +1 @@ +.db \ No newline at end of file diff --git a/nym-vpn-api/vpn-api-server/Cargo.toml b/nym-vpn-api/vpn-api-server/Cargo.toml new file mode 100644 index 0000000000..d0697b3ced --- /dev/null +++ b/nym-vpn-api/vpn-api-server/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "nym-vpn-api" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait.workspace = true +axum.workspace = true +anyhow.workspace = true +bip39 = { workspace = true, features = ["zeroize"] } +bs58.workspace = true +cfg-if = { workspace = true } +colored.workspace = true +clap = { workspace = true, features = ["derive", "env"] } +dotenv.workspace = true +futures.workspace = true +humantime.workspace = true +rand.workspace = true +reqwest = { workspace = true, features = ["rustls-tls"] } +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] } +strum = { workspace = true, features = ["derive"] } +strum_macros.workspace = true +time.workspace = true +thiserror.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] } +tokio-util = { workspace = true, features = ["rt"] } +tower.workspace = true +tower-http = { workspace = true, features = ["cors"], optional = true } +tracing.workspace = true +url.workspace = true +uuid = { workspace = true, features = ["serde"] } +utoipa = { workspace = true, features = ["axum_extras", "time"] } +utoipa-swagger-ui = { workspace = true, features = ["axum"] } +zeroize.workspace = true + +nym-bin-common = { path = "../../common/bin-common", features = ["basic_tracing"] } +nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } +nym-config = { path = "../../common/config" } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", "serde"] } +nym-credentials = { path = "../../common/credentials" } +nym-credentials-interface = { path = "../../common/credentials-interface" } +nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] } +nym-validator-client = { path = "../../common/client-libs/validator-client" } +nym-network-defaults = { path = "../../common/network-defaults" } + +nym-vpn-api-requests = { path = "../vpn-api-requests", features = ["openapi"] } + +[dev-dependencies] +tempfile = { workspace = true } + +[build-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } + +[features] +default = ["cors"] +cors = ["tower-http"] diff --git a/nym-vpn-api/vpn-api-server/build.rs b/nym-vpn-api/vpn-api-server/build.rs new file mode 100644 index 0000000000..9e5c18ec6c --- /dev/null +++ b/nym-vpn-api/vpn-api-server/build.rs @@ -0,0 +1,22 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#[tokio::main] +async fn main() { + use sqlx::{Connection, SqliteConnection}; + use std::env; + + let out_dir = env::var("OUT_DIR").unwrap(); + let database_path = format!("{out_dir}/nym-vpn-api-example.sqlite"); + + let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc")) + .await + .expect("Failed to create SQLx database connection"); + + sqlx::migrate!("./migrations") + .run(&mut conn) + .await + .expect("Failed to perform SQLx migrations"); + + println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path); +} diff --git a/nym-vpn-api/vpn-api-server/migrations/01_initial.sql b/nym-vpn-api/vpn-api-server/migrations/01_initial.sql new file mode 100644 index 0000000000..c3900d9b36 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/migrations/01_initial.sql @@ -0,0 +1,19 @@ +/* + * Copyright 2023 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + +CREATE TABLE blinded_shares +( + id INTEGER NOT NULL PRIMARY KEY, + status TEXT NOT NULL, + device_id TEXT NOT NULL, + credential_id TEXT NOT NULL, + data TEXT DEFAULT NULL, + error_message TEXT DEFAULT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated TIMESTAMP WITHOUT TIME ZONE NOT NULL +); + +CREATE UNIQUE INDEX blinded_shares_index ON blinded_shares (credential_id, device_id); + diff --git a/nym-vpn-api/vpn-api-server/migrations/02_cherry_picking_chaos.sql b/nym-vpn-api/vpn-api-server/migrations/02_cherry_picking_chaos.sql new file mode 100644 index 0000000000..674b6d328d --- /dev/null +++ b/nym-vpn-api/vpn-api-server/migrations/02_cherry_picking_chaos.sql @@ -0,0 +1,78 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + + +DROP TABLE blinded_shares; +CREATE TABLE blinded_shares +( + id INTEGER NOT NULL PRIMARY KEY, +-- added request_uuid to tie it to deposit and actual share data + request_uuid TEXT NOT NULL REFERENCES ticketbook_deposit(request_uuid), + status TEXT NOT NULL, + device_id TEXT NOT NULL, + credential_id TEXT NOT NULL, +-- replaced the explicit data field in favour of separate table alongside +-- the information on the number of shares available (need min. threshold) + available_shares INTEGER NOT NULL DEFAULT 0, + error_message TEXT DEFAULT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated TIMESTAMP WITHOUT TIME ZONE NOT NULL +); + +CREATE UNIQUE INDEX blinded_shares_index ON blinded_shares (credential_id, device_id); + + +CREATE TABLE ticketbook_deposit ( + deposit_id INTEGER PRIMARY KEY NOT NULL, + deposit_tx_hash TEXT NOT NULL, + requested_on TIMESTAMP WITHOUT TIME ZONE NOT NULL, + request_uuid TEXT UNIQUE NOT NULL, + deposit_amount TEXT NOT NULL, + client_pubkey BLOB NOT NULL, + ed25519_deposit_private_key BLOB NOT NULL +); + +CREATE TABLE partial_blinded_wallet ( + corresponding_deposit INTEGER NOT NULL REFERENCES ticketbook_deposit(deposit_id), + epoch_id INTEGER NOT NULL, + expiration_date DATE NOT NULL, + node_id INTEGER NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + blinded_signature BLOB NOT NULL +); + +CREATE TABLE partial_blinded_wallet_failure ( + corresponding_deposit INTEGER NOT NULL REFERENCES ticketbook_deposit(deposit_id), + epoch_id INTEGER NOT NULL, + expiration_date DATE NOT NULL, + node_id INTEGER NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + failure_message TEXT NOT NULL +); + +-- copied (+rev) from nym-api +CREATE TABLE master_verification_key ( + epoch_id INTEGER PRIMARY KEY NOT NULL, + serialization_revision INTEGER NOT NULL, + serialised_key BLOB NOT NULL +); + +CREATE TABLE global_coin_index_signatures ( + -- we can only have a single entry + epoch_id INTEGER PRIMARY KEY NOT NULL, + serialization_revision INTEGER NOT NULL, + + -- combined signatures for all indices + serialised_signatures BLOB NOT NULL +); + +CREATE TABLE global_expiration_date_signatures ( + expiration_date DATE NOT NULL UNIQUE PRIMARY KEY, + epoch_id INTEGER NOT NULL, + serialization_revision INTEGER NOT NULL, + + -- combined signatures for all tuples issued for given day + serialised_signatures BLOB NOT NULL +); diff --git a/nym-vpn-api/vpn-api-server/src/cli.rs b/nym-vpn-api/vpn-api-server/src/cli.rs new file mode 100644 index 0000000000..480f84ec9c --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/cli.rs @@ -0,0 +1,88 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::default_database_filepath; +use crate::webhook::ZkNymWebHookConfig; +use clap::builder::ArgPredicate; +use clap::Parser; +use nym_bin_common::bin_info; +use std::fs::create_dir_all; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::PathBuf; +use std::sync::OnceLock; +use tracing::info; + +fn pretty_build_info_static() -> &'static str { + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) +} + +// if needed this could be split into subcommands +#[derive(Parser, Debug)] +#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())] +pub struct Cli { + #[clap(flatten)] + pub(crate) webhook: ZkNymWebHookConfig, + + /// Path pointing to an env file that configures the binary. + #[clap(short, long)] + pub(crate) config_env_file: Option, + + /// Specifies the custom port value used for the api server. + /// default: `8080` + #[clap( + long, + env = "VPN_API_PORT", + default_value = "8080", + default_value_if("bind_address", ArgPredicate::IsPresent, None) + )] + pub port: Option, + + /// Specifies the custom bind address value used for the api server. + /// default: `0.0.0.0:8080` + #[clap(long, env = "VPN_API_BIND_ADDRESS")] + pub bind_address: Option, + + /// Specifies the mnemonic authorised for making deposits for "free pass" ticketbooks + #[clap(long, env = "VPN_API_MNEMONIC")] + pub mnemonic: bip39::Mnemonic, + + /// Bearer token for accessing the http endpoints. + #[clap(long, env = "VPN_API_AUTH_TOKEN", alias = "http-bearer-token")] + pub(crate) http_auth_token: String, + + #[clap(long, env = "VPN_API_PERSISTENT_STORAGE_STORAGE")] + pub(crate) persistent_storage_path: Option, +} + +impl Cli { + pub fn bind_address(&self) -> SocketAddr { + // SAFETY: + // if `bind_address` hasn't been specified, `port` will default to "8080", + // so some value will always be available to use + #[allow(clippy::unwrap_used)] + self.bind_address.unwrap_or_else(|| { + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.port.unwrap()) + }) + } + + pub fn persistent_storage_path(&self) -> PathBuf { + self.persistent_storage_path.clone().unwrap_or_else(|| { + // if this blows up, then we shouldn't continue + #[allow(clippy::expect_used)] + let default_path = default_database_filepath(); + if let Some(parent) = default_path.parent() { + // make sure it exists + #[allow(clippy::unwrap_used)] + create_dir_all(parent).unwrap(); + } + + info!( + "setting the storage path path to {}", + default_path.display() + ); + + default_path + }) + } +} diff --git a/nym-vpn-api/vpn-api-server/src/config/mod.rs b/nym-vpn-api/vpn-api-server/src/config/mod.rs new file mode 100644 index 0000000000..128d6f53dc --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/config/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_config::{must_get_home, DEFAULT_DATA_DIR, NYM_DIR}; +use std::path::PathBuf; + +pub const DEFAULT_NYM_VPN_API_DIR: &str = "nym-vpn-api"; + +pub const DEFAULT_DB_FILENAME: &str = "nym-vpn-api.sqlite"; + +pub fn default_data_directory() -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYM_VPN_API_DIR) + .join(DEFAULT_DATA_DIR) +} + +pub fn default_database_filepath() -> PathBuf { + default_data_directory().join(DEFAULT_DB_FILENAME) +} diff --git a/nym-vpn-api/vpn-api-server/src/credentials/mod.rs b/nym-vpn-api/vpn-api-server/src/credentials/mod.rs new file mode 100644 index 0000000000..a6ee988de5 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/credentials/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod ticketbook; diff --git a/nym-vpn-api/vpn-api-server/src/credentials/ticketbook/mod.rs b/nym-vpn-api/vpn-api-server/src/credentials/ticketbook/mod.rs new file mode 100644 index 0000000000..14624dc7c1 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/credentials/ticketbook/mod.rs @@ -0,0 +1,302 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use crate::http::state::ApiState; +use futures::{stream, StreamExt}; +use nym_credentials::IssuanceTicketBook; +use nym_credentials_interface::Base58; +use nym_crypto::asymmetric::ed25519; +use nym_validator_client::ecash::BlindSignRequestBody; +use nym_validator_client::nyxd::contract_traits::EcashSigningClient; +use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData; +use nym_vpn_api_requests::api::v1::ticketbook::models::{ + TicketbookAsyncRequest, TicketbookObtainQueryParams, TicketbookRequest, + TicketbookWalletSharesResponse, WalletShare, +}; +use rand::rngs::OsRng; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; +use time::OffsetDateTime; +use tokio::sync::Mutex; +use tokio::time::timeout; +use tracing::{debug, error, info, instrument}; +use uuid::Uuid; + +// use the same type alias as our contract without importing the whole thing just for this single line +pub type NodeId = u64; + +#[instrument( + skip(state, request_data, request, requested_on), + fields( + expiration_date = %request_data.expiration_date, + ticketbook_type = %request_data.ticketbook_type + ) +)] +pub(crate) async fn try_obtain_wallet_shares( + state: &ApiState, + request: Uuid, + requested_on: OffsetDateTime, + request_data: TicketbookRequest, +) -> Result, VpnApiError> { + let mut rng = OsRng; + + let ed25519_keypair = ed25519::KeyPair::new(&mut rng); + + let epoch = state.current_epoch_id().await?; + let deposit_amount = state.deposit_amount().await?; + let threshold = state.ecash_threshold(epoch).await?; + let expiration_date = request_data.expiration_date; + + // before we commit to making the deposit, ensure we have required signatures cached and stored + let _ = state.master_verification_key(Some(epoch)).await?; + let _ = state.master_coin_index_signatures(Some(epoch)).await?; + let _ = state + .master_expiration_date_signatures(expiration_date) + .await?; + let ecash_api_clients = state.ecash_clients(epoch).await?.clone(); + + let chain_write_permit = state.start_chain_tx().await; + + info!("starting the deposit!"); + // TODO: batch those up + // TODO: batch those up + let deposit_res = chain_write_permit + .make_ticketbook_deposit( + ed25519_keypair.public_key().to_base58_string(), + deposit_amount.clone(), + None, + ) + .await?; + + // explicitly drop it here so other tasks could start using it + drop(chain_write_permit); + + let deposit_id = deposit_res.parse_singleton_u32_contract_data()?; + let tx_hash = deposit_res.transaction_hash; + info!(deposit_id = %deposit_id, tx_hash = %tx_hash, "deposit finished"); + + // store the deposit information so if we fail, we could perhaps still reuse it for another issuance + state + .storage() + .insert_deposit_data( + deposit_id, + tx_hash, + requested_on, + request, + deposit_amount, + &request_data.ecash_pubkey, + &ed25519_keypair, + ) + .await?; + + let plaintext = + IssuanceTicketBook::request_plaintext(&request_data.withdrawal_request, deposit_id); + let signature = ed25519_keypair.private_key().sign(plaintext); + + let credential_request = BlindSignRequestBody::new( + request_data.withdrawal_request.into(), + deposit_id, + signature, + request_data.ecash_pubkey, + request_data.expiration_date, + request_data.ticketbook_type, + ); + + let wallet_shares = Arc::new(Mutex::new(HashMap::new())); + + info!("attempting to contract all nym-apis for the partial wallets..."); + stream::iter(ecash_api_clients) + .for_each_concurrent(None, |client| async { + // move the client into the block + let client = client; + + debug!("contacting {client} for blinded partial wallet"); + let res = timeout( + Duration::from_secs(5), + client.api_client.blind_sign(&credential_request), + ) + .await + .map_err(|_| VpnApiError::EcashApiRequestTimeout { + client_repr: client.to_string(), + }) + .and_then(|res| res.map_err(Into::into)); + + // 1. try to store it + if let Err(err) = state + .storage() + .insert_partial_wallet_share( + deposit_id, + epoch, + expiration_date, + client.node_id, + &res, + ) + .await + { + error!("failed to persist issued partial share: {err}") + } + + // 2. add it to the map + match res { + Ok(share) => { + wallet_shares + .lock() + .await + .insert(client.node_id, share.blinded_signature); + } + Err(err) => { + error!("failed to obtain partial blinded wallet share from {client}: {err}") + } + } + }) + .await; + + // SAFETY: the futures have completed, so we MUST have the only arc reference + #[allow(clippy::unwrap_used)] + let wallet_shares = Arc::into_inner(wallet_shares).unwrap().into_inner(); + let shares = wallet_shares.len(); + + if shares < threshold as usize { + return Err(VpnApiError::InsufficientNumberOfCredentials { + available: shares, + threshold, + }); + } + + Ok(wallet_shares + .into_iter() + .map(|(node_index, share)| WalletShare { + node_index, + bs58_encoded_share: share.to_bs58(), + }) + .collect()) +} + +// same as try_obtain_wallet_shares, but writes failures into the db +async fn try_obtain_wallet_shares_async( + state: &ApiState, + request: Uuid, + requested_on: OffsetDateTime, + request_data: TicketbookRequest, + device_id: &str, + credential_id: &str, +) -> Result, VpnApiError> { + let shares = match try_obtain_wallet_shares(state, request, requested_on, request_data).await { + Ok(shares) => shares, + Err(err) => { + let obtained = match err { + VpnApiError::InsufficientNumberOfCredentials { available, .. } => available, + _ => 0, + }; + + // currently there's no retry mechanisms, but, who knows, that might change + if let Err(err) = state + .storage() + .update_pending_async_blinded_shares_error( + obtained, + device_id, + credential_id, + &err.to_string(), + ) + .await + { + error!("failed to update database with the error information: {err}") + } + return Err(err); + } + }; + + Ok(shares) +} + +async fn try_obtain_blinded_ticketbook_async_inner( + state: &ApiState, + request: Uuid, + requested_on: OffsetDateTime, + request_data: TicketbookAsyncRequest, + params: TicketbookObtainQueryParams, +) -> Result<(), VpnApiError> { + let epoch_id = state.current_epoch_id().await?; + + let device_id = &request_data.device_id; + let credential_id = &request_data.credential_id; + + // 1. try to obtain global data + let ( + master_verification_key, + aggregated_expiration_date_signatures, + aggregated_coin_index_signatures, + ) = state + .global_data( + params.include_master_verification_key, + params.include_coin_index_signatures, + params.include_expiration_date_signatures, + epoch_id, + request_data.inner.expiration_date, + ) + .await?; + + // 2. try to obtain shares (failures are written to the DB) + let shares = try_obtain_wallet_shares_async( + state, + request, + requested_on, + request_data.inner, + device_id, + credential_id, + ) + .await?; + + // 3. update the storage, if possible + // (as long as we can trigger webhook, we should still be good) + if let Err(err) = state + .storage() + .update_pending_async_blinded_shares_issued(shares.len(), device_id, credential_id) + .await + { + error!(uuid = %request, "failed to update db with issued information: {err}") + } + + // 4. build the response + let response = TicketbookWalletSharesResponse { + epoch_id, + shares, + master_verification_key, + aggregated_coin_index_signatures, + aggregated_expiration_date_signatures, + }; + + // 5. call the webhook + state + .zk_nym_web_hook() + .try_trigger(request, &response) + .await; + + Ok(()) +} + +#[instrument(skip_all, fields(credential_id = %request_data.credential_id, device_id = %request_data.device_id))] +#[allow(clippy::too_many_arguments)] +pub(crate) async fn try_obtain_blinded_ticketbook_async( + state: ApiState, + request: Uuid, + requested_on: OffsetDateTime, + request_data: TicketbookAsyncRequest, + params: TicketbookObtainQueryParams, +) { + if let Err(err) = try_obtain_blinded_ticketbook_async_inner( + &state, + request, + requested_on, + request_data, + params, + ) + .await + { + error!(uuid = %request, "failed to resolve the blinded ticketbook issuance: {err}") + } else { + info!(uuid = %request, "managed to resolve the blinded ticketbook issuance") + } +} diff --git a/nym-vpn-api/vpn-api-server/src/error.rs b/nym-vpn-api/vpn-api-server/src/error.rs new file mode 100644 index 0000000000..4ffba86be7 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/error.rs @@ -0,0 +1,126 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_validator_client::coconut::EcashApiError; +use nym_validator_client::nym_api::EpochId; +use nym_validator_client::nyxd::error::NyxdError; +use std::io; +use std::net::SocketAddr; +use thiserror::Error; +use time::OffsetDateTime; + +#[derive(Debug, Error)] +pub enum VpnApiError { + #[error("encountered an internal io error: {source}")] + IoError { + #[from] + source: io::Error, + }, + + #[error("could not derive valid client url with the provided webhook parameters")] + InvalidWebhookUrl, + + #[error("failed to serialise recovery data: {source}")] + SerdeJsonFailure { + #[from] + source: serde_json::Error, + }, + + #[error("the provided expiration date is too late")] + ExpirationDateTooLate, + + #[error("the provided expiration date is too early")] + ExpirationDateTooEarly, + + #[error("failed to bind to {address}: {source}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?")] + SocketBindFailure { + address: SocketAddr, + source: io::Error, + }, + + #[error("the api server failed with the following message: {source}")] + HttpServerFailure { source: io::Error }, + + #[error("the ecash contract address is not set")] + UnavailableEcashContract, + + #[error("the DKG contract address is not set")] + UnavailableDKGContract, + + #[error("the bandwidth contract doesn't have any admin set")] + MissingBandwidthContractAdmin, + + #[error( + "the provided mnemonic does not correspond to the current admin of the bandwidth contract" + )] + MismatchedMnemonic, + + #[error("failed to interact with the nyx chain: {source}")] + NyxdFailure { + #[from] + source: NyxdError, + }, + + #[error("validator client error: {0}")] + ValidatorClientError(#[from] nym_validator_client::ValidatorClientError), + + #[error("failed to perform ecash operation: {source}")] + EcashApiFailure { + #[from] + source: EcashApiError, + }, + + #[error("Compact ecash internal error: {0}")] + CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError), + + #[error("there are no rpc endpoints provided in the environment")] + NoNyxEndpointsAvailable, + + #[error("the threshold value for epoch {epoch_id} is not available")] + UnavailableThreshold { epoch_id: EpochId }, + + #[error( + "we have only {available} api clients available while the minimum threshold is {threshold}" + )] + InsufficientNumberOfSigners { available: usize, threshold: u64 }, + + #[error( + "we have only managed to obtain {available} partial credentials while the minimum threshold is {threshold}" + )] + InsufficientNumberOfCredentials { available: usize, threshold: u64 }, + + #[error("failed to interact with the credentials: {source}")] + CredentialsFailure { + #[from] + source: nym_credentials::Error, + }, + + #[error("the DKG has not yet been initialised in the system")] + UninitialisedDkg, + + #[error("credentials can't yet be issued in the system. approximate expected availability: {availability}")] + CredentialsNotYetIssuable { availability: OffsetDateTime }, + + #[error("reached seemingly impossible ecash failure")] + UnknownEcashFailure, + + #[error("experienced internal database error: {0}")] + InternalDatabaseError(#[from] sqlx::Error), + + #[error("experienced internal storage error: {reason}")] + DatabaseInconsistency { reason: String }, + + #[error("failed to perform startup SQL migration: {0}")] + StartupMigrationFailure(#[from] sqlx::migrate::MigrateError), + + #[error("timed out while attempting to obtain partial wallet from {client_repr}")] + EcashApiRequestTimeout { client_repr: String }, +} + +impl VpnApiError { + pub fn database_inconsistency>(reason: S) -> VpnApiError { + VpnApiError::DatabaseInconsistency { + reason: reason.into(), + } + } +} diff --git a/nym-vpn-api/vpn-api-server/src/helpers.rs b/nym-vpn-api/vpn-api-server/src/helpers.rs new file mode 100644 index 0000000000..12ba427a70 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/helpers.rs @@ -0,0 +1,42 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use time::OffsetDateTime; +use tracing::{debug, info, warn}; + +pub struct LockTimer { + created: OffsetDateTime, + message: String, +} + +impl LockTimer { + pub fn new>(message: S) -> Self { + LockTimer { + message: message.into(), + ..Default::default() + } + } +} + +impl Drop for LockTimer { + fn drop(&mut self) { + let time_taken = OffsetDateTime::now_utc() - self.created; + let time_taken_formatted = humantime::format_duration(time_taken.unsigned_abs()); + if time_taken > time::Duration::SECOND * 10 { + warn!(time_taken = %time_taken_formatted, "{}", self.message) + } else if time_taken > time::Duration::SECOND * 5 { + info!(time_taken = %time_taken_formatted, "{}", self.message) + } else { + debug!(time_taken = %time_taken_formatted, "{}", self.message) + }; + } +} + +impl Default for LockTimer { + fn default() -> Self { + LockTimer { + created: OffsetDateTime::now_utc(), + message: "released the lock".to_string(), + } + } +} diff --git a/nym-vpn-api/vpn-api-server/src/http/helpers.rs b/nym-vpn-api/vpn-api-server/src/http/helpers.rs new file mode 100644 index 0000000000..df35c59221 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/helpers.rs @@ -0,0 +1,13 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use rand::rngs::OsRng; +use rand::RngCore; +use uuid::Uuid; + +pub fn random_uuid() -> Uuid { + let mut bytes = [0u8; 16]; + let mut rng = OsRng; + rng.fill_bytes(&mut bytes); + Uuid::from_bytes(bytes) +} diff --git a/nym-vpn-api/vpn-api-server/src/http/middleware/auth.rs b/nym-vpn-api/vpn-api-server/src/http/middleware/auth.rs new file mode 100644 index 0000000000..ce9c4e0912 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/middleware/auth.rs @@ -0,0 +1,115 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use axum::http::{header, HeaderValue, StatusCode}; +use axum::response::IntoResponse; +use axum::{extract::Request, response::Response}; +use futures::future::BoxFuture; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; +use tracing::{debug, instrument, trace}; +use zeroize::Zeroizing; + +#[derive(Debug, Clone)] +pub struct AuthLayer { + bearer_token: Arc>, +} + +impl AuthLayer { + pub fn new(bearer_token: Arc>) -> Self { + AuthLayer { bearer_token } + } +} + +impl Layer for AuthLayer { + type Service = RequireAuth; + + fn layer(&self, inner: S) -> Self::Service { + RequireAuth::new(inner, self.bearer_token.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct RequireAuth { + inner: S, + bearer_token: Arc>, +} + +impl RequireAuth { + pub fn new(inner: S, bearer_token: Arc>) -> Self { + RequireAuth { + inner, + bearer_token, + } + } + + fn check_auth_header(&self, header: Option<&HeaderValue>) -> Result<(), &'static str> { + let Some(token) = header else { + trace!("missing header"); + return Err("`Authorization` header is missing"); + }; + + let Ok(authorization) = token.to_str() else { + trace!("invalid header"); + return Err("`Authorization` header contains invalid characters"); + }; + + debug!("header value: '{authorization}'"); + + let split = authorization.split_once(' '); + let bearer_token = match split { + // Found proper bearer + Some(("Bearer", contents)) => contents, + // Found empty bearer; + _ if authorization == "Bearer" => "", + // Found nothing + _ => return Err("`Authorization` header must be a bearer token"), + }; + + debug!("parsed token: '{bearer_token}'"); + + if self.bearer_token.is_empty() && bearer_token.is_empty() { + return Ok(()); + } + if bearer_token.is_empty() { + return Err("`Authorization` header must contain non-empty `Bearer` token"); + } + + if self.bearer_token.as_str() != bearer_token { + return Err("`Authorization` header does not contain the correct `Bearer` token"); + } + + Ok(()) + } +} + +impl Service for RequireAuth +where + S: Service + Send + 'static, + S: Send + Sync + 'static, + S::Future: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = BoxFuture<'static, Result>; + + #[inline] + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + #[instrument(skip_all, fields(uri = %req.uri()))] + fn call(&mut self, req: Request) -> Self::Future { + debug!("checking the auth"); + + let auth_header = req.headers().get(header::AUTHORIZATION); + + match self.check_auth_header(auth_header) { + Ok(_authorised) => Box::pin(self.inner.call(req)), + Err(err) => { + Box::pin(async move { Ok((StatusCode::UNAUTHORIZED, err).into_response()) }) + } + } + } +} diff --git a/nym-vpn-api/vpn-api-server/src/http/middleware/logging.rs b/nym-vpn-api/vpn-api-server/src/http/middleware/logging.rs new file mode 100644 index 0000000000..825104ec5b --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/middleware/logging.rs @@ -0,0 +1,64 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use axum::{ + extract::{ConnectInfo, Request}, + http::{ + header::{HOST, USER_AGENT}, + HeaderValue, + }, + middleware::Next, + response::IntoResponse, +}; +use colored::*; +use std::net::SocketAddr; +use tokio::time::Instant; +use tracing::info; + +/// Simple logger for requests +pub async fn logger( + ConnectInfo(addr): ConnectInfo, + req: Request, + next: Next, +) -> impl IntoResponse { + let method = req.method().to_string().green(); + let uri = req.uri().to_string().blue(); + let agent = header_map( + req.headers().get(USER_AGENT), + "Unknown User Agent".to_string(), + ); + + let host = header_map(req.headers().get(HOST), "Unknown Host".to_string()); + + let start = Instant::now(); + let res = next.run(req).await; + let time_taken = start.elapsed(); + let status = res.status(); + let print_status = if status.is_client_error() || status.is_server_error() { + status.to_string().red() + } else if status.is_success() { + status.to_string().green() + } else { + status.to_string().yellow() + }; + + let taken = "time taken".bold(); + + let time_taken = match time_taken.as_millis() { + ms if ms > 500 => format!("{taken}: {}", format!("{ms}ms").red()), + ms if ms > 200 => format!("{taken}: {}", format!("{ms}ms").yellow()), + ms if ms > 50 => format!("{taken}: {}", format!("{ms}ms").bright_yellow()), + ms => format!("{taken}: {ms}ms"), + }; + + let agent_str = "agent".bold(); + info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"); + + res +} + +fn header_map(header: Option<&HeaderValue>, msg: String) -> String { + header + .map(|x| x.to_str().unwrap_or(&msg).to_string()) + .unwrap_or(msg) +} diff --git a/nym-vpn-api/vpn-api-server/src/http/middleware/mod.rs b/nym-vpn-api/vpn-api-server/src/http/middleware/mod.rs new file mode 100644 index 0000000000..b51d9a59b5 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/middleware/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod auth; +pub mod logging; diff --git a/nym-vpn-api/vpn-api-server/src/http/mod.rs b/nym-vpn-api/vpn-api-server/src/http/mod.rs new file mode 100644 index 0000000000..1267b3abfb --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use crate::http::router::build_router; +use crate::http::state::ApiState; +use axum::Router; +use std::net::SocketAddr; +use tokio_util::sync::CancellationToken; +use tracing::info; + +pub mod helpers; +pub mod middleware; +pub mod router; +pub mod state; +pub mod types; + +pub struct HttpServer { + bind_address: SocketAddr, + cancellation: CancellationToken, + router: Router, +} + +impl HttpServer { + pub fn new(bind_address: SocketAddr, state: ApiState, auth_token: String) -> Self { + HttpServer { + bind_address, + cancellation: state.cancellation_token(), + router: build_router(state, auth_token), + } + } + + pub async fn run_forever(self) -> Result<(), VpnApiError> { + let address = self.bind_address; + info!("starting the http server on http://{address}"); + + let listener = tokio::net::TcpListener::bind(address) + .await + .map_err(|source| VpnApiError::SocketBindFailure { address, source })?; + + let cancellation = self.cancellation; + + axum::serve( + listener, + self.router + .into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(async move { cancellation.cancelled().await }) + .await + .map_err(|source| VpnApiError::HttpServerFailure { source }) + } +} diff --git a/nym-vpn-api/vpn-api-server/src/http/router/api/mod.rs b/nym-vpn-api/vpn-api-server/src/http/router/api/mod.rs new file mode 100644 index 0000000000..59b9f9bcea --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/router/api/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::http::state::ApiState; +use axum::Router; +use nym_vpn_api_requests::routes; + +use crate::http::middleware::auth::AuthLayer; +pub(crate) use nym_http_api_common::{Output, OutputParams}; + +pub mod v1; + +pub(super) fn routes(auth_layer: AuthLayer) -> Router { + Router::new().nest(routes::api::V1, v1::routes(auth_layer)) +} diff --git a/nym-vpn-api/vpn-api-server/src/http/router/api/v1/mod.rs b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/mod.rs new file mode 100644 index 0000000000..a6929d3591 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::http::middleware::auth::AuthLayer; +use crate::http::state::ApiState; +use axum::Router; +use nym_vpn_api_requests::routes::api::v1; + +// pub mod bandwidth_voucher; +// pub mod freepass; +pub mod openapi; +pub mod ticketbook; + +pub(super) fn routes(auth_layer: AuthLayer) -> Router { + // from docs: + // ``` + // Note that the middleware is only applied to existing routes. + // So you have to first add your routes (and / or fallback) and then call layer afterwards. + // Additional routes added after layer is called will not have the middleware added. + // ``` + // thus we first add relevant API routes, then the auth layer and finally the swagger routes + Router::new() + .nest(v1::TICKETBOOK, ticketbook::routes().route_layer(auth_layer)) + .merge(openapi::route()) +} diff --git a/nym-vpn-api/vpn-api-server/src/http/router/api/v1/openapi.rs b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/openapi.rs new file mode 100644 index 0000000000..ed215b6c3b --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/openapi.rs @@ -0,0 +1,121 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::http::router::api; +use crate::http::types::RequestError; +use axum::Router; +use nym_vpn_api_requests::api as api_requests; +use nym_vpn_api_requests::routes::api::{v1, v1_absolute}; +use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme}; +use utoipa::{Modify, OpenApi}; +use utoipa_swagger_ui::SwaggerUi; + +/* +#[derive(OpenApi)] +#[openapi( + info(title = "Nym VPN Api"), + paths( + api::v1::freepass::generate_freepass, + api::v1::bandwidth_voucher::obtain_bandwidth_voucher_shares, + api::v1::bandwidth_voucher::obtain_async_bandwidth_voucher_shares, + api::v1::bandwidth_voucher::current_deposit, + api::v1::bandwidth_voucher::prehashed_public_attributes, + api::v1::bandwidth_voucher::partial_verification_keys, + api::v1::bandwidth_voucher::master_verification_key, + api::v1::bandwidth_voucher::current_epoch, + api::v1::bandwidth_voucher::shares::query_for_shares_by_id, + ), + components( + schemas( + api::Output, + api::OutputParams, + api_requests::v1::ErrorResponse, + api_requests::v1::freepass::models::FreepassCredentialResponse, + api_requests::v1::freepass::models::FreepassQueryParams, + api_requests::v1::bandwidth_voucher::models::DepositResponse, + api_requests::v1::bandwidth_voucher::models::AttributesResponse, + api_requests::v1::bandwidth_voucher::models::BandwidthVoucherResponse, + api_requests::v1::bandwidth_voucher::models::BandwidthVoucherAsyncResponse, + api_requests::v1::bandwidth_voucher::models::PartialVerificationKeysResponse, + api_requests::v1::bandwidth_voucher::models::CurrentEpochResponse, + api_requests::v1::bandwidth_voucher::models::CredentialShare, + api_requests::v1::bandwidth_voucher::models::PartialVerificationKey, + api_requests::v1::bandwidth_voucher::models::MasterVerificationKeyResponse, + api_requests::v1::bandwidth_voucher::models::BandwidthVoucherAsyncRequest, + api_requests::v1::bandwidth_voucher::models::BandwidthVoucherRequest, + api_requests::v1::bandwidth_voucher::models::BlindSignRequestJsonSchemaWrapper + ), + responses(RequestError), + ), + modifiers(&SecurityAddon), +)] +pub(crate) struct ApiDoc; + */ + +#[derive(OpenApi)] +#[openapi( + info(title = "Nym VPN Api"), + paths( + api::v1::ticketbook::obtain_ticketbook_shares, + api::v1::ticketbook::obtain_ticketbook_shares_async, + api::v1::ticketbook::current_deposit, + api::v1::ticketbook::partial_verification_keys, + api::v1::ticketbook::master_verification_key, + api::v1::ticketbook::current_epoch, + api::v1::ticketbook::shares::query_for_shares_by_id, + api::v1::ticketbook::shares::query_for_shares_by_device_id_and_credential_id, + ), + components( + schemas( + api::Output, + api::OutputParams, + api_requests::v1::ErrorResponse, + api_requests::v1::ticketbook::models::DepositResponse, + api_requests::v1::ticketbook::models::PartialVerificationKeysResponse, + api_requests::v1::ticketbook::models::CurrentEpochResponse, + api_requests::v1::ticketbook::models::PartialVerificationKey, + api_requests::v1::ticketbook::models::MasterVerificationKeyResponse, + api_requests::v1::ticketbook::models::TicketbookRequest, + api_requests::v1::ticketbook::models::TicketbookAsyncRequest, + api_requests::v1::ticketbook::models::WithdrawalRequestBs58Wrapper, + api_requests::v1::ticketbook::models::PartialVerificationKey, + api_requests::v1::ticketbook::models::AggregatedExpirationDateSignaturesResponse, + api_requests::v1::ticketbook::models::AggregatedCoinIndicesSignaturesResponse, + api_requests::v1::ticketbook::models::WalletShare, + api_requests::v1::ticketbook::models::TicketbookWalletSharesResponse, + api_requests::v1::ticketbook::models::TicketbookWalletSharesAsyncResponse, + api_requests::v1::ticketbook::models::BlindedWalletSharesResponse, + api_requests::v1::ticketbook::models::WebhookBlindedSharesResponse, + api_requests::v1::ticketbook::models::TicketbookObtainQueryParams, + api_requests::v1::ticketbook::models::SharesQueryParams, + api_requests::v1::ticketbook::models::PlaceholderJsonSchemaImpl, + ), + responses(RequestError), + ), + modifiers(&SecurityAddon), +)] +pub(crate) struct ApiDoc; + +struct SecurityAddon; + +impl Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + if let Some(components) = openapi.components.as_mut() { + components.add_security_scheme( + "auth_token", + SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)), + ) + } + } +} + +// if reverse proxy doesn't work, we might have to look into: https://github.com/juhaku/utoipa/issues/842 +pub(crate) fn route() -> Router { + // provide absolute path to the openapi.json + let config = + utoipa_swagger_ui::Config::from(format!("{}/api-docs/openapi.json", v1_absolute())); + SwaggerUi::new(v1::SWAGGER) + .url("/api-docs/openapi.json", ApiDoc::openapi()) + .config(config) + .into() +} diff --git a/nym-vpn-api/vpn-api-server/src/http/router/api/v1/ticketbook/mod.rs b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/ticketbook/mod.rs new file mode 100644 index 0000000000..9c6a1932cf --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/ticketbook/mod.rs @@ -0,0 +1,400 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::credentials::ticketbook::{ + try_obtain_blinded_ticketbook_async, try_obtain_wallet_shares, +}; +use crate::http::helpers::random_uuid; +use crate::http::state::ApiState; +use crate::http::types::RequestError; +use crate::nym_api_helpers::ensure_sane_expiration_date; +use axum::extract::{Query, State}; +use axum::http::StatusCode; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use nym_compact_ecash::Base58; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_vpn_api_requests::api::v1::ticketbook::models::{ + CurrentEpochResponse, DepositResponse, MasterVerificationKeyResponse, PartialVerificationKey, + PartialVerificationKeysResponse, TicketbookAsyncRequest, TicketbookObtainQueryParams, + TicketbookRequest, TicketbookWalletSharesAsyncResponse, TicketbookWalletSharesResponse, +}; +use nym_vpn_api_requests::routes::api::v1::ticketbook; +use time::OffsetDateTime; +use tracing::{error, info, span, warn, Level}; + +pub(crate) mod shares; + +pub type FormattedDepositResponse = FormattedResponse; +pub type FormattedCurrentEpochResponse = FormattedResponse; +pub type FormattedMasterVerificationKeyResponse = FormattedResponse; +pub type FormattedPartialVerificationKeysResponse = + FormattedResponse; +pub type FormattedTicketbookWalletSharesResponse = + FormattedResponse; +pub type FormattedTicketbookWalletSharesAsyncResponse = + FormattedResponse; + +/// Attempt to obtain blinded shares of an ecash ticketbook wallet +#[utoipa::path( + post, + path = "/obtain", + context_path = "/api/v1/ticketbook", + tag = "Ticketbook", + request_body( + content = TicketbookRequest, + description = "cryptographic material required for obtaining ticketbook wallet shares", + content_type = "application/json" + ), + responses( + (status = 200, content( + ("application/json" = TicketbookWalletSharesResponse), + ("application/yaml" = TicketbookWalletSharesResponse), + )), + (status = 400, description = "the provided request hasn't been created against correct attributes"), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 422, description = "provided request was malformed"), + (status = 500, body = ErrorResponse, description = "failed to obtain a ticketbook"), + (status = 503, body = ErrorResponse, description = "ticketbooks can't be issued at this moment: the epoch transition is probably taking place"), + ), + params(TicketbookObtainQueryParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn obtain_ticketbook_shares( + State(state): State, + Query(params): Query, + Json(payload): Json, +) -> Result { + let uuid = random_uuid(); + let requested_on = OffsetDateTime::now_utc(); + + let span = span!(Level::INFO, "obtain ticketboook", uuid = %uuid); + let _entered = span.enter(); + info!(""); + + let output = params.output.unwrap_or_default(); + + state.ensure_not_in_epoch_transition(Some(uuid)).await?; + let epoch_id = state + .current_epoch_id() + .await + .map_err(|err| RequestError::new_server_error(err, uuid))?; + + if let Err(err) = ensure_sane_expiration_date(payload.expiration_date) { + warn!("failure due to invalid expiration date"); + return Err(RequestError::new_with_uuid( + err.to_string(), + uuid, + StatusCode::BAD_REQUEST, + )); + } + + // if additional data was requested, grab them first in case there are any cache/network issues + let ( + master_verification_key, + aggregated_expiration_date_signatures, + aggregated_coin_index_signatures, + ) = state + .response_global_data( + params.include_master_verification_key, + params.include_expiration_date_signatures, + params.include_coin_index_signatures, + epoch_id, + payload.expiration_date, + uuid, + ) + .await?; + + let shares = try_obtain_wallet_shares(&state, uuid, requested_on, payload) + .await + .inspect_err(|err| warn!("request failure: {err}")) + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + info!("request was successful!"); + Ok(output.to_response(TicketbookWalletSharesResponse { + epoch_id, + shares, + master_verification_key, + aggregated_coin_index_signatures, + aggregated_expiration_date_signatures, + })) +} + +/// Attempt to obtain blinded shares of an ecash ticketbook wallet asynchronously +#[utoipa::path( + post, + path = "/obtain-async", + context_path = "/api/v1/ticketbook", + tag = "Ticketbook", + request_body( + content = TicketbookAsyncRequest, + description = "cryptographic material required for obtaining ticketbook wallet shares", + content_type = "application/json" + ), + responses( + (status = 200, content( + ("application/json" = TicketbookWalletSharesAsyncResponse), + ("application/yaml" = TicketbookWalletSharesAsyncResponse), + )), + (status = 400, description = "the provided request hasn't been created against correct attributes"), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 409, description = "shares were already requested"), + (status = 422, description = "provided request was malformed"), + (status = 500, body = ErrorResponse, description = "failed to obtain a ticketbook"), + (status = 503, body = ErrorResponse, description = "ticketbooks can't be issued at this moment: the epoch transition is probably taking place"), + ), + params(TicketbookObtainQueryParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn obtain_ticketbook_shares_async( + State(state): State, + Query(params): Query, + Json(payload): Json, +) -> Result { + let uuid = random_uuid(); + let requested_on = OffsetDateTime::now_utc(); + + let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid); + let _entered = span.enter(); + info!(""); + + let output = params.output.unwrap_or_default(); + + // 1. perform basic validation + state.ensure_not_in_epoch_transition(Some(uuid)).await?; + + if let Err(err) = ensure_sane_expiration_date(payload.inner.expiration_date) { + warn!("failure due to invalid expiration date"); + return Err(RequestError::new_with_uuid( + err.to_string(), + uuid, + StatusCode::BAD_REQUEST, + )); + } + + // 2. store the request to retrieve the id + let pending = match state + .storage() + .insert_new_pending_async_shares_request(uuid, &payload.device_id, &payload.credential_id) + .await + { + Err(err) => { + error!("failed to insert new pending async shares: {err}"); + return Err(RequestError::new_with_uuid( + err.to_string(), + uuid, + StatusCode::CONFLICT, + )); + } + Ok(pending) => pending, + }; + + // 3. try to spawn a new task attempting to resolve the request + if state + .try_spawn(try_obtain_blinded_ticketbook_async( + state.clone(), + uuid, + requested_on, + payload, + params, + )) + .is_none() + { + // we're going through the shutdown + return Err(RequestError::new_with_uuid( + "server shutdown in progress", + uuid, + StatusCode::INTERNAL_SERVER_ERROR, + )); + } + + // 4. in the meantime, return the id to the user + Ok(output.to_response(TicketbookWalletSharesAsyncResponse { + id: pending.id, + uuid, + })) +} + +/// Obtain the current value of the bandwidth voucher deposit +#[utoipa::path( + get, + path = "/deposit-amount", + context_path = "/api/v1/ticketbook", + tag = "Ticketbook", + responses( + (status = 200, content( + ("application/json" = DepositResponse), + ("application/yaml" = DepositResponse), + )), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 500, body = ErrorResponse, description = "failed to obtain current deposit information"), + ), + params(OutputParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn current_deposit( + Query(output): Query, + State(state): State, +) -> Result { + let output = output.output.unwrap_or_default(); + let current_deposit = state + .deposit_amount() + .await + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + Ok(output.to_response(DepositResponse { + current_deposit_amount: current_deposit.amount, + current_deposit_denom: current_deposit.denom, + })) +} + +/// Obtain partial verification keys of all signers for the current epoch. +#[utoipa::path( + get, + path = "/partial-verification-keys", + context_path = "/api/v1/ticketbook", + tag = "Ticketbook", + responses( + (status = 200, content( + ("application/json" = PartialVerificationKeysResponse), + ("application/yaml" = PartialVerificationKeysResponse), + )), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 500, body = ErrorResponse, description = "failed to obtain current epoch information"), + (status = 503, body = ErrorResponse, description = "credentials can't be issued at this moment: the epoch transition is probably taking place"), + ), + params(OutputParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn partial_verification_keys( + Query(output): Query, + State(state): State, +) -> Result { + let output = output.output.unwrap_or_default(); + + state.ensure_not_in_epoch_transition(None).await?; + + let epoch_id = state + .current_epoch_id() + .await + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + let signers = state + .ecash_clients(epoch_id) + .await + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + Ok(output.to_response(PartialVerificationKeysResponse { + epoch_id, + keys: signers + .iter() + .map(|signer| PartialVerificationKey { + node_index: signer.node_id, + bs58_encoded_key: signer.verification_key.to_bs58(), + }) + .collect(), + })) +} + +/// Obtain the master verification key for the current epoch. +#[utoipa::path( + get, + path = "/master-verification-key", + context_path = "/api/v1/ticketbook", + tag = "Ticketbook", + responses( + (status = 200, content( + ("application/json" = MasterVerificationKeyResponse), + ("application/yaml" = MasterVerificationKeyResponse), + )), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 500, body = ErrorResponse, description = "failed to obtain current epoch information"), + (status = 503, body = ErrorResponse, description = "credentials can't be issued at this moment: the epoch transition is probably taking place"), + ), + params(OutputParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn master_verification_key( + Query(output): Query, + State(state): State, +) -> Result { + let output = output.output.unwrap_or_default(); + + state.ensure_not_in_epoch_transition(None).await?; + + let epoch_id = state + .current_epoch_id() + .await + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + let key = state + .master_verification_key(Some(epoch_id)) + .await + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + Ok(output.to_response(MasterVerificationKeyResponse { + epoch_id, + bs58_encoded_key: key.to_bs58(), + })) +} + +/// Obtain the id of the current epoch. +/// This is exposed to allow clients to cache verification keys. +#[utoipa::path( + get, + path = "/current-epoch", + context_path = "/api/v1/ticketbook", + tag = "Ticketbook", + responses( + (status = 200, content( + ("application/json" = CurrentEpochResponse), + ("application/yaml" = CurrentEpochResponse), + )), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 500, body = ErrorResponse, description = "failed to obtain current epoch information"), + (status = 503, body = ErrorResponse, description = "credentials can't be issued at this moment: the epoch transition is probably taking place"), + ), + params(OutputParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn current_epoch( + Query(output): Query, + State(state): State, +) -> Result { + let output = output.output.unwrap_or_default(); + + state.ensure_not_in_epoch_transition(None).await?; + + let epoch_id = state + .current_epoch_id() + .await + .map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?; + + Ok(output.to_response(CurrentEpochResponse { epoch_id })) +} + +pub(crate) fn routes() -> Router { + Router::new() + .route(ticketbook::DEPOSIT_AMOUNT, get(current_deposit)) + .route(ticketbook::MASTER_KEY, get(master_verification_key)) + .route(ticketbook::PARTIAL_KEYS, get(partial_verification_keys)) + .route(ticketbook::CURRENT_EPOCH, get(current_epoch)) + .route(ticketbook::OBTAIN, post(obtain_ticketbook_shares)) + .route( + ticketbook::OBTAIN_ASYNC, + post(obtain_ticketbook_shares_async), + ) + .nest(ticketbook::SHARES, shares::routes()) +} diff --git a/nym-vpn-api/vpn-api-server/src/http/router/api/v1/ticketbook/shares.rs b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/ticketbook/shares.rs new file mode 100644 index 0000000000..2f23dbc3aa --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/router/api/v1/ticketbook/shares.rs @@ -0,0 +1,206 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use crate::http::helpers::random_uuid; +use crate::http::router::api::v1::ticketbook::FormattedTicketbookWalletSharesResponse; +use crate::http::state::ApiState; +use crate::http::types::RequestError; +use crate::storage::models::MinimalWalletShare; +use axum::extract::{Path, Query, State}; +use axum::http::StatusCode; +use axum::routing::get; +use axum::Router; +use nym_http_api_common::OutputParams; +use nym_validator_client::nym_api::EpochId; +use nym_vpn_api_requests::api::v1::ticketbook::models::{ + SharesQueryParams, TicketbookWalletSharesResponse, +}; +use nym_vpn_api_requests::routes::api::v1::ticketbook::shares; +use tracing::{debug, span, warn, Level}; +use uuid::Uuid; + +async fn shares_to_response( + state: ApiState, + uuid: Uuid, + shares: Vec, + params: SharesQueryParams, +) -> Result { + // in all calls we ensured the shares are non-empty + #[allow(clippy::unwrap_used)] + let first = shares.first().unwrap(); + let expiration_date = first.expiration_date; + let epoch_id = first.epoch_id as EpochId; + + let threshold = state.response_ecash_threshold(uuid, epoch_id).await?; + if shares.len() < threshold as usize { + return Err(RequestError::new_server_error( + VpnApiError::InsufficientNumberOfCredentials { + available: shares.len(), + threshold, + }, + uuid, + )); + } + + // grab any requested additional data + let ( + master_verification_key, + aggregated_expiration_date_signatures, + aggregated_coin_index_signatures, + ) = state + .response_global_data( + params.include_master_verification_key, + params.include_expiration_date_signatures, + params.include_coin_index_signatures, + epoch_id, + expiration_date, + uuid, + ) + .await?; + + // finally produce a response + Ok(params + .output + .unwrap_or_default() + .to_response(TicketbookWalletSharesResponse { + epoch_id, + shares: shares.into_iter().map(Into::into).collect(), + master_verification_key, + aggregated_coin_index_signatures, + aggregated_expiration_date_signatures, + })) +} + +/// Query by id for blinded shares of a bandwidth voucher +#[utoipa::path( + get, + path = "/{share_id}", + context_path = "/api/v1/ticketbook/shares", + tag = "Ticketbook Wallet Shares", + responses( + (status = 200, content( + ("application/json" = TicketbookWalletSharesResponse), + ("application/yaml" = TicketbookWalletSharesResponse), + )), + (status = 404, description = "share_id not found"), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 500, body = ErrorResponse, description = "failed to query for bandwidth blinded shares"), + ), + params(OutputParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn query_for_shares_by_id( + State(state): State, + Query(params): Query, + Path(share_id): Path, +) -> Result { + let uuid = random_uuid(); + + let span = span!(Level::INFO, "query shares by id", uuid = %uuid, share_id = %share_id); + let _entered = span.enter(); + debug!(""); + + // TODO: edge case: this will **NOT** work if shares got created in epoch X, + // but this query happened in epoch X+1 + let shares = match state + .storage() + .load_wallet_shares_by_shares_id(share_id) + .await + { + Ok(shares) => { + if shares.is_empty() { + debug!("not found"); + return Err(RequestError::new_with_uuid( + format!("not found - share_id = {share_id}"), + uuid, + StatusCode::NOT_FOUND, + )); + } + shares + } + Err(err) => { + warn!("db failure: {err}"); + return Err(RequestError::new_with_uuid( + format!("oh no, something went wrong {err}"), + uuid, + StatusCode::INTERNAL_SERVER_ERROR, + )); + } + }; + + shares_to_response(state, uuid, shares, params).await +} + +/// Query by id for blinded wallet shares of a ticketbook +#[utoipa::path( + get, + path = "/device/{device_id}/credential/{credential_id}", + context_path = "/api/v1/ticketbook/shares", + tag = "Ticketbook Wallet Shares", + responses( + (status = 200, content( + ("application/json" = TicketbookWalletSharesResponse), + ("application/yaml" = TicketbookWalletSharesResponse), + )), + (status = 404, description = "share_id not found"), + (status = 401, description = "authentication token is missing or is invalid"), + (status = 500, body = ErrorResponse, description = "failed to query for bandwidth blinded shares"), + ), + params(OutputParams), + security( + ("auth_token" = []) + ) +)] +pub(crate) async fn query_for_shares_by_device_id_and_credential_id( + State(state): State, + Query(params): Query, + Path((device_id, credential_id)): Path<(String, String)>, +) -> Result { + let uuid = random_uuid(); + + let span = span!(Level::INFO, "query shares by device and credential ids", uuid = %uuid, device_id = %device_id, credential_id = %credential_id); + let _entered = span.enter(); + debug!(""); + + // TODO: edge case: this will **NOT** work if shares got created in epoch X, + // but this query happened in epoch X+1 + let shares = match state + .storage() + .load_wallet_shares_by_device_and_credential_id(&device_id, &credential_id) + .await + { + Ok(shares) => { + if shares.is_empty() { + debug!("not found"); + return Err(RequestError::new_with_uuid( + format!("not found - device_id = {device_id}, credential_id = {credential_id}"), + uuid, + StatusCode::NOT_FOUND, + )); + } + shares + } + Err(err) => { + warn!("db failure: {err}"); + return Err(RequestError::new_with_uuid( + format!("oh no, something went wrong {err}"), + uuid, + StatusCode::INTERNAL_SERVER_ERROR, + )); + } + }; + + shares_to_response(state, uuid, shares, params).await +} + +pub(crate) fn routes() -> Router { + Router::new() + .route(shares::SHARE_BY_ID, get(query_for_shares_by_id)) + .route( + shares::SHARE_BY_DEVICE_AND_CREDENTIAL_ID, + get(query_for_shares_by_device_id_and_credential_id), + ) +} diff --git a/nym-vpn-api/vpn-api-server/src/http/router/mod.rs b/nym-vpn-api/vpn-api-server/src/http/router/mod.rs new file mode 100644 index 0000000000..6e2ac305c6 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/router/mod.rs @@ -0,0 +1,44 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::http::middleware::auth::AuthLayer; +use crate::http::middleware::logging; +use crate::http::state::ApiState; +use axum::response::Redirect; +use axum::routing::{get, MethodRouter}; +use axum::Router; +use nym_vpn_api_requests::routes; +use std::sync::Arc; +use zeroize::Zeroizing; + +pub mod api; + +fn swagger_redirect() -> MethodRouter { + // redirects with 303 status code + get(|| async { Redirect::to("/api/v1/swagger/") }) +} + +pub fn build_router(state: ApiState, auth_token: String) -> Router { + // let auth_layer = from_extractor::(); + let auth_middleware = AuthLayer::new(Arc::new(Zeroizing::new(auth_token))); + + let router = Router::new() + // just redirect root and common typos for swagger for the current api version page (v1) + .route("/", swagger_redirect()) + .route("/swagger", swagger_redirect()) + .route("/swagger/", swagger_redirect()) + .route("/swagger/index.html", swagger_redirect()) + .nest(routes::API, api::routes(auth_middleware)) + // we don't have to be using middleware, but we already had that code + // we might want something like: https://github.com/tokio-rs/axum/blob/main/examples/tracing-aka-logging/src/main.rs#L44 instead + .layer(axum::middleware::from_fn(logging::logger)) + .with_state(state); + + cfg_if::cfg_if! { + if #[cfg(feature = "cors")] { + router.layer(tower_http::cors::CorsLayer::very_permissive()) + } else { + router + } + } +} diff --git a/nym-vpn-api/vpn-api-server/src/http/state/mod.rs b/nym-vpn-api/vpn-api-server/src/http/state/mod.rs new file mode 100644 index 0000000000..8ffd1bcc94 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/state/mod.rs @@ -0,0 +1,683 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use crate::helpers::LockTimer; +use crate::http::types::RequestError; +use crate::nym_api_helpers::{ + ensure_sane_expiration_date, query_all_threshold_apis, CachedEpoch, CachedImmutableEpochItem, + CachedImmutableItems, +}; +use crate::storage::VpnApiStorage; +use crate::webhook::ZkNymWebHookConfig; +use axum::http::StatusCode; +use bip39::Mnemonic; +use nym_compact_ecash::scheme::coin_indices_signatures::{ + aggregate_annotated_indices_signatures, CoinIndexSignatureShare, +}; +use nym_compact_ecash::scheme::expiration_date_signatures::{ + aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare, +}; +use nym_compact_ecash::Base58; +use nym_credentials::ecash::utils::{ecash_today, EcashTime}; +use nym_credentials::{ + AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey, +}; +use nym_credentials_interface::VerificationKeyAuth; +use nym_validator_client::coconut::EcashApiError; +use nym_validator_client::nym_api::EpochId; +use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch; +use nym_validator_client::nyxd::contract_traits::{ + DkgQueryClient, EcashQueryClient, NymContractsProvider, PagedDkgQueryClient, +}; +use nym_validator_client::nyxd::{Coin, NyxdClient}; +use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, EcashApiClient}; +use nym_vpn_api_requests::api::v1::ticketbook::models::{ + AggregatedCoinIndicesSignaturesResponse, AggregatedExpirationDateSignaturesResponse, + MasterVerificationKeyResponse, +}; +use std::future::Future; +use std::ops::Deref; +use std::sync::Arc; +use time::{Date, OffsetDateTime}; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use tokio::task::JoinHandle; +use tokio_util::sync::CancellationToken; +use tokio_util::task::TaskTracker; +use tracing::{debug, info, warn}; +use uuid::Uuid; + +// currently we need to hold our keypair so that we could request a freepass credential +#[derive(Clone)] +pub struct ApiState { + inner: Arc, +} + +// a lot of functionalities, mostly to do with caching and storage is just copy-pasted from nym-api, +// since we have to do more or less the same work +impl ApiState { + pub async fn new( + storage: VpnApiStorage, + zk_nym_web_hook_config: ZkNymWebHookConfig, + mnemonic: Mnemonic, + ) -> Result { + let network_details = nym_network_defaults::NymNetworkDetails::new_from_env(); + let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?; + + let nyxd_url = network_details + .endpoints + .first() + .ok_or_else(|| VpnApiError::NoNyxEndpointsAvailable)? + .nyxd_url + .as_str(); + + let client = NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic)?; + + if client.ecash_contract_address().is_none() { + return Err(VpnApiError::UnavailableEcashContract); + } + + if client.dkg_contract_address().is_none() { + return Err(VpnApiError::UnavailableDKGContract); + } + + let state = ApiState { + inner: Arc::new(ApiStateInner { + storage, + client: RwLock::new(client), + ecash_state: EcashState::default(), + zk_nym_web_hook_config, + task_tracker: TaskTracker::new(), + cancellation_token: CancellationToken::new(), + }), + }; + + // since this is startup, + // might as well do all the needed network queries to establish needed global signatures + // if we don't already have them + state.build_initial_cache().await?; + + Ok(state) + } + + async fn build_initial_cache(&self) -> Result<(), VpnApiError> { + let today = ecash_today().date(); + + let epoch_id = self.current_epoch_id().await?; + let _ = self.deposit_amount().await?; + let _ = self.master_verification_key(Some(epoch_id)).await?; + let _ = self.ecash_threshold(epoch_id).await?; + let _ = self.ecash_clients(epoch_id).await?; + let _ = self.master_coin_index_signatures(Some(epoch_id)).await?; + let _ = self.master_expiration_date_signatures(today).await?; + + Ok(()) + } + + pub(crate) fn try_spawn(&self, task: F) -> Option> + where + F: Future + Send + 'static, + F::Output: Send + 'static, + { + // don't spawn new task if we've received cancellation token + if self.inner.cancellation_token.is_cancelled() { + None + } else { + self.inner.task_tracker.reopen(); + // TODO: later use a task queue since most requests will be blocked waiting on chain permit anyway + let join_handle = self.inner.task_tracker.spawn(task); + self.inner.task_tracker.close(); + Some(join_handle) + } + } + + pub(crate) async fn cancel_and_wait(&self) { + self.inner.cancellation_token.cancel(); + self.inner.task_tracker.wait().await + } + + pub(crate) fn cancellation_token(&self) -> CancellationToken { + self.inner.cancellation_token.clone() + } + + pub(crate) fn zk_nym_web_hook(&self) -> &ZkNymWebHookConfig { + &self.inner.zk_nym_web_hook_config + } + + async fn ensure_credentials_issuable(&self) -> Result<(), VpnApiError> { + let epoch = self.current_epoch().await?; + + if epoch.state.is_final() { + Ok(()) + } else if let Some(final_timestamp) = epoch.final_timestamp_secs() { + // SAFETY: the timestamp values in our DKG contract should be valid timestamps, + // otherwise it means the chain is seriously misbehaving + #[allow(clippy::unwrap_used)] + let finish_dt = OffsetDateTime::from_unix_timestamp(final_timestamp as i64).unwrap(); + + Err(VpnApiError::CredentialsNotYetIssuable { + availability: finish_dt, + }) + } else if epoch.state.is_waiting_initialisation() { + return Err(VpnApiError::UninitialisedDkg); + } else { + Err(VpnApiError::UnknownEcashFailure) + } + } + + pub(crate) fn storage(&self) -> &VpnApiStorage { + &self.inner.storage + } + + pub async fn deposit_amount(&self) -> Result { + let read_guard = self.inner.ecash_state.required_deposit_cache.read().await; + if read_guard.is_valid() { + return Ok(read_guard.required_amount.clone()); + } + + // update cache + drop(read_guard); + let mut write_guard = self.inner.ecash_state.required_deposit_cache.write().await; + let deposit_amount = self + .query_chain() + .await + .get_required_deposit_amount() + .await?; + + write_guard.update(deposit_amount.clone().into()); + + Ok(deposit_amount.into()) + } + + async fn current_epoch(&self) -> Result { + let read_guard = self.inner.ecash_state.cached_epoch.read().await; + if read_guard.is_valid() { + return Ok(read_guard.current_epoch); + } + + // update cache + drop(read_guard); + let mut write_guard = self.inner.ecash_state.cached_epoch.write().await; + let epoch = self.query_chain().await.get_current_epoch().await?; + + write_guard.update(epoch); + Ok(epoch) + } + + pub async fn current_epoch_id(&self) -> Result { + let read_guard = self.inner.ecash_state.cached_epoch.read().await; + if read_guard.is_valid() { + return Ok(read_guard.current_epoch.epoch_id); + } + + // update cache + drop(read_guard); + let mut write_guard = self.inner.ecash_state.cached_epoch.write().await; + let epoch = self.query_chain().await.get_current_epoch().await?; + + write_guard.update(epoch); + Ok(epoch.epoch_id) + } + + pub(crate) async fn query_chain(&self) -> RwLockReadGuard { + let _acquire_timer = LockTimer::new("acquire chain query permit"); + self.inner.client.read().await + } + + pub(crate) async fn start_chain_tx(&self) -> ChainWritePermit { + let _acquire_timer = LockTimer::new("acquire exclusive chain write permit"); + + ChainWritePermit { + lock_timer: LockTimer::new("exclusive chain access permit"), + inner: self.inner.client.write().await, + } + } + + pub(crate) async fn global_data( + &self, + include_master_verification_key: bool, + include_expiration_date_signatures: bool, + include_coin_index_signatures: bool, + epoch_id: EpochId, + expiration_date: Date, + ) -> Result< + ( + Option, + Option, + Option, + ), + VpnApiError, + > { + let master_verification_key = if include_master_verification_key { + debug!("including master verification key in the response"); + Some( + self.master_verification_key(Some(epoch_id)) + .await + .map(|key| MasterVerificationKeyResponse { + epoch_id, + bs58_encoded_key: key.to_bs58(), + }) + .inspect_err(|err| warn!("request failure: {err}"))?, + ) + } else { + None + }; + + let aggregated_expiration_date_signatures = if include_expiration_date_signatures { + debug!("including expiration date signatures in the response"); + Some( + self.master_expiration_date_signatures(expiration_date) + .await + .map(|signatures| AggregatedExpirationDateSignaturesResponse { + signatures: signatures.clone(), + }) + .inspect_err(|err| warn!("request failure: {err}"))?, + ) + } else { + None + }; + + let aggregated_coin_index_signatures = if include_coin_index_signatures { + debug!("including coin index signatures in the response"); + Some( + self.master_coin_index_signatures(Some(epoch_id)) + .await + .map(|signatures| AggregatedCoinIndicesSignaturesResponse { + signatures: signatures.clone(), + }) + .inspect_err(|err| warn!("request failure: {err}"))?, + ) + } else { + None + }; + + Ok(( + master_verification_key, + aggregated_expiration_date_signatures, + aggregated_coin_index_signatures, + )) + } + + pub(crate) async fn response_global_data( + &self, + include_master_verification_key: bool, + include_expiration_date_signatures: bool, + include_coin_index_signatures: bool, + epoch_id: EpochId, + expiration_date: Date, + uuid: Uuid, + ) -> Result< + ( + Option, + Option, + Option, + ), + RequestError, + > { + self.global_data( + include_master_verification_key, + include_expiration_date_signatures, + include_coin_index_signatures, + epoch_id, + expiration_date, + ) + .await + .map_err(|err| RequestError::new_server_error(err, uuid)) + } + + pub async fn ensure_not_in_epoch_transition( + &self, + uuid: Option, + ) -> Result<(), RequestError> { + if let Err(err) = self.ensure_credentials_issuable().await { + return if let Some(uuid) = uuid { + Err(RequestError::new_with_uuid( + err.to_string(), + uuid, + StatusCode::SERVICE_UNAVAILABLE, + )) + } else { + Err(RequestError::new( + err.to_string(), + StatusCode::SERVICE_UNAVAILABLE, + )) + }; + } + Ok(()) + } + + pub(crate) async fn ecash_clients( + &self, + epoch_id: EpochId, + ) -> Result>, VpnApiError> { + self.inner + .ecash_state + .epoch_clients + .get_or_init(epoch_id, || async { + Ok(self + .query_chain() + .await + .get_all_verification_key_shares(epoch_id) + .await? + .into_iter() + .map(TryInto::try_into) + .collect::, EcashApiError>>()?) + }) + .await + } + + pub(crate) async fn ecash_threshold(&self, epoch_id: EpochId) -> Result { + self.inner + .ecash_state + .threshold_values + .get_or_init(epoch_id, || async { + if let Some(threshold) = self + .query_chain() + .await + .get_epoch_threshold(epoch_id) + .await? + { + Ok(threshold) + } else { + Err(VpnApiError::UnavailableThreshold { epoch_id }) + } + }) + .await + .map(|t| *t) + } + + pub(crate) async fn response_ecash_threshold( + &self, + uuid: Uuid, + epoch_id: EpochId, + ) -> Result { + self.ecash_threshold(epoch_id) + .await + .map_err(|err| RequestError::new_server_error(err, uuid)) + } + + pub(crate) async fn master_verification_key( + &self, + epoch_id: Option, + ) -> Result, VpnApiError> { + let epoch_id = match epoch_id { + Some(id) => id, + None => self.current_epoch_id().await?, + }; + + self.inner + .ecash_state + .master_verification_key + .get_or_init(epoch_id, || async { + // 1. check the storage + if let Some(stored) = self + .inner + .storage + .get_master_verification_key(epoch_id) + .await? + { + return Ok(stored.key); + } + + info!("attempting to establish master verification key for epoch {epoch_id}..."); + + // 2. perform actual aggregation + let all_apis = self.ecash_clients(epoch_id).await?; + let threshold = self.ecash_threshold(epoch_id).await?; + + if all_apis.len() < threshold as usize { + return Err(VpnApiError::InsufficientNumberOfSigners { + threshold, + available: all_apis.len(), + }); + } + + let master_key = nym_credentials::aggregate_verification_keys(&all_apis)?; + + let epoch = EpochVerificationKey { + epoch_id, + key: master_key, + }; + + // 3. save the key in the storage for when we reboot + self.inner + .storage + .insert_master_verification_key(&epoch) + .await?; + + Ok(epoch.key) + }) + .await + } + + pub(crate) async fn master_coin_index_signatures( + &self, + epoch_id: Option, + ) -> Result, VpnApiError> { + let epoch_id = match epoch_id { + Some(id) => id, + None => self.current_epoch_id().await?, + }; + + self.inner + .ecash_state + .coin_index_signatures + .get_or_init(epoch_id, || async { + // 1. check the storage + if let Some(master_sigs) = self + .inner + .storage + .get_master_coin_index_signatures(epoch_id) + .await? + { + return Ok(master_sigs); + } + + info!( + "attempting to establish master coin index signatures for epoch {epoch_id}..." + ); + + // 2. go around APIs and attempt to aggregate the data + let master_vk = self.master_verification_key(Some(epoch_id)).await?; + let all_apis = self.ecash_clients(epoch_id).await?; + let threshold = self.ecash_threshold(epoch_id).await?; + + let get_partial_signatures = |api: EcashApiClient| async { + // move the api into the closure + let api = api; + let node_index = api.node_id; + let partial_vk = api.verification_key; + + let partial = api + .api_client + .partial_coin_indices_signatures(Some(epoch_id)) + .await? + .signatures; + Ok(CoinIndexSignatureShare { + index: node_index, + key: partial_vk, + signatures: partial, + }) + }; + + let shares = + query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures) + .await?; + + let aggregated = aggregate_annotated_indices_signatures( + nym_credentials_interface::ecash_parameters(), + &master_vk, + &shares, + )?; + + let sigs = AggregatedCoinIndicesSignatures { + epoch_id, + signatures: aggregated, + }; + + // 3. save the signatures in the storage for when we reboot + self.inner + .storage + .insert_master_coin_index_signatures(&sigs) + .await?; + + Ok(sigs) + }) + .await + } + + pub(crate) async fn master_expiration_date_signatures( + &self, + expiration_date: Date, + ) -> Result, VpnApiError> { + self.inner + .ecash_state + .expiration_date_signatures + .get_or_init(expiration_date, || async { + // 1. sanity check to see if the expiration_date is not nonsense + ensure_sane_expiration_date(expiration_date)?; + + // 2. check the storage + if let Some(master_sigs) = self + .inner + .storage + .get_master_expiration_date_signatures(expiration_date) + .await? + { + return Ok(master_sigs); + } + + + info!( + "attempting to establish master expiration date signatures for {expiration_date}..." + ); + + // 3. go around APIs and attempt to aggregate the data + let epoch_id = self.current_epoch_id().await?; + let master_vk = self.master_verification_key(Some(epoch_id)).await?; + let all_apis = self.ecash_clients(epoch_id).await?; + let threshold = self.ecash_threshold(epoch_id).await?; + + let get_partial_signatures = |api: EcashApiClient| async { + // move the api into the closure + let api = api; + let node_index = api.node_id; + let partial_vk = api.verification_key; + + let partial = api + .api_client + .partial_expiration_date_signatures(Some(expiration_date)) + .await? + .signatures; + Ok(ExpirationDateSignatureShare { + index: node_index, + key: partial_vk, + signatures: partial, + }) + }; + + let shares = + query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures) + .await?; + + let aggregated = aggregate_annotated_expiration_signatures( + &master_vk, + expiration_date.ecash_unix_timestamp(), + &shares, + )?; + + let sigs = AggregatedExpirationDateSignatures { + epoch_id, + expiration_date, + signatures: aggregated, + }; + + // 4. save the signatures in the storage for when we reboot + self.inner + .storage + .insert_master_expiration_date_signatures(&sigs) + .await?; + + Ok(sigs) + }) + .await + } +} + +struct ApiStateInner { + storage: VpnApiStorage, + + client: RwLock, + + zk_nym_web_hook_config: ZkNymWebHookConfig, + + ecash_state: EcashState, + + task_tracker: TaskTracker, + + cancellation_token: CancellationToken, +} + +pub(crate) struct CachedDeposit { + valid_until: OffsetDateTime, + required_amount: Coin, +} + +impl CachedDeposit { + const MAX_VALIDITY: time::Duration = time::Duration::MINUTE; + + fn is_valid(&self) -> bool { + self.valid_until > OffsetDateTime::now_utc() + } + + fn update(&mut self, required_amount: Coin) { + self.valid_until = OffsetDateTime::now_utc() + Self::MAX_VALIDITY; + self.required_amount = required_amount; + } +} + +impl Default for CachedDeposit { + fn default() -> Self { + CachedDeposit { + valid_until: OffsetDateTime::UNIX_EPOCH, + required_amount: Coin { + amount: u128::MAX, + denom: "unym".to_string(), + }, + } + } +} + +#[derive(Default)] +pub(crate) struct EcashState { + pub(crate) required_deposit_cache: RwLock, + + pub(crate) cached_epoch: RwLock, + + pub(crate) master_verification_key: CachedImmutableEpochItem, + + pub(crate) threshold_values: CachedImmutableEpochItem, + + pub(crate) epoch_clients: CachedImmutableEpochItem>, + + pub(crate) coin_index_signatures: CachedImmutableEpochItem, + + pub(crate) expiration_date_signatures: + CachedImmutableItems, +} + +// explicitly wrap the WriteGuard for extra information regarding time taken +pub(crate) struct ChainWritePermit<'a> { + // it's not really dead, we only care about it being dropped + #[allow(dead_code)] + lock_timer: LockTimer, + inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>, +} + +impl<'a> Deref for ChainWritePermit<'a> { + type Target = DirectSigningHttpRpcNyxdClient; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} diff --git a/nym-vpn-api/vpn-api-server/src/http/types.rs b/nym-vpn-api/vpn-api-server/src/http/types.rs new file mode 100644 index 0000000000..34406480da --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/http/types.rs @@ -0,0 +1,64 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use axum::Json; +use nym_vpn_api_requests::api::v1::ErrorResponse; +use utoipa::ToResponse; +use uuid::Uuid; + +#[derive(Debug, Clone, ToResponse)] +#[response(description = "Error response with additional message")] +pub struct RequestError { + pub inner: ErrorResponse, + + pub status: StatusCode, +} + +impl RequestError { + pub fn new>(message: S, status: StatusCode) -> Self { + RequestError { + inner: ErrorResponse { + uuid: None, + message: message.into(), + }, + status, + } + } + + pub fn new_status(status: StatusCode) -> Self { + RequestError { + inner: ErrorResponse { + uuid: None, + message: String::new(), + }, + status, + } + } + + pub fn new_server_error(err: VpnApiError, uuid: Uuid) -> Self { + RequestError::new_with_uuid(err.to_string(), uuid, StatusCode::INTERNAL_SERVER_ERROR) + } + + pub fn new_with_uuid>(message: S, uuid: Uuid, status: StatusCode) -> Self { + RequestError { + inner: ErrorResponse { + uuid: Some(uuid), + message: message.into(), + }, + status, + } + } + + pub fn from_err(err: E, status: StatusCode) -> Self { + Self::new(err.to_string(), status) + } +} + +impl IntoResponse for RequestError { + fn into_response(self) -> Response { + (self.status, Json(self.inner)).into_response() + } +} diff --git a/nym-vpn-api/vpn-api-server/src/main.rs b/nym-vpn-api/vpn-api-server/src/main.rs new file mode 100644 index 0000000000..b534f442c8 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/main.rs @@ -0,0 +1,95 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] +#![warn(clippy::todo)] +#![warn(clippy::dbg_macro)] + +use crate::cli::Cli; +use crate::error::VpnApiError; +use crate::http::state::ApiState; +use crate::http::HttpServer; +use crate::storage::VpnApiStorage; +use crate::tasks::StoragePruner; +use clap::Parser; +use nym_bin_common::logging::setup_tracing_logger; +use nym_network_defaults::setup_env; +use tracing::{info, trace}; + +pub mod cli; +pub mod config; +pub mod credentials; +pub mod error; +pub mod helpers; +pub mod http; +pub mod nym_api_helpers; +pub mod storage; +pub mod tasks; +mod webhook; + +pub async fn wait_for_signal() { + use tokio::signal::unix::{signal, SignalKind}; + + // if we fail to setup the signals, we should just blow up + #[allow(clippy::expect_used)] + let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); + #[allow(clippy::expect_used)] + let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel"); + + tokio::select! { + _ = tokio::signal::ctrl_c() => { + info!("Received SIGINT"); + }, + _ = sigterm.recv() => { + info!("Received SIGTERM"); + } + _ = sigquit.recv() => { + info!("Received SIGQUIT"); + } + } +} + +async fn run_api(cli: Cli) -> Result<(), VpnApiError> { + // create the tasks + let bind_address = cli.bind_address(); + + let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?; + let mnemonic = cli.mnemonic; + let auth_token = cli.http_auth_token; + let webhook_cfg = cli.webhook; + let api_state = ApiState::new(storage.clone(), webhook_cfg, mnemonic).await?; + let http_server = HttpServer::new(bind_address, api_state.clone(), auth_token); + + let storage_pruner = StoragePruner::new(api_state.cancellation_token(), storage); + + // spawn all the tasks + api_state.try_spawn(http_server.run_forever()); + api_state.try_spawn(storage_pruner.run_forever()); + + // wait for cancel signal (SIGINT, SIGTERM or SIGQUIT) + wait_for_signal().await; + + // cancel all the tasks and wait for all task to terminate + api_state.cancel_and_wait().await; + + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + std::env::set_var( + "RUST_LOG", + "trace,handlebars=warn,tendermint_rpc=warn,h2=warn,hyper=warn,rustls=warn,reqwest=warn,tungstenite=warn,async_tungstenite=warn,tokio_util=warn,tokio_tungstenite=warn,tokio-util=warn,nym_validator_client=info", + ); + + let cli = Cli::parse(); + cli.webhook.ensure_valid_client_url()?; + trace!("args: {cli:#?}"); + + setup_env(cli.config_env_file.as_ref()); + setup_tracing_logger(); + + run_api(cli).await?; + Ok(()) +} diff --git a/nym-vpn-api/vpn-api-server/src/nym_api_helpers.rs b/nym-vpn-api/vpn-api-server/src/nym_api_helpers.rs new file mode 100644 index 0000000000..c862cf8047 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/nym_api_helpers.rs @@ -0,0 +1,181 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// TODO: this was just copied from nym-api; +// it should have been therefore extracted to a common crate instead and imported as dependency + +use crate::error::VpnApiError; +use futures::{stream, StreamExt}; +use nym_credentials::ecash::utils::{cred_exp_date, ecash_today}; +use nym_validator_client::nym_api::EpochId; +use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch; +use nym_validator_client::EcashApiClient; +use std::cmp::min; +use std::collections::HashMap; +use std::future::Future; +use std::hash::Hash; +use std::ops::Deref; +use time::{Date, OffsetDateTime}; +use tokio::sync::{Mutex, RwLock, RwLockReadGuard}; +use tracing::warn; + +pub(crate) struct CachedEpoch { + valid_until: OffsetDateTime, + pub(crate) current_epoch: Epoch, +} + +impl Default for CachedEpoch { + fn default() -> Self { + CachedEpoch { + valid_until: OffsetDateTime::UNIX_EPOCH, + current_epoch: Epoch::default(), + } + } +} + +impl CachedEpoch { + pub(crate) fn is_valid(&self) -> bool { + self.valid_until > OffsetDateTime::now_utc() + } + + pub(crate) fn update(&mut self, epoch: Epoch) { + let now = OffsetDateTime::now_utc(); + + let validity_duration = if let Some(epoch_finish) = epoch.deadline { + #[allow(clippy::unwrap_used)] + let state_end = + OffsetDateTime::from_unix_timestamp(epoch_finish.seconds() as i64).unwrap(); + let until_epoch_state_end = state_end - now; + // make it valid until the next epoch transition or next 5min, whichever is smaller + min(until_epoch_state_end, 5 * time::Duration::MINUTE) + } else { + 5 * time::Duration::MINUTE + }; + + self.valid_until = now + validity_duration; + self.current_epoch = epoch; + } +} + +// a map of items that never change for given key +pub(crate) struct CachedImmutableItems { + // I wonder if there's a more efficient structure with OnceLock or OnceCell or something + inner: RwLock>, +} + +// an item that stays constant throughout given epoch +pub(crate) type CachedImmutableEpochItem = CachedImmutableItems; + +impl Default for CachedImmutableItems { + fn default() -> Self { + CachedImmutableItems { + inner: RwLock::new(HashMap::new()), + } + } +} + +impl Deref for CachedImmutableItems { + type Target = RwLock>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl CachedImmutableItems +where + K: Eq + Hash, +{ + pub(crate) async fn get_or_init(&self, key: K, f: F) -> Result, E> + where + F: FnOnce() -> U, + U: Future>, + K: Clone, + { + // 1. see if we already have the item cached + let guard = self.inner.read().await; + if let Ok(item) = RwLockReadGuard::try_map(guard, |map| map.get(&key)) { + return Ok(item); + } + + // 2. attempt to retrieve (and cache) it + let mut write_guard = self.inner.write().await; + + // see if another task has already set the item whilst we were waiting for the lock + if write_guard.get(&key).is_some() { + let read_guard = write_guard.downgrade(); + + // SAFETY: we just checked the entry exists and we never dropped the guard + #[allow(clippy::unwrap_used)] + return Ok(RwLockReadGuard::map(read_guard, |map| { + map.get(&key).unwrap() + })); + } + + let init = f().await?; + write_guard.insert(key.clone(), init); + + let guard = write_guard.downgrade(); + + // SAFETY: + // we just inserted the entry into the map while NEVER dropping the lock (only downgraded it) + // so it MUST exist and thus the unwrap is fine + #[allow(clippy::unwrap_used)] + Ok(RwLockReadGuard::map(guard, |map| map.get(&key).unwrap())) + } +} + +pub(crate) fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), VpnApiError> { + let today = ecash_today(); + + if expiration_date < today.date() { + // what's the point of signatures with expiration in the past? + return Err(VpnApiError::ExpirationDateTooEarly); + } + + // SAFETY: we're nowhere near MAX date + #[allow(clippy::unwrap_used)] + if expiration_date > cred_exp_date().date().next_day().unwrap() { + // don't allow issuing signatures too far in advance (1 day beyond current value is fine) + return Err(VpnApiError::ExpirationDateTooLate); + } + + Ok(()) +} + +pub(crate) async fn query_all_threshold_apis( + all_apis: Vec, + threshold: u64, + f: F, +) -> Result, VpnApiError> +where + F: Fn(EcashApiClient) -> U, + U: Future>, +{ + let shares = Mutex::new(Vec::with_capacity(all_apis.len())); + + stream::iter(all_apis) + .for_each_concurrent(8, |api| async { + // can't be bothered to restructure the code to appease the borrow checker properly, + // so just assign this to a variable + let disp = api.to_string(); + match f(api).await { + Ok(partial_share) => shares.lock().await.push(partial_share), + Err(err) => { + warn!("failed to obtain partial threshold data from API: {disp}: {err}") + } + } + }) + .await; + + let shares = shares.into_inner(); + + if shares.len() < threshold as usize { + return Err(VpnApiError::InsufficientNumberOfSigners { + threshold, + available: shares.len(), + }); + } + + Ok(shares) +} diff --git a/nym-vpn-api/vpn-api-server/src/storage/manager.rs b/nym-vpn-api/vpn-api-server/src/storage/manager.rs new file mode 100644 index 0000000000..c1dd782e00 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/storage/manager.rs @@ -0,0 +1,413 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use crate::storage::models::{ + BlindedShares, BlindedSharesStatus, MinimalWalletShare, RawCoinIndexSignatures, + RawExpirationDateSignatures, RawVerificationKey, +}; +use nym_validator_client::nyxd::contract_traits::ecash_query_client::DepositId; +use time::{Date, OffsetDateTime}; + +#[derive(Clone)] +pub(crate) struct SqliteStorageManager { + pub(crate) connection_pool: sqlx::SqlitePool, +} + +impl SqliteStorageManager { + pub(crate) async fn load_blinded_shares_status_by_shares_id( + &self, + id: i64, + ) -> Result, sqlx::Error> { + let res = sqlx::query_as( + r#" + SELECT * + FROM blinded_shares + WHERE id = ?; + "#, + ) + .bind(id) + .fetch_optional(&self.connection_pool) + .await?; + + Ok(res) + } + + pub(crate) async fn load_wallet_shares_by_shares_id( + &self, + id: i64, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + MinimalWalletShare, + r#" + SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date + FROM partial_blinded_wallet as t1 + JOIN ticketbook_deposit as t2 + on t1.corresponding_deposit = t2.deposit_id + JOIN blinded_shares as t3 + ON t2.request_uuid = t3.request_uuid + WHERE t3.id = ?; + "#, + id + ) + .fetch_all(&self.connection_pool) + .await + } + + pub(crate) async fn load_blinded_shares_status_by_device_and_credential_id( + &self, + device_id: &str, + credential_id: &str, + ) -> Result, sqlx::Error> { + let res = sqlx::query_as( + r#" + SELECT * + FROM blinded_shares + WHERE device_id = ? AND credential_id = ?; + "#, + ) + .bind(device_id) + .bind(credential_id) + .fetch_optional(&self.connection_pool) + .await?; + + Ok(res) + } + + pub(crate) async fn load_wallet_shares_by_device_and_credential_id( + &self, + device_id: &str, + credential_id: &str, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + MinimalWalletShare, + r#" + SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date + FROM partial_blinded_wallet as t1 + JOIN ticketbook_deposit as t2 + on t1.corresponding_deposit = t2.deposit_id + JOIN blinded_shares as t3 + ON t2.request_uuid = t3.request_uuid + WHERE t3.device_id = ? AND t3.credential_id = ?; + "#, + device_id, + credential_id + ) + .fetch_all(&self.connection_pool) + .await + } + + pub(crate) async fn insert_new_pending_async_shares_request( + &self, + request: String, + device_id: &str, + credential_id: &str, + ) -> Result { + let now = OffsetDateTime::now_utc(); + let res = sqlx::query_as( + r#" + INSERT INTO blinded_shares (status, request_uuid, device_id, credential_id, created, updated) + VALUES (?, ?, ?, ?, ?, ?) + RETURNING * + "#, + ) + .bind(BlindedSharesStatus::Pending) + .bind(request) + .bind(device_id) + .bind(credential_id) + .bind(now) + .bind(now) + .fetch_one(&self.connection_pool) + .await?; + + Ok(res) + } + + pub(crate) async fn update_pending_async_blinded_shares_issued( + &self, + available_shares: i64, + device_id: &str, + credential_id: &str, + ) -> Result { + let now = OffsetDateTime::now_utc(); + let res = sqlx::query_as( + r#" + UPDATE blinded_shares + SET status = ?, updated = ?, error_message = NULL, available_shares = ? + WHERE device_id = ? AND credential_id = ? + RETURNING *; + "#, + ) + .bind(BlindedSharesStatus::Issued) + .bind(now) + .bind(available_shares) + .bind(device_id) + .bind(credential_id) + .fetch_one(&self.connection_pool) + .await?; + + Ok(res) + } + + pub(crate) async fn update_pending_async_blinded_shares_error( + &self, + available_shares: i64, + device_id: &str, + credential_id: &str, + error: &str, + ) -> Result { + let now = time::OffsetDateTime::now_utc(); + let res = sqlx::query_as( + r#" + UPDATE blinded_shares + SET status = ?, error_message = ?, updated = ?, available_shares = ? + WHERE device_id = ? AND credential_id = ? + RETURNING *; + "#, + ) + .bind(BlindedSharesStatus::Error) + .bind(error) + .bind(now) + .bind(available_shares) + .bind(device_id) + .bind(credential_id) + .fetch_one(&self.connection_pool) + .await?; + + Ok(res) + } + + pub(crate) async fn prune_old_blinded_shares( + &self, + delete_after: OffsetDateTime, + ) -> Result<(), VpnApiError> { + sqlx::query!( + r#" + DELETE FROM blinded_shares WHERE created < ? + "#, + delete_after, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn prune_old_partial_blinded_wallets( + &self, + delete_after: OffsetDateTime, + ) -> Result<(), VpnApiError> { + sqlx::query!( + r#" + DELETE FROM partial_blinded_wallet WHERE created < ? + "#, + delete_after, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn prune_old_partial_blinded_wallet_failures( + &self, + delete_after: OffsetDateTime, + ) -> Result<(), VpnApiError> { + sqlx::query!( + r#" + DELETE FROM partial_blinded_wallet_failure WHERE created < ? + "#, + delete_after, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn get_master_verification_key( + &self, + epoch_id: i64, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + RawVerificationKey, + r#" + SELECT epoch_id as "epoch_id: u32", serialised_key, serialization_revision as "serialization_revision: u8" + FROM master_verification_key WHERE epoch_id = ? + "#, + epoch_id + ) + .fetch_optional(&self.connection_pool) + .await + } + + pub(crate) async fn insert_master_verification_key( + &self, + serialisation_revision: u8, + epoch_id: i64, + data: &[u8], + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?)", + epoch_id, + data, + serialisation_revision + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn get_master_coin_index_signatures( + &self, + epoch_id: i64, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + RawCoinIndexSignatures, + r#" + SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8" + FROM global_coin_index_signatures WHERE epoch_id = ? + "#, + epoch_id + ) + .fetch_optional(&self.connection_pool) + .await + } + + pub(crate) async fn insert_master_coin_index_signatures( + &self, + serialisation_revision: u8, + epoch_id: i64, + data: &[u8], + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT INTO global_coin_index_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?)", + epoch_id, + data, + serialisation_revision + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + pub(crate) async fn get_master_expiration_date_signatures( + &self, + expiration_date: Date, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + RawExpirationDateSignatures, + r#" + SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8" + FROM global_expiration_date_signatures + WHERE expiration_date = ? + "#, + expiration_date + ) + .fetch_optional(&self.connection_pool) + .await + } + + pub(crate) async fn insert_master_expiration_date_signatures( + &self, + serialisation_revision: u8, + epoch_id: i64, + expiration_date: Date, + data: &[u8], + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO global_expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision) + VALUES (?, ?, ?, ?) + "#, + expiration_date, + epoch_id, + data, + serialisation_revision + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn insert_deposit_data( + &self, + deposit_id: DepositId, + deposit_tx_hash: String, + requested_on: OffsetDateTime, + request_uuid: String, + deposit_amount: String, + client_pubkey: &[u8], + deposit_ed25519_private_key: &[u8], + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO ticketbook_deposit(deposit_id, deposit_tx_hash, requested_on, request_uuid, deposit_amount, client_pubkey, ed25519_deposit_private_key) + VALUES (?, ?, ?, ?, ?, ?, ?) + "#, + deposit_id, + deposit_tx_hash, + requested_on, + request_uuid, + deposit_amount, + client_pubkey, + deposit_ed25519_private_key, + ) + .execute(&self.connection_pool) + .await?; + + Ok(()) + } + + pub(crate) async fn insert_partial_wallet_share( + &self, + deposit_id: DepositId, + epoch_id: i64, + expiration_date: Date, + node_id: i64, + created: OffsetDateTime, + share: &[u8], + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO partial_blinded_wallet(corresponding_deposit, epoch_id, expiration_date, node_id, created, blinded_signature) + VALUES (?, ?, ?, ?, ?, ?) + "#, + deposit_id, + epoch_id, + expiration_date, + node_id, + created, + share + ) + .execute(&self.connection_pool) + .await?; + + Ok(()) + } + + pub(crate) async fn insert_partial_wallet_issuance_failure( + &self, + deposit_id: DepositId, + epoch_id: i64, + expiration_date: Date, + node_id: i64, + created: OffsetDateTime, + failure_message: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO partial_blinded_wallet_failure(corresponding_deposit, epoch_id, expiration_date, node_id, created, failure_message) + VALUES (?, ?, ?, ?, ?, ?) + "#, + deposit_id, + epoch_id, + expiration_date, + node_id, + created, + failure_message + ) + .execute(&self.connection_pool) + .await?; + + Ok(()) + } +} diff --git a/nym-vpn-api/vpn-api-server/src/storage/mod.rs b/nym-vpn-api/vpn-api-server/src/storage/mod.rs new file mode 100644 index 0000000000..e165907871 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/storage/mod.rs @@ -0,0 +1,460 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::credentials::ticketbook::NodeId; +use crate::error::VpnApiError; +use crate::storage::manager::SqliteStorageManager; +use crate::storage::models::{BlindedShares, MinimalWalletShare}; +use nym_compact_ecash::PublicKeyUser; +use nym_credentials::ecash::bandwidth::issuance::Hash; +use nym_credentials::ecash::bandwidth::serialiser::VersionedSerialise; +use nym_credentials::{ + AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey, +}; +use nym_crypto::asymmetric::ed25519; +use nym_validator_client::ecash::BlindedSignatureResponse; +use nym_validator_client::nym_api::EpochId; +use nym_validator_client::nyxd::contract_traits::ecash_query_client::DepositId; +use nym_validator_client::nyxd::Coin; +use sqlx::ConnectOptions; +use std::fmt::Debug; +use std::path::Path; +use time::{Date, OffsetDateTime}; +use tracing::{debug, error, info, instrument}; +use uuid::Uuid; +use zeroize::Zeroizing; + +mod manager; +pub mod models; + +#[derive(Clone)] +pub struct VpnApiStorage { + pub(crate) storage_manager: SqliteStorageManager, +} + +impl VpnApiStorage { + #[instrument] + pub async fn init + Debug>(database_path: P) -> Result { + debug!("Attempting to connect to database"); + + let opts = sqlx::sqlite::SqliteConnectOptions::new() + .filename(database_path) + .create_if_missing(true) + .disable_statement_logging(); + + let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { + Ok(db) => db, + Err(err) => { + error!("Failed to connect to SQLx database: {err}"); + return Err(err.into()); + } + }; + + if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await { + error!("Failed to initialize SQLx database: {err}"); + return Err(err.into()); + } + + info!("Database migration finished!"); + + Ok(VpnApiStorage { + storage_manager: SqliteStorageManager { connection_pool }, + }) + } + + #[allow(dead_code)] + pub(crate) async fn load_blinded_shares_status_by_shares_id( + &self, + id: i64, + ) -> Result, VpnApiError> { + Ok(self + .storage_manager + .load_blinded_shares_status_by_shares_id(id) + .await?) + } + + pub(crate) async fn load_wallet_shares_by_shares_id( + &self, + id: i64, + ) -> Result, VpnApiError> { + Ok(self + .storage_manager + .load_wallet_shares_by_shares_id(id) + .await?) + } + + #[allow(dead_code)] + pub(crate) async fn load_blinded_shares_status_by_device_and_credential_id( + &self, + device_id: &str, + credential_id: &str, + ) -> Result, VpnApiError> { + Ok(self + .storage_manager + .load_blinded_shares_status_by_device_and_credential_id(device_id, credential_id) + .await?) + } + + pub(crate) async fn load_wallet_shares_by_device_and_credential_id( + &self, + device_id: &str, + credential_id: &str, + ) -> Result, VpnApiError> { + Ok(self + .storage_manager + .load_wallet_shares_by_device_and_credential_id(device_id, credential_id) + .await?) + } + + pub(crate) async fn insert_new_pending_async_shares_request( + &self, + request: Uuid, + device_id: &str, + credential_id: &str, + ) -> Result { + Ok(self + .storage_manager + .insert_new_pending_async_shares_request(request.to_string(), device_id, credential_id) + .await?) + } + + pub(crate) async fn update_pending_async_blinded_shares_issued( + &self, + available_shares: usize, + device_id: &str, + credential_id: &str, + ) -> Result { + self.storage_manager + .update_pending_async_blinded_shares_issued( + available_shares as i64, + device_id, + credential_id, + ) + .await + } + + pub(crate) async fn update_pending_async_blinded_shares_error( + &self, + available_shares: usize, + device_id: &str, + credential_id: &str, + error: &str, + ) -> Result { + self.storage_manager + .update_pending_async_blinded_shares_error( + available_shares as i64, + device_id, + credential_id, + error, + ) + .await + } + + pub(crate) async fn prune_old_blinded_shares(&self) -> Result<(), VpnApiError> { + let max_age = OffsetDateTime::now_utc() - time::Duration::days(31); + + self.storage_manager + .prune_old_partial_blinded_wallets(max_age) + .await?; + self.storage_manager + .prune_old_partial_blinded_wallet_failures(max_age) + .await?; + self.storage_manager.prune_old_blinded_shares(max_age).await + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn insert_deposit_data( + &self, + deposit_id: DepositId, + deposit_tx_hash: Hash, + requested_on: OffsetDateTime, + request: Uuid, + deposit_amount: Coin, + client_ecash_pubkey: &PublicKeyUser, + ed22519_keypair: &ed25519::KeyPair, + ) -> Result<(), VpnApiError> { + debug!("inserting deposit data"); + + let private_key_bytes = Zeroizing::new(ed22519_keypair.private_key().to_bytes()); + + self.storage_manager + .insert_deposit_data( + deposit_id, + deposit_tx_hash.to_string(), + requested_on, + request.to_string(), + deposit_amount.to_string(), + &client_ecash_pubkey.to_bytes(), + private_key_bytes.as_ref(), + ) + .await?; + Ok(()) + } + + pub(crate) async fn insert_partial_wallet_share( + &self, + deposit_id: DepositId, + epoch_id: EpochId, + expiration_date: Date, + node_id: NodeId, + res: &Result, + ) -> Result<(), VpnApiError> { + debug!("inserting partial wallet share"); + let now = OffsetDateTime::now_utc(); + + match res { + Ok(share) => { + self.storage_manager + .insert_partial_wallet_share( + deposit_id, + epoch_id as i64, + expiration_date, + node_id as i64, + now, + &share.blinded_signature.to_bytes(), + ) + .await?; + } + Err(err) => { + self.storage_manager + .insert_partial_wallet_issuance_failure( + deposit_id, + epoch_id as i64, + expiration_date, + node_id as i64, + now, + err.to_string(), + ) + .await? + } + } + Ok(()) + } + + pub(crate) async fn get_master_verification_key( + &self, + epoch_id: EpochId, + ) -> Result, VpnApiError> { + let Some(raw) = self + .storage_manager + .get_master_verification_key(epoch_id as i64) + .await? + else { + return Ok(None); + }; + + let deserialised = + EpochVerificationKey::try_unpack(&raw.serialised_key, raw.serialization_revision) + .map_err(|err| VpnApiError::database_inconsistency(err.to_string()))?; + Ok(Some(deserialised)) + } + + pub(crate) async fn insert_master_verification_key( + &self, + key: &EpochVerificationKey, + ) -> Result<(), VpnApiError> { + let packed = key.pack(); + Ok(self + .storage_manager + .insert_master_verification_key(packed.revision, key.epoch_id as i64, &packed.data) + .await?) + } + + pub(crate) async fn get_master_coin_index_signatures( + &self, + epoch_id: EpochId, + ) -> Result, VpnApiError> { + let Some(raw) = self + .storage_manager + .get_master_coin_index_signatures(epoch_id as i64) + .await? + else { + return Ok(None); + }; + + let deserialised = AggregatedCoinIndicesSignatures::try_unpack( + &raw.serialised_signatures, + raw.serialization_revision, + ) + .map_err(|err| VpnApiError::database_inconsistency(err.to_string()))?; + Ok(Some(deserialised)) + } + + pub(crate) async fn insert_master_coin_index_signatures( + &self, + signatures: &AggregatedCoinIndicesSignatures, + ) -> Result<(), VpnApiError> { + let packed = signatures.pack(); + self.storage_manager + .insert_master_coin_index_signatures( + packed.revision, + signatures.epoch_id as i64, + &packed.data, + ) + .await?; + Ok(()) + } + + pub(crate) async fn get_master_expiration_date_signatures( + &self, + expiration_date: Date, + ) -> Result, VpnApiError> { + let Some(raw) = self + .storage_manager + .get_master_expiration_date_signatures(expiration_date) + .await? + else { + return Ok(None); + }; + + let deserialised = AggregatedExpirationDateSignatures::try_unpack( + &raw.serialised_signatures, + raw.serialization_revision, + ) + .map_err(|err| VpnApiError::database_inconsistency(err.to_string()))?; + Ok(Some(deserialised)) + } + + pub(crate) async fn insert_master_expiration_date_signatures( + &self, + signatures: &AggregatedExpirationDateSignatures, + ) -> Result<(), VpnApiError> { + let packed = signatures.pack(); + self.storage_manager + .insert_master_expiration_date_signatures( + packed.revision, + signatures.epoch_id as i64, + signatures.expiration_date, + &packed.data, + ) + .await?; + Ok(()) + } +} + +#[allow(clippy::expect_used)] +#[allow(clippy::unwrap_used)] +#[cfg(test)] +mod tests { + use super::*; + use crate::http::helpers; + use crate::storage::models::BlindedSharesStatus; + use nym_compact_ecash::scheme::keygen::KeyPairUser; + use rand::rngs::OsRng; + use rand::RngCore; + use std::ops::Deref; + use tempfile::{NamedTempFile, TempPath}; + + // create the wrapper so the underlying file gets deleted when it's no longer needed + struct StorageTestWrapper { + inner: VpnApiStorage, + _path: TempPath, + } + + impl StorageTestWrapper { + async fn new() -> anyhow::Result { + let file = NamedTempFile::new()?; + let path = file.into_temp_path(); + + println!("Creating database at {:?}...", path); + + Ok(StorageTestWrapper { + inner: VpnApiStorage::init(&path).await?, + _path: path, + }) + } + + async fn insert_dummy_deposit(&self, uuid: Uuid) -> anyhow::Result { + let mut rng = OsRng; + let deposit_id = rng.next_u32(); + let tx_hash = Hash::Sha256(Default::default()); + let requested_on = OffsetDateTime::now_utc(); + let deposit_amount = Coin::new(1, "ufoomp"); + let client_keypair = KeyPairUser::new(); + let client_ecash_pubkey = &client_keypair.public_key(); + + let deposit_keypair = ed25519::KeyPair::new(&mut rng); + + self.inner + .insert_deposit_data( + deposit_id, + tx_hash, + requested_on, + uuid, + deposit_amount, + client_ecash_pubkey, + &deposit_keypair, + ) + .await?; + + Ok(deposit_id) + } + } + + impl Deref for StorageTestWrapper { + type Target = VpnApiStorage; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + async fn get_storage() -> anyhow::Result { + StorageTestWrapper::new().await + } + + #[tokio::test] + async fn test_creation() -> anyhow::Result<()> { + let storage = get_storage().await; + assert!(storage.is_ok()); + + Ok(()) + } + + #[tokio::test] + async fn test_add() -> anyhow::Result<()> { + let storage = get_storage().await?; + + let dummy_uuid = helpers::random_uuid(); + println!("πŸš€ insert_pending_blinded_share..."); + + storage.insert_dummy_deposit(dummy_uuid).await?; + let res = storage + .insert_new_pending_async_shares_request(dummy_uuid, "1234", "1234") + .await; + if let Err(e) = &res { + println!("❌ {}", e); + } + assert!(res.is_ok()); + let res = res.unwrap(); + println!("res = {:?}", res); + assert_eq!(res.status, BlindedSharesStatus::Pending); + + println!("πŸš€ update_pending_blinded_share_error..."); + let res = storage + .update_pending_async_blinded_shares_error(0, "1234", "1234", "this is an error") + .await; + if let Err(e) = &res { + println!("❌ {}", e); + } + assert!(res.is_ok()); + let res = res.unwrap(); + println!("res = {:?}", res); + assert!(res.error_message.is_some()); + assert_eq!(res.status, BlindedSharesStatus::Error); + + println!("πŸš€ update_pending_blinded_share_data..."); + let res = storage + .update_pending_async_blinded_shares_issued(42, "1234", "1234") + .await; + if let Err(e) = &res { + println!("❌ {}", e); + } + assert!(res.is_ok()); + let res = res.unwrap(); + println!("res = {:?}", res); + assert_eq!(res.status, BlindedSharesStatus::Issued); + assert!(res.error_message.is_none()); + + Ok(()) + } +} diff --git a/nym-vpn-api/vpn-api-server/src/storage/models.rs b/nym-vpn-api/vpn-api-server/src/storage/models.rs new file mode 100644 index 0000000000..191db6d250 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/storage/models.rs @@ -0,0 +1,86 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, Type}; +use std::convert::Into; +use strum_macros::{Display, EnumString}; +use time::{Date, OffsetDateTime}; + +#[derive(Serialize, Deserialize, Debug, Clone, EnumString, Type, PartialEq, Display)] +#[sqlx(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum BlindedSharesStatus { + Pending, + Issued, + Error, +} + +#[derive(Serialize, Deserialize, Debug, Clone, FromRow)] +pub struct BlindedShares { + pub id: i64, + pub request_uuid: String, + pub status: BlindedSharesStatus, + pub device_id: String, + pub credential_id: String, + pub available_shares: i64, + pub error_message: Option, + pub created: OffsetDateTime, + pub updated: OffsetDateTime, +} + +pub struct FullBlindedShares { + pub status: BlindedShares, + pub shares: (), +} + +#[derive(FromRow)] +pub struct RawExpirationDateSignatures { + #[allow(dead_code)] + pub epoch_id: u32, + pub serialised_signatures: Vec, + pub serialization_revision: u8, +} + +#[derive(FromRow)] +pub struct RawCoinIndexSignatures { + #[allow(dead_code)] + pub epoch_id: u32, + pub serialised_signatures: Vec, + pub serialization_revision: u8, +} + +#[derive(FromRow)] +pub struct RawVerificationKey { + #[allow(dead_code)] + pub epoch_id: u32, + pub serialised_key: Vec, + pub serialization_revision: u8, +} + +#[derive(FromRow)] +pub struct WalletShare { + #[allow(dead_code)] + pub corresponding_deposit: i64, + pub node_id: i64, + #[allow(dead_code)] + pub created: OffsetDateTime, + pub blinded_signature: Vec, +} + +#[derive(FromRow)] +pub struct MinimalWalletShare { + pub epoch_id: i64, + pub expiration_date: Date, + pub node_id: i64, + pub blinded_signature: Vec, +} + +impl From for nym_vpn_api_requests::api::v1::ticketbook::models::WalletShare { + fn from(value: MinimalWalletShare) -> Self { + nym_vpn_api_requests::api::v1::ticketbook::models::WalletShare { + node_index: value.node_id as u64, + bs58_encoded_share: bs58::encode(&value.blinded_signature).into_string(), + } + } +} diff --git a/nym-vpn-api/vpn-api-server/src/tasks.rs b/nym-vpn-api/vpn-api-server/src/tasks.rs new file mode 100644 index 0000000000..6389f58452 --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/tasks.rs @@ -0,0 +1,36 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::storage::VpnApiStorage; +use tokio_util::sync::CancellationToken; +use tracing::{error, info}; + +pub struct StoragePruner { + cancellation_token: CancellationToken, + storage: VpnApiStorage, +} + +impl StoragePruner { + pub fn new(cancellation_token: CancellationToken, storage: VpnApiStorage) -> Self { + Self { + cancellation_token, + storage, + } + } + + pub async fn run_forever(self) { + while !self.cancellation_token.is_cancelled() { + tokio::select! { + _ = self.cancellation_token.cancelled() => { + // The token was cancelled, task can shut down + } + _ = tokio::time::sleep(std::time::Duration::from_secs(60 * 60)) => { + match self.storage.prune_old_blinded_shares().await { + Ok(_res) => info!("🧹 Pruning old blinded shares complete"), + Err(err) => error!("Failed to prune old blinded shares: {err}"), + } + } + } + } + } +} diff --git a/nym-vpn-api/vpn-api-server/src/webhook.rs b/nym-vpn-api/vpn-api-server/src/webhook.rs new file mode 100644 index 0000000000..9ebdca250f --- /dev/null +++ b/nym-vpn-api/vpn-api-server/src/webhook.rs @@ -0,0 +1,75 @@ +// Copyright 2024 Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::error::VpnApiError; +use clap::Args; +use reqwest::header::AUTHORIZATION; +use serde::Serialize; +use tracing::{debug, error, instrument, span, Level}; +use url::Url; +use uuid::Uuid; + +#[derive(Args, Debug, Clone)] +pub struct ZkNymWebHookConfig { + #[clap(long, env = "WEBHOOK_ZK_NYMS_URL")] + pub webhook_url: Url, + + #[clap(long, env = "WEBHOOK_ZK_NYMS_CLIENT_ID")] + pub webhook_client_id: String, + + #[clap(long, env = "WEBHOOK_ZK_NYMS_CLIENT_SECRET")] + pub webhook_client_secret: String, +} + +impl ZkNymWebHookConfig { + pub fn ensure_valid_client_url(&self) -> Result<(), VpnApiError> { + self.client_url() + .map_err(|_| VpnApiError::InvalidWebhookUrl) + .map(|_| ()) + } + + fn client_url(&self) -> Result { + self.webhook_url.join(&self.webhook_client_id) + } + + fn unchecked_client_url(&self) -> Url { + // we ensured we have valid url on startup + #[allow(clippy::unwrap_used)] + self.client_url().unwrap() + } + + fn bearer_token(&self) -> String { + format!("Bearer {}", self.webhook_client_secret) + } + + #[instrument(skip_all)] + pub async fn try_trigger(&self, original_uuid: Uuid, payload: &T) { + let url = self.unchecked_client_url(); + let span = span!(Level::DEBUG, "webhook", uuid = %original_uuid, url = %url); + let _entered = span.enter(); + + debug!("πŸ•ΈοΈ about to trigger the webhook"); + + match reqwest::Client::new() + .post(url.clone()) + .header(AUTHORIZATION, self.bearer_token()) + .json(payload) + .send() + .await + { + Ok(res) => { + if !res.status().is_success() { + error!("βŒπŸ•ΈοΈ failed to call webhook: {res:?}"); + } else { + debug!("βœ…πŸ•ΈοΈ webhook triggered successfully: {res:?}"); + if let Ok(body) = res.text().await { + debug!("body = {body}"); + } + } + } + Err(err) => { + error!("failed to call webhook: {err}") + } + } + } +}