diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..f386da5c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,62 @@ +name: ci + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: commerceblockx + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - + name: Get last commit hash + id: get_commit_hash + run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + - + name: Build and push for mercury-server + uses: docker/build-push-action@v5 + with: + context: . + file: ./server/Dockerfile + push: true + tags: commerceblockx/mercury-server:${{ env.COMMIT_HASH }} + - + name: Build and push for token-server + uses: docker/build-push-action@v5 + with: + context: . + file: ./token-server/Dockerfile + push: true + tags: commerceblockx/token-server:${{ env.COMMIT_HASH }} + - + name: Build and push for mercury-explorer + uses: docker/build-push-action@v5 + with: + context: . + file: ./explorer/Dockerfile + push: true + tags: commerceblockx/mercury-explorer:${{ env.COMMIT_HASH }} + - + name: Build and push for keylist-cronjob + uses: docker/build-push-action@v5 + with: + context: . + file: ./keylistCron/Dockerfile + push: true + tags: commerceblockx/keylist-cronjob:${{ env.COMMIT_HASH }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..bc05bc7c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,112 @@ +name: Integration Tests + +on: + push: + branches: + - dev + pull_request: + branches: + - dev + +jobs: + test: + runs-on: ubuntu-latest + + services: + docker: + image: docker:19.03.12 + options: --privileged + ports: + - 5432:5432 + - 18443:18443 + - 50002:50002 + - 50001:50001 + - 8000:8000 + - 18080:18080 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Docker Compose + run: | + docker-compose -f docker-compose-test.yml up --build -d + - name: Wait for services to be ready + run: | + sleep 80 # Adjust time as necessary for services to initialize + - name: Verify Bitcoin daemon Service with Curl + run: | + container_id=$(docker ps -qf "name=mercurylayer_bitcoind_1") + echo "Container ID: $container_id" + docker logs $container_id + wallet_name="new_wallet" + docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass createwallet $wallet_name + address=$(docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getnewaddress $wallet_name) + echo "New Wallet Address: $address" + docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address" + docker exec $container_id bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001 + - name: Verify ElectrumX Service with Curl + run: | + container_id=$(docker ps -qf "name=mercurylayer_electrs_1") + echo "Container ID: $container_id" + docker logs $container_id + - name: Verify Enclave Service with Curl + run: | + container_id=$(docker ps -qf "name=mercurylayer_enclave-sgx_1") + echo "Container ID: $container_id" + docker logs $container_id + - name: Verify Mercury Service with Curl + run: | + container_id=$(docker ps -qf "name=mercurylayer_mercury_1") + echo "Container ID: $container_id" + docker logs $container_id + docker exec $container_id \ + curl http://0.0.0.0:8000/info/config + - name: Get Public Key + run: | + docker exec $(docker ps -qf "name=enclave") \ + curl -X POST http://0.0.0.0:18080/get_public_key \ + -H "Content-Type: application/json" \ + -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' + docker logs $(docker ps -qf "name=enclave") + - name: Check connectivity between containers + run: | + # Get container IDs + enclave_container=$(docker ps -qf "name=mercurylayer_enclave-sgx_1") + mercury_container=$(docker ps -qf "name=mercurylayer_mercury_1") + + # Check if mercurylayer_mercury_1 can reach mercurylayer_enclave-sgx_1 + docker exec $mercury_container curl -v http://mercurylayer_enclave-sgx_1:18080/get_public_key \ + -H "Content-Type: application/json" \ + -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' + + # Alternatively, using IP address if service name resolution fails + enclave_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $enclave_container) + docker exec $mercury_container curl -v http://$enclave_ip:18080/get_public_key \ + -H "Content-Type: application/json" \ + -d '{"statechain_id":"550e8400e29b41d4a716446655440000"}' + + docker inspect mercurylayer_mercury_1 + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '20.12.2' + + - name: Install Node.js dependencies for client + run: | + cd clients/apps/nodejs + npm install + - name: Install Node.js dependencies for lib + run: | + cd clients/libs/nodejs + npm install + - name: Run Client-Side Tests + run: | + cd clients/apps/nodejs + node test_basic_workflow2.js + node test_atomic_swap.js + - name: Tear Down + run: | + docker-compose -f docker-compose-test.yml down diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..54e5a418 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +.vscode +/server/.env +/token-server/.env diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..a339f0cd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4056 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn 2.0.60", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[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" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bip39" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b9e657de8ff1c3488a4ab77cb51d604eab53415ce34f0bc800f2eac9b13c28" +dependencies = [ + "bitcoin_hashes 0.11.0", + "rand_core 0.4.2", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75" +dependencies = [ + "base64 0.13.1", + "bech32", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "bitcoinconsensus", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", + "serde", +] + +[[package]] +name = "bitcoinconsensus" +version = "0.20.2-0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54505558b77e0aa21b2491a7b39cbae9db22ac8b1bc543ef4600edb762306f9c" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +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.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[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.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "client-rust" +version = "0.1.0" +dependencies = [ + "anyhow", + "bech32", + "bip39", + "bitcoin", + "chrono", + "clap", + "config", + "electrum-client", + "hex", + "mercuryrustlib", + "rand", + "reqwest", + "schemars", + "secp256k1-zkp", + "serde", + "serde_json", + "sqlx", + "tokio", + "uuid 1.4.1", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.4.0", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" + +[[package]] +name = "ecies" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0206e602d2645ec8b24ed8307fadbc6c3110e2b11ab2f806fc02fee49327079" +dependencies = [ + "aes-gcm", + "getrandom", + "hkdf", + "libsecp256k1", + "once_cell", + "parking_lot", + "rand_core 0.6.4", + "sha2", + "typenum", + "wasm-bindgen", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "electrum-client" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc133f1c8d829d254f013f946653cbeb2b08674b960146361d1e9b67733ad19" +dependencies = [ + "bitcoin", + "bitcoin-private", + "byteorder", + "libc", + "log", + "rustls", + "serde", + "serde_json", + "webpki", + "webpki-roots 0.22.6", + "winapi", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + +[[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", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "figment" +version = "0.10.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a014ac935975a70ad13a3bff2463b1c1b083b35ae4cb6309cfc59476aa7a181f" +dependencies = [ + "atomic 0.6.0", + "pear", + "serde", + "toml 0.8.2", + "uncased", + "version_check", +] + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[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 0.9.8", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "goblin" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07a4ffed2093b118a525b1d8f5204ae274faed5604537caf7135d0f18d9887" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.1", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de910d521f7cc3135c4de8db1cb910e0b5ed1dc6f57c381cd07e8e661ce10094" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[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", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[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 = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[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.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mercury-server" +version = "0.1.0" +dependencies = [ + "bitcoin", + "chrono", + "config", + "hex", + "mercurylib", + "rand", + "reqwest", + "rocket", + "schemars", + "secp256k1-zkp", + "serde", + "serde_json", + "sha2", + "sqlx", + "uuid 1.4.1", +] + +[[package]] +name = "mercury-wasm" +version = "0.1.0" +dependencies = [ + "bip39", + "console_error_panic_hook", + "getrandom", + "js-sys", + "mercurylib", + "rand", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "mercurylib" +version = "0.1.0" +dependencies = [ + "bech32", + "bip39", + "bitcoin", + "ecies", + "hex", + "secp256k1-zkp", + "serde", + "serde_json", + "thiserror", + "uniffi", +] + +[[package]] +name = "mercuryrustlib" +version = "0.1.0" +dependencies = [ + "anyhow", + "bech32", + "bip39", + "bitcoin", + "chrono", + "clap", + "config", + "electrum-client", + "hex", + "mercurylib", + "rand", + "reqwest", + "schemars", + "secp256k1-zkp", + "serde", + "serde_json", + "sqlx", + "tokio", + "uuid 1.4.1", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +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.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "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 1.11.1", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "oneshot-uniffi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +dependencies = [ + "bitflags 2.4.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.60", +] + +[[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.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec 1.11.1", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pear" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.60", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "pest_meta" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "version_check", + "yansi", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +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.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "regex" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.1", + "regex-syntax 0.8.0", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.0", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.4", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-socks", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + +[[package]] +name = "rocket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7bb57ccb26670d73b6a47396c83139447b9e7878cab627fdfe9ea8da489150" +dependencies = [ + "async-stream", + "async-trait", + "atomic 0.5.3", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap 2.0.2", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" +dependencies = [ + "devise", + "glob", + "indexmap 2.0.2", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.60", + "unicode-xid", + "version_check", +] + +[[package]] +name = "rocket_http" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a1663694d059fe5f943ea5481363e48050acedd241d46deb2e27f71110389e" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap 2.0.2", + "log", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec 1.11.1", + "stable-pattern", + "state", + "time", + "tokio", + "uncased", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust" +version = "0.1.0" +dependencies = [ + "anyhow", + "electrum-client", + "hex", + "mercuryrustlib", + "serde", + "serde_json", + "sha2", + "tokio", + "uuid 1.4.1", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring 0.16.20", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.4", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "schemars" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +dependencies = [ + "chrono", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", + "uuid 0.8.2", +] + +[[package]] +name = "schemars_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "bitcoin_hashes 0.12.0", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-zkp" +version = "0.9.2" +source = "git+https://github.com/ssantos21/rust-secp256k1-zkp.git?branch=blinded-musig-scheme#e03de85e3c74e58249bb3bb43e8642e5e744cfa0" +dependencies = [ + "bitcoin-private", + "rand", + "secp256k1", + "secp256k1-zkp-sys", +] + +[[package]] +name = "secp256k1-zkp-sys" +version = "0.8.1" +source = "git+https://github.com/ssantos21/rust-secp256k1-zkp.git?branch=blinded-musig-scheme#e03de85e3c74e58249bb3bb43e8642e5e744cfa0" +dependencies = [ + "cc", + "secp256k1-sys", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +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 = "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.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 = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[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 = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +dependencies = [ + "ahash 0.8.3", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.0.2", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec 1.11.1", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid 1.4.1", + "webpki-roots 0.24.0", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +dependencies = [ + "atoi", + "base64 0.21.4", + "bitflags 2.4.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec 1.11.1", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid 1.4.1", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +dependencies = [ + "atoi", + "base64 0.21.4", + "bitflags 2.4.0", + "byteorder", + "chrono", + "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", + "sha1", + "sha2", + "smallvec 1.11.1", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid 1.4.1", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time", + "tracing", + "url", + "uuid 1.4.1", +] + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[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.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +dependencies = [ + "deranged", + "itoa", + "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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "token-server" +version = "0.1.0" +dependencies = [ + "config", + "hex", + "rand", + "reqwest", + "rocket", + "schemars", + "serde", + "serde_json", + "sqlx", + "uuid 1.4.1", +] + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.4", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[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-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.0.2", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec 1.11.1", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ubyte" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" +dependencies = [ + "serde", +] + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uncased" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "serde", + "version_check", +] + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-normalization" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf" +dependencies = [ + "smallvec 0.6.14", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "uniffi" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5566fae48a5cb017005bf9cd622af5236b2a203a13fb548afde3506d3c68277" +dependencies = [ + "anyhow", + "camino", + "clap", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a77bb514bcd4bf27c9bd404d7c3f2a6a8131b957eba9c22cfeb7751c4278e09" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "clap", + "fs-err", + "glob", + "goblin", + "heck", + "once_cell", + "paste", + "serde", + "textwrap", + "toml 0.5.11", + "uniffi_meta", + "uniffi_testing", + "uniffi_udl", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae7e5a6c33b1dec3f255f57ec0b6af0f0b2bb3021868be1d5eec7a38e2905ebc" +dependencies = [ + "quote", + "syn 2.0.60", +] + +[[package]] +name = "uniffi_core" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea3eb5474d50fc149b7e4d86b9c5bd4a61dcc167f0683902bf18ae7bbb3deef" +dependencies = [ + "anyhow", + "bytes", + "camino", + "log", + "once_cell", + "oneshot-uniffi", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18331d35003f46f0d04047fbe4227291815b83a937a8c32bc057f990962182c4" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.60", + "toml 0.5.11", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7224422c4cfd181c7ca9fca2154abca4d21db962f926f270f996edd38b0c4b8" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ce878d0bdfc288b58797044eaaedf748526c56eef3575380bb4d4b19d69eee" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c43c9ed40a8d20a5c3eae2d23031092db6b96dc8e571beb449ba9757484cea0" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom", + "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 = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[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 = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.3", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +dependencies = [ + "rustls-webpki", +] + +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" +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", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..5f7d46e9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = ["clients/apps/rust", "clients/libs/rust", "server", "wasm", "lib", "token-server", "clients/tests/rust"] +exclude = ["clients/apps/kotlin", "clients/apps/nodejs", "clients/apps/react-app", "clients/libs/nodejs", "docs", "enclave", "explorer", "keylistCron"] +resolver = "2" diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 00000000..dea7ede3 --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,3 @@ +[global] +address = "0.0.0.0" +port = 8000 \ No newline at end of file diff --git a/Usage.md b/Usage.md new file mode 100644 index 00000000..bd8762fc --- /dev/null +++ b/Usage.md @@ -0,0 +1,38 @@ +# Usage + +## Simulation mode + +### Bulid and run docker containers + +```bash +$ cd mercurylayer +$ docker compose -f docker-compose-sim.yml up --build +``` +### Add mmnemonics + +```bash +$ docker exec -it mercurylayer-enclave-sgx-1 bash +$ curl -X POST http://0.0.0.0:18080/add_mnemonic \ +-H "Content-Type: application/json" \ +-d '{ + "mnemonic": "achieve merry hidden lyrics element brand student armed dismiss vague fury avocado grief crazy garlic gallery blur spider bag bless motor crawl surround copper", + "password": "b1gHKyfXTzs6", + "index": 0, + "threshold": 2 +}' +``` + +## Hardware Mode + +```bash +$ cd mercurylayer +$ ./build_compose_hw_run.sh +``` + +Add mnemonics in the same way as above. + +## Compose down + +`docker compose -f docker-compose-sim.yml down -v` in simulation mode + +`docker compose -f docker-compose-hw.yml down -v` in hardware mode diff --git a/build_compose_hw_run.sh b/build_compose_hw_run.sh new file mode 100755 index 00000000..ad45db12 --- /dev/null +++ b/build_compose_hw_run.sh @@ -0,0 +1,5 @@ +set -e + +docker volume create --driver local --opt type=tmpfs --opt device=tmpfs --opt o=rw aesmd-socket + +docker compose -f docker-compose-hw.yml up --build diff --git a/client/README.md b/client/README.md deleted file mode 100644 index bca84705..00000000 --- a/client/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Mercury Layer Client - -Mercury layer client provides a user interface to the Mercury Layer protocol, via the Mercury Layer server. diff --git a/clients/apps/kotlin/.gitignore b/clients/apps/kotlin/.gitignore new file mode 100644 index 00000000..d02394dd --- /dev/null +++ b/clients/apps/kotlin/.gitignore @@ -0,0 +1,44 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +wallet.db \ No newline at end of file diff --git a/clients/apps/kotlin/.idea/.gitignore b/clients/apps/kotlin/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/clients/apps/kotlin/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/clients/apps/kotlin/.idea/.name b/clients/apps/kotlin/.idea/.name new file mode 100644 index 00000000..d606037c --- /dev/null +++ b/clients/apps/kotlin/.idea/.name @@ -0,0 +1 @@ +test2 \ No newline at end of file diff --git a/clients/apps/kotlin/.idea/gradle.xml b/clients/apps/kotlin/.idea/gradle.xml new file mode 100644 index 00000000..2a65317e --- /dev/null +++ b/clients/apps/kotlin/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/clients/apps/kotlin/.idea/kotlinc.xml b/clients/apps/kotlin/.idea/kotlinc.xml new file mode 100644 index 00000000..fe63bb67 --- /dev/null +++ b/clients/apps/kotlin/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/clients/apps/kotlin/.idea/misc.xml b/clients/apps/kotlin/.idea/misc.xml new file mode 100644 index 00000000..85d2fc04 --- /dev/null +++ b/clients/apps/kotlin/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/clients/apps/kotlin/.idea/vcs.xml b/clients/apps/kotlin/.idea/vcs.xml new file mode 100644 index 00000000..def3818f --- /dev/null +++ b/clients/apps/kotlin/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/clients/apps/kotlin/README.md b/clients/apps/kotlin/README.md new file mode 100644 index 00000000..500ddfae --- /dev/null +++ b/clients/apps/kotlin/README.md @@ -0,0 +1,49 @@ +# Mercury Layer CL Kotlin client + +## Command-line arguments: + +### Create Wallet +`create-wallet ` + +Example: `create-wallet w1` + +### New Deposit Address +`new-deposit-address ` + +Example: `new-deposit-address w1 1000` + +### List Statecoins + +`list-statecoins ` + +Example: `list-statecoins w1` + +### New Statechain Adress + +`new-transfer-address ` + +Example: `new-transfer-address w2` + +### Send a Statecoin + +`new-transfer-address ` + +Example: `transfer-send w1 2dd78ce438a1450083996fa7f37a02d0 tml1qqp3sp8pu0v38d9krdekckuy4qnueqtnlq8radjwf5rvvgkdnm4y03szgljuxjweppkyymh44wr034avmut5w83xcaey83pp7nqqxqcnyldsa3jpyr` + +### Receive a Statecoin + +`transfer-receive ` + +Example: `transfer-receive w2` + +### Withdraw a Statecoin to a Bitcoin Address + +`withdraw ` + +Example: `withdraw w1 453351464f6a4b0ab8401826576c69be tb1qw2hfzv5qzxtmzatjxn5600k9yqnhhddeu6npu5` + +### Broadcast Basckup Transaction to a Bitcoin Address + +`broadcast-backup-transaction ` + +Example: `broadcast-backup-transaction w1 e82e423298ca4c7bb7e37771b4dc3e8a tb1qw2hfzv5qzxtmzatjxn5600k9yqnhhddeu6npu5` \ No newline at end of file diff --git a/clients/apps/kotlin/Settings.toml b/clients/apps/kotlin/Settings.toml new file mode 100644 index 00000000..ae064d0d --- /dev/null +++ b/clients/apps/kotlin/Settings.toml @@ -0,0 +1,11 @@ +statechain_entity = "http://127.0.0.1:8000" +#statechain_entity = "http://j23wevaeducxuy3zahd6bpn4x76cymwz2j3bdixv7ow4awjrg5p6jaid.onion" +#electrum_server = "tcp://127.0.0.1:50001" +electrum_server = "tcp://signet-electrumx.wakiyamap.dev:50001" +electrum_type = "electrs" +network = "signet" +fee_rate_tolerance = 5 +database_file="wallet.db" +confirmation_target = 2 +#tor_proxy = "socks5h://localhost:9050" +max_fee_rate = 1 \ No newline at end of file diff --git a/clients/apps/kotlin/build.gradle.kts b/clients/apps/kotlin/build.gradle.kts new file mode 100644 index 00000000..9db9b58a --- /dev/null +++ b/clients/apps/kotlin/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + kotlin("jvm") version "1.9.23" + kotlin("plugin.serialization") version "1.9.23" +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } +} + +val ktor_version: String by project + +dependencies { + testImplementation(kotlin("test")) + implementation("net.java.dev.jna:jna:5.14.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("com.github.ajalt.clikt:clikt:4.4.0") + implementation("com.squareup.okio:okio:3.9.0") + implementation("com.akuleshov7:ktoml-core:0.5.1") + implementation("com.akuleshov7:ktoml-file:0.5.1") + implementation("io.ktor:ktor-client-core:$ktor_version") + implementation("io.ktor:ktor-client-cio:$ktor_version") + implementation("io.ktor:ktor-client-content-negotiation:$ktor_version") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") + implementation("org.slf4j:slf4j-nop:2.1.0-alpha1") + implementation("org.slf4j:slf4j-api:2.1.0-alpha1") + // implementation("org.bitcoindevkit:bdk-jvm:1.0.0-alpha.9") + implementation("com.github.bitcoinj:bitcoinj:release-0.16-SNAPSHOT") + implementation("com.github.spindj:electrumj:11eb7ceef7") + implementation("org.xerial:sqlite-jdbc:3.45.3.0") +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/clients/apps/kotlin/gradle.properties b/clients/apps/kotlin/gradle.properties new file mode 100644 index 00000000..8d251a6e --- /dev/null +++ b/clients/apps/kotlin/gradle.properties @@ -0,0 +1,2 @@ +kotlin.code.style=official +ktor_version=2.3.10 \ No newline at end of file diff --git a/clients/apps/kotlin/gradle/wrapper/gradle-wrapper.jar b/clients/apps/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/clients/apps/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/clients/apps/kotlin/gradle/wrapper/gradle-wrapper.properties b/clients/apps/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1b17dfed --- /dev/null +++ b/clients/apps/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Apr 27 06:09:37 BRT 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/clients/apps/kotlin/gradlew b/clients/apps/kotlin/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/clients/apps/kotlin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/clients/apps/kotlin/gradlew.bat b/clients/apps/kotlin/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/clients/apps/kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/clients/apps/kotlin/settings.gradle.kts b/clients/apps/kotlin/settings.gradle.kts new file mode 100644 index 00000000..4e93638f --- /dev/null +++ b/clients/apps/kotlin/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "test2" + diff --git a/clients/apps/kotlin/src/main/kotlin/BroadcastBackupTransaction.kt b/clients/apps/kotlin/src/main/kotlin/BroadcastBackupTransaction.kt new file mode 100644 index 00000000..c06731a8 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/BroadcastBackupTransaction.kt @@ -0,0 +1,104 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.convert +import com.github.ajalt.clikt.parameters.arguments.optional +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +class BroadcastBackupTransaction: CliktCommand(help = "Broadcast a backup transaction via CPFP") { + + private val walletName: String by argument(help = "Name of the wallet") + + private val statechainId: String by argument(help = "Statechain id") + + private val toAddress: String by argument(help = "Address to send the funds") + + private val feeRate: UInt? by argument(help = "Fee Rate").convert { it.toUInt() }.optional() + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + private suspend fun execute() { + val wallet = appContext.sqliteManager.loadWallet(walletName) + + CoinUpdate.execute(wallet, appContext) + + if (!validateAddress(toAddress, wallet.network)) { + throw Exception("Invalid address") + } + + val backupTxs = appContext.sqliteManager.getBackupTxs(statechainId) + + // val backupTx = if (backupTxs.isEmpty()) null else backupTxs.maxByOrNull { it.txN } + + val coinsWithStatechainId = wallet.coins.filter { it.statechainId == statechainId } + + if (coinsWithStatechainId.isEmpty()) { + throw Exception("There is no coin for the statechain id $statechainId") + } + + val coin = coinsWithStatechainId.sortedBy { it.locktime }.first() + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw Exception("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}"); + } + + var backupTx: BackupTx? = null + + try { + backupTx = latestBackupTxPaysToUserPubkey(backupTxs, coin, wallet.network) + } catch (e: Exception) { + println("Error: ${e.message}") + return + } + + var feeRateSatsPerByte: UInt = 0u + + if (feeRate == null) { + val electrumFeeRate = getFeeRateSatsPerByte(appContext.clientConfig) + feeRateSatsPerByte = if (electrumFeeRate > appContext.clientConfig.maxFeeRate.toUInt()) { + appContext.clientConfig.maxFeeRate.toUInt() + } else { + electrumFeeRate.toUInt() + } + } else { + feeRateSatsPerByte = feeRate as UInt + } + + + val cpfpTx = createCpfpTx(backupTx, coin, toAddress, feeRateSatsPerByte.toULong(), wallet.network); + + val electrumClient = getElectrumClient(appContext.clientConfig) + + val backupTxTxid = electrumClient.blockchainTransactionBroadcast(backupTx.tx) + + val cpfpTxTxid = electrumClient.blockchainTransactionBroadcast(cpfpTx) + + electrumClient.closeConnection() + + coin.txCpfp = cpfpTxTxid + coin.withdrawalAddress = toAddress + coin.status = CoinStatus.WITHDRAWING + + appContext.sqliteManager.updateWallet(wallet) + + completeWithdraw(appContext.clientConfig, coin.statechainId!!, coin.signedStatechainId!!) + + val json = buildJsonObject { + put("backupTx", backupTxTxid) + put("cpfpTx", cpfpTxTxid) + } + + println(Json.encodeToString(json)) + } + + override fun run() { + runBlocking { execute() } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/ClientConfig.kt b/clients/apps/kotlin/src/main/kotlin/ClientConfig.kt new file mode 100644 index 00000000..2a27f55f --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/ClientConfig.kt @@ -0,0 +1,58 @@ +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.parsers.TomlParser +import com.akuleshov7.ktoml.source.decodeFromStream +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.io.File +import java.io.InputStream + +@Serializable +data class ConfigData( + @SerialName("statechain_entity") + val statechainEntity: String, + @SerialName("electrum_server") + val electrumServer: String, + @SerialName("electrum_type") + val electrumType: String, + @SerialName("network") + val network: String, + @SerialName("fee_rate_tolerance") + val feeRateTolerance: Short, + @SerialName("database_file") + val databaseFile: String, + @SerialName("confirmation_target") + val confirmationTarget: Short, + @SerialName("max_fee_rate") + val maxFeeRate: Short, +) + +class ClientConfig { + + val statechainEntity: String + val electrumServer: String + val electrumType: String + val network: String + val feeRateTolerance: Int + val databaseFile: String + val confirmationTarget: Int + val maxFeeRate: Int + + init { + val file = File("Settings.toml") + if (!file.exists()) { + throw Exception("The file 'Settings.toml' does not exist.") + } + + val configData = Toml.decodeFromStream(file.inputStream()) + + statechainEntity = configData.statechainEntity + electrumServer = configData.electrumServer + electrumType = configData.electrumType + network = configData.network + feeRateTolerance = configData.feeRateTolerance.toInt() + databaseFile = configData.databaseFile + confirmationTarget = configData.confirmationTarget.toInt() + maxFeeRate = configData.maxFeeRate.toInt() + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/CoinUpdate.kt b/clients/apps/kotlin/src/main/kotlin/CoinUpdate.kt new file mode 100644 index 00000000..25a7ab9a --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/CoinUpdate.kt @@ -0,0 +1,291 @@ +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.serialization.kotlinx.json.* +import org.bitcoinj.core.NetworkParameters +import org.electrumj.Util +import org.electrumj.dto.BlockchainScripthashListUnspentResponseEntry + +data class DepositResult( + val activity: Activity, + val backupTx: BackupTx +) + +class CoinUpdate() { + + companion object { + + private suspend fun createTx1( + coin: Coin, + clientConfig: ClientConfig, + walletNetwork: String, + tx0Hash: String, + tx0Vout: UInt + ): BackupTx { + + if (coin.status != CoinStatus.INITIALISED) { + throw IllegalStateException("The coin with the public key ${coin.userPubkey} is not in the INITIALISED state"); + } + + if (coin.utxoTxid != null && coin.utxoVout != null) { + throw Exception("The coin with the public key ${coin.userPubkey} has already been deposited") + } + coin.utxoTxid = tx0Hash + coin.utxoVout = tx0Vout + + coin.status = CoinStatus.IN_MEMPOOL + + val toAddress = getUserBackupAddress(coin, walletNetwork) + + val infoConfig = getInfoConfig(clientConfig) + + val initlock = infoConfig.initlock; + val interval = infoConfig.interval; + + val feeRateSatsPerByte: UInt = if (infoConfig.feeRateSatsPerByte > clientConfig.maxFeeRate.toUInt()) { + clientConfig.maxFeeRate.toUInt() + } else { + infoConfig.feeRateSatsPerByte.toUInt() + } + + val signedTx = Transaction.create( + coin, + clientConfig, + null, + 0u, + toAddress, + walletNetwork, + false, + feeRateSatsPerByte, + initlock, + interval + ) + + if (coin.publicNonce == null) { + throw Exception("coin.publicNonce is None") + } + + if (coin.blindingFactor == null) { + throw Exception("coin.blindingFactor is None") + } + + if (coin.statechainId == null) { + throw Exception("coin.statechainId is None") + } + + val backupTx = BackupTx( + txN = 1u, + tx = signedTx, + clientPublicNonce = coin.publicNonce ?: throw Exception("publicNonce is null"), + serverPublicNonce = coin.serverPublicNonce ?: throw Exception("serverPublicNonce is null"), + clientPublicKey = coin.userPubkey, + serverPublicKey = coin.serverPubkey ?: throw Exception("serverPubkey is null"), + blindingFactor = coin.blindingFactor ?: throw Exception("blindingFactor is null") + ) + + val blockHeight = getBlockheight(backupTx) + coin.locktime = blockHeight + + return backupTx + } + + /*fun testElect(clientConfig: ClientConfig) { + val electrumClient = getElectrumClient(clientConfig) + + val netParam = NetworkParameters.fromID(NetworkParameters.ID_TESTNET) + val scripthash: String = Util.scripthash(netParam,"tb1p8jgrjg4rt4rt9nydw0r2ql9yl77ns069a3czkz3ml7zyc2whfdks0td33p") + + val responseEntries: List = electrumClient.blockchainScripthashListUnspent(scripthash) + + electrumClient.closeConnection() + + println("responseEntries.count: ${responseEntries.count()}") + + responseEntries.forEach { entry -> + + println("entry.height: ${entry.height}") + println("entry.txHash: ${entry.txHash}") + println("entry.txPos: ${entry.txPos}") + println("entry.value: ${entry.value}") + + } + }*/ + + private suspend fun checkDeposit(coin: Coin, clientConfig: ClientConfig, walletNetwork: String): DepositResult? { + + if (coin.statechainId == null && coin.utxoTxid == null && coin.utxoVout == null) { + if (coin.status != CoinStatus.INITIALISED) { + throw IllegalStateException("Coin does not have a statechain ID, a UTXO and the status is not INITIALISED") + } else { + return null + } + } + + val electrumClient = getElectrumClient(clientConfig) + + // TODO: check wallet network + val netParam = NetworkParameters.fromID(NetworkParameters.ID_TESTNET) + val scripthash: String = Util.scripthash(netParam, coin.aggregatedAddress) + + val responseEntries: List = + electrumClient.blockchainScripthashListUnspent(scripthash) + + var utxo: BlockchainScripthashListUnspentResponseEntry? = null; + + responseEntries.forEach { entry -> + if (entry.value == coin.amount!!.toLong()) { + utxo = entry + } + } + + if (utxo == null) { + return null + } + + if (utxo!!.height == 0.toLong() && coin.status == CoinStatus.IN_MEMPOOL) { + return null + } + + val blockHeader = electrumClient.blockchainHeadersSubscribe() + val blockheight = blockHeader.height.toUInt(); + + electrumClient.closeConnection() + + var depositResult: DepositResult? = null + + if (coin.status == CoinStatus.INITIALISED) { + val utxoTxid = utxo!!.txHash; + val utxoVout = utxo!!.txPos; + + val backupTx = createTx1(coin, clientConfig, walletNetwork, utxoTxid, utxoVout.toUInt()) + + val activityUtxo = "${utxo!!.txHash}:${utxo!!.txPos}" + + val activity = createActivity(activityUtxo, utxo!!.value.toUInt(), "deposit") + + depositResult = DepositResult(activity, backupTx) + } + + if (utxo!!.height > 0) { + val confirmations = blockheight - utxo!!.height.toUInt() + 1u + + coin.status = CoinStatus.UNCONFIRMED + + if (confirmations >= clientConfig.confirmationTarget.toUInt()) { + coin.status = CoinStatus.CONFIRMED + } + } + + return depositResult + } + + private suspend fun checkTransfer(coin: Coin, clientConfig: ClientConfig): Boolean { + + if (coin.statechainId == null) { + throw Exception("The coin with the aggregated address ${coin.aggregatedAddress} does not have a statechain ID"); + } + + val statechainInfo = getStatechainInfo(clientConfig, coin.statechainId!!) ?: return true + + val enclavePublicKey = statechainInfo.enclavePublicKey + + val isTransferred = !isEnclavePubkeyPartOfCoin(coin, enclavePublicKey) + + return isTransferred + } + + private fun checkWithdrawal(coin: Coin, clientConfig: ClientConfig, walletNetwork: String): Boolean { + + var txid: String? = null + + if (coin.txWithdraw != null) { + txid = coin.txWithdraw + } + + if (coin.txCpfp != null) { + if (txid != null) { + throw Exception("Coin ${coin.aggregatedAddress} has both txWithdraw and txCpfp") + } + txid = coin.txCpfp + } + + if (txid == null) { + throw Exception("Coin ${coin.aggregatedAddress} has neither txWithdraw nor txCpfp") + } + + if (coin.withdrawalAddress == null) { + throw Exception("Coin ${coin.aggregatedAddress} has no withdrawalAddress") + } + + val electrumClient = getElectrumClient(clientConfig) + + // TODO: check wallet network + val netParam = NetworkParameters.fromID(NetworkParameters.ID_TESTNET) + val scripthash: String = Util.scripthash(netParam, coin.withdrawalAddress!!) + + val responseEntries: List = + electrumClient.blockchainScripthashListUnspent(scripthash) + + var utxo: BlockchainScripthashListUnspentResponseEntry? = null; + + responseEntries.forEach { entry -> + if (entry.txHash == txid) { + utxo = entry + } + } + + if (utxo == null) { + return false + } + + val blockHeader = electrumClient.blockchainHeadersSubscribe() + val blockheight = blockHeader.height.toUInt() + + electrumClient.closeConnection() + + if (utxo!!.height > 0) { + val confirmations = blockheight - utxo!!.height.toUInt() + 1u + return confirmations >= clientConfig.confirmationTarget.toUInt() + } + + return false + } + + suspend fun execute(wallet: Wallet, appContext: AppContext) { + + val clientConfig = appContext.clientConfig + val sqliteManager = appContext.sqliteManager + + // testElect(clientConfig) + + wallet.coins.forEach { coin -> + if (coin.status == CoinStatus.INITIALISED || coin.status == CoinStatus.IN_MEMPOOL || coin.status == CoinStatus.UNCONFIRMED) { + val depositResult = checkDeposit(coin, clientConfig, wallet.network) + + if (depositResult != null) { + wallet.activities = wallet.activities.plus(depositResult.activity) + sqliteManager.insertBackupTxs(coin.statechainId!!, listOf(depositResult.backupTx)) + } + } + else if (coin.status == CoinStatus.IN_TRANSFER) { + val isTransferred = checkTransfer(coin, clientConfig) + + if (isTransferred) { + coin.status = CoinStatus.TRANSFERRED; + } + } + else if (coin.status == CoinStatus.WITHDRAWING) { + val isWithdrawn = checkWithdrawal(coin, clientConfig, wallet.network) + + if (isWithdrawn) { + coin.status = CoinStatus.WITHDRAWN; + } + } + } + + sqliteManager.updateWallet(wallet) + } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/CreateWallet.kt b/clients/apps/kotlin/src/main/kotlin/CreateWallet.kt new file mode 100644 index 00000000..d5aa85a1 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/CreateWallet.kt @@ -0,0 +1,100 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +class CreateWallet : CliktCommand(help = "Creates a new wallet") { + private val walletName: String by argument(help = "Name of the wallet to create") + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + override fun run() { + + val clientConfig = appContext.clientConfig; + val sqliteManager = appContext.sqliteManager; + + var mnemonic: String? = null + + try { + mnemonic = generateMnemonic() + } catch (e: MercuryException) { + println("Failed to generate mnemonic: ${e.message}") + } + + var infoConfig: InfoConfig? = null + + // TODO: not recommend for production. Change to use CoroutineScope or another approach + runBlocking { + infoConfig = getInfoConfig(clientConfig) + } + + if (infoConfig == null) { + println("ERROR: infoConfig is null.") + return + } + + val electrumClient = getElectrumClient(clientConfig) + + val blockHeader = electrumClient.blockchainHeadersSubscribe() + val blockheight = blockHeader.height.toUInt(); + + electrumClient.closeConnection() + + val notifications = false; + val tutorials = false; + + val (electrumProtocol, electrumHost, electrumPort) = splitUrl(clientConfig.electrumServer) + + val settings = Settings( + clientConfig.network, + null, + null, + null, + null, + null, + clientConfig.statechainEntity, + null, + electrumProtocol, + electrumHost, + electrumPort.toString(), + clientConfig.electrumType, + notifications, + tutorials + ) + + val mutableTokenList: MutableList = mutableListOf() + val mutableActivityList: MutableList = mutableListOf() + val mutableCoinList: MutableList = mutableListOf() + + val wallet = Wallet( + walletName, + mnemonic!!, + "0.0.1", + clientConfig.statechainEntity, + clientConfig.electrumServer, + clientConfig.network, + blockheight, + infoConfig!!.initlock, + infoConfig!!.interval, + mutableTokenList, + mutableActivityList, + mutableCoinList, + settings + ) + + sqliteManager.insertWallet(wallet); + + val json = buildJsonObject { + put("result", "Wallet '$walletName' has been created successfully.") + } + + println(Json.encodeToString(json)) + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/Deposit.kt b/clients/apps/kotlin/src/main/kotlin/Deposit.kt new file mode 100644 index 00000000..9875f7a2 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/Deposit.kt @@ -0,0 +1,122 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.convert +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject + +class Deposit: CliktCommand(help = "Generates a new deposit address", name = "new-deposit-address") { + + private val walletName: String by argument(help = "Name of the wallet to create") + + private val amount: UInt by argument(help = "Statecoin amount").convert { it.toUInt() } + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + private suspend fun getTokenFromServer() : Token { + val endpoint = "tokens/token_init" + + val clientConfig = appContext.clientConfig; + + val url = clientConfig.statechainEntity + "/" + endpoint; + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val token: Token = httpClient.get(url).body() + + httpClient.close() + + return token + } + + private suspend fun addTokenToWallet(wallet: Wallet) { + val token = getTokenFromServer() + token.confirmed = true + wallet.tokens = wallet.tokens.plus(token) + + appContext.sqliteManager.updateWallet(wallet) + } + + private suspend fun execute(wallet: Wallet, token: Token, amount: UInt) { + + val coin = getNewCoin(wallet) + wallet.coins = wallet.coins.plus(coin) + + appContext.sqliteManager.updateWallet(wallet) + + val depositMsg1 = createDepositMsg1(coin, token.tokenId) + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val url = appContext.clientConfig.statechainEntity + "/" + "deposit/init/pod" + + val depositMsg1Response: DepositMsg1Response = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(depositMsg1) + }.body() + + httpClient.close() + + val depositInitResult = handleDepositMsg1Response(coin, depositMsg1Response) + + coin.statechainId = depositInitResult.statechainId; + coin.signedStatechainId = depositInitResult.signedStatechainId; + coin.serverPubkey = depositInitResult.serverPubkey; + + val aggregatedPublicKey = createAggregatedAddress(coin, wallet.network); + + coin.amount = amount + coin.aggregatedAddress = aggregatedPublicKey.aggregateAddress; + coin.aggregatedPubkey = aggregatedPublicKey.aggregatePubkey; + + token.spent = true + + appContext.sqliteManager.updateWallet(wallet) + + val json = buildJsonObject { + put("deposit_address", coin.aggregatedAddress!!) + put("statechain_id", coin.statechainId!!) + } + + println(Json.encodeToString(json)) + } + + override fun run() { + val wallet = appContext.sqliteManager.loadWallet(walletName) + + runBlocking { + addTokenToWallet(wallet) + + val foundToken = wallet.tokens.find { token -> token.confirmed && !token.spent } + + if (foundToken == null) { + throw Exception("There is no token available") + } + + execute(wallet, foundToken, amount) + } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/ListStatecoins.kt b/clients/apps/kotlin/src/main/kotlin/ListStatecoins.kt new file mode 100644 index 00000000..45315d24 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/ListStatecoins.kt @@ -0,0 +1,43 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.* + +class ListStatecoins: CliktCommand(help = "List all wallet' statecoins") { + + private val walletName: String by argument(help = "Name of the wallet to create") + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + override fun run() { + + // TODO: not recommend for production. Change to use CoroutineScope or another approach + runBlocking { + val wallet = appContext.sqliteManager.loadWallet(walletName) + + CoinUpdate.execute(wallet, appContext) + + val resultJson = buildJsonObject { + putJsonArray("coins") { + wallet.coins.forEach { coin -> + add(buildJsonObject { + put("statechain_id", coin.statechainId ?: "Empty") + put("amount", coin.amount.toString()) + put("status", coin.status.toString()) + put("deposit_address", coin.aggregatedAddress ?: "Empty") + put("statechain_address", coin.address ?: "Empty") + put("locktime", coin.locktime.toString()) + }) + } + } + } + + val prettyJsonString = Json { prettyPrint = true }.encodeToString(JsonObject.serializer(), resultJson) + println(prettyJsonString) + } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/Main.kt b/clients/apps/kotlin/src/main/kotlin/Main.kt new file mode 100644 index 00000000..bb956be8 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/Main.kt @@ -0,0 +1,188 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.context +import com.github.ajalt.clikt.core.subcommands + +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import org.electrumj.ElectrumClient +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +suspend fun getStatechainInfo(clientConfig: ClientConfig, statechainId: String): StatechainInfoResponsePayload? { + val url = "${clientConfig.statechainEntity}/info/statechain/${statechainId}" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val response = httpClient.get(url) + + if (response.status == HttpStatusCode.NotFound) { + return null + } + + val payload: StatechainInfoResponsePayload = response.body() + + httpClient.close() + + return payload +} + +suspend fun completeWithdraw(clientConfig: ClientConfig, statechainId: String, signedStatechainId: String) { + + val withdrawCompletePayload = WithdrawCompletePayload( + statechainId, + signedStatechainId + ) + + val url = "${clientConfig.statechainEntity}/withdraw/complete" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val response = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(withdrawCompletePayload) + } + + if (response.status != HttpStatusCode.OK) { + val errorBody: String = response.bodyAsText() + throw Exception("Failed to complete withdraw: HTTP ${response.status} - $errorBody") + } + + httpClient.close() +} + +fun createActivity(utxo: String, amount: UInt, action: String): Activity { + val date = ZonedDateTime.now() // This will get the current date and time in UTC + val isoString = date.format(DateTimeFormatter.ISO_ZONED_DATE_TIME) // Converts the date to an ISO 8601 string + + return Activity( + utxo = utxo, + amount = amount, + action = action, + date = isoString + ) +} + +fun splitUrl(electrumServerUrl: String): Triple { + val protocolEndIndex = electrumServerUrl.indexOf("://") + if (protocolEndIndex == -1) throw IllegalArgumentException("Invalid URL: Protocol delimiter not found") + + val protocol = electrumServerUrl.substring(0, protocolEndIndex) + val remainder = electrumServerUrl.substring(protocolEndIndex + 3) + + val colonIndex = remainder.lastIndexOf(':') + if (colonIndex == -1) throw IllegalArgumentException("Invalid URL: Port delimiter not found") + + val host = remainder.substring(0, colonIndex) + val port = remainder.substring(colonIndex + 1).toIntOrNull() + ?: throw IllegalArgumentException("Invalid URL: Port is not an integer") + + return Triple(protocol, host, port) +} + +fun getElectrumClient(clientConfig: ClientConfig): ElectrumClient{ + + val (protocol, host, port) = splitUrl(clientConfig.electrumServer) + + val electrumClient = ElectrumClient(host, port) + + var isSecure = false + + if (protocol == "ssl") { + isSecure = true + } + + electrumClient.openConnection(isSecure) + + return electrumClient; +} + +suspend fun getFeeRateSatsPerByte(clientConfig: ClientConfig): ULong { + val electrumClient = getElectrumClient(clientConfig) + + var feeRateBtcPerKb = electrumClient.blockchainEstimatefee(3) + + if (feeRateBtcPerKb <= 0.0) { + feeRateBtcPerKb = 0.00001 + } + + val feeRateSatsPerByte = (feeRateBtcPerKb * 100000.0).toULong() + + electrumClient.closeConnection() + + return feeRateSatsPerByte +} + +suspend fun getInfoConfig(clientConfig: ClientConfig): InfoConfig { + val endpoint = "info/config" + + // TODO: add support to Tor + + val url = clientConfig.statechainEntity + "/" + endpoint; + + // val client = HttpClient(CIO) + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val serverConfig: ServerConfig = httpClient.get(url).body() + + httpClient.close() + + val feeRateSatsPerByte = getFeeRateSatsPerByte(clientConfig) + + return InfoConfig( + serverConfig.initlock, + serverConfig.interval, + feeRateSatsPerByte + ) +} + +data class AppContext( + val clientConfig: ClientConfig, + val sqliteManager: SqliteManager +) + +//TIP To Run code, press or +// click the icon in the gutter. +class MainCommand : CliktCommand() { + private val clientConfig = ClientConfig() + private val sqliteManager = SqliteManager(clientConfig) + + init { + + context { + obj = AppContext(clientConfig, sqliteManager) + } + } + + override fun run() = Unit // Main command does nothing on its own +} + +fun main(args: Array) = MainCommand() + .subcommands( + CreateWallet(), + Deposit(), + ListStatecoins(), + Withdraw(), + BroadcastBackupTransaction(), + NewTransferAddress(), + TransferSend(), + TransferReceive() + ) + .main(args) diff --git a/clients/apps/kotlin/src/main/kotlin/NewTransferAddress.kt b/clients/apps/kotlin/src/main/kotlin/NewTransferAddress.kt new file mode 100644 index 00000000..30e1a08d --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/NewTransferAddress.kt @@ -0,0 +1,43 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import java.util.UUID + +class NewTransferAddress: CliktCommand(help = "New transfer address for a statecoin") { + + private val walletName: String by argument(help = "Name of the wallet to create") + + private val generateBatchId: Boolean by option("-b", "--generate-batch-id", + help = "Generate a batch ID for the transfer").flag(default = false) + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + override fun run() { + val wallet = appContext.sqliteManager.loadWallet(walletName) + + val coin = getNewCoin(wallet) + wallet.coins = wallet.coins.plus(coin) + + appContext.sqliteManager.updateWallet(wallet) + + val json = buildJsonObject { + put("transfer-address", coin.address) + if (generateBatchId) { + put("batch-id", UUID.randomUUID().toString()) + } + } + + val prettyJsonString = Json { prettyPrint = true }.encodeToString(JsonObject.serializer(), json) + println(prettyJsonString) + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/SqliteManager.kt b/clients/apps/kotlin/src/main/kotlin/SqliteManager.kt new file mode 100644 index 00000000..c233228e --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/SqliteManager.kt @@ -0,0 +1,124 @@ +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.Json +import java.sql.DriverManager + +class SqliteManager(clientConfig: ClientConfig) { + + private val databaseUrl = "jdbc:sqlite:" + clientConfig.databaseFile + + init { + createDatabase() + } + + private fun createDatabase() { + DriverManager.getConnection(databaseUrl).use { conn -> + conn.createStatement().use { statement -> + statement.execute("CREATE TABLE IF NOT EXISTS wallet (wallet_name TEXT NOT NULL UNIQUE, wallet_json TEXT NOT NULL)") + statement.execute("CREATE TABLE IF NOT EXISTS backup_txs (statechain_id TEXT NOT NULL, txs TEXT NOT NULL)") + } + } + } + + fun insertWallet(wallet: Wallet) { + + val walletJson = Json.encodeToString(Wallet.serializer(), wallet) + + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("INSERT INTO wallet (wallet_name, wallet_json) VALUES (?, ?)").use { statement -> + statement.setString(1, wallet.name) + statement.setString(2, walletJson) + statement.executeUpdate() + } + } + } + + fun loadWallet(walletName: String) : Wallet { + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("SELECT wallet_json FROM wallet WHERE wallet_name = ?").use { statement -> + statement.setString(1, walletName) + val rs = statement.executeQuery() + if (rs.next()) { + val walletJson = rs.getString("wallet_json"); + val wallet = Json.decodeFromString(Wallet.serializer(), walletJson) + return wallet + } else { + throw InternalException("Wallet $walletName not found !") + } + } + } + } + + fun updateWallet(wallet: Wallet) { + + val walletJson = Json.encodeToString(Wallet.serializer(), wallet) + + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("UPDATE wallet SET wallet_json = ? WHERE wallet_name = ?").use { statement -> + statement.setString(1, walletJson) + statement.setString(2, wallet.name) + statement.executeUpdate() + } + } + } + + fun insertBackupTxs(statechainId: String, listBackupTx: List) { + + val listBackupTxJson = Json.encodeToString(ListSerializer(BackupTx.serializer()), listBackupTx) + + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("INSERT INTO backup_txs (statechain_id, txs) VALUES (?, ?)").use { statement -> + statement.setString(1, statechainId) + statement.setString(2, listBackupTxJson) + statement.executeUpdate() + } + } + } + + fun getBackupTxs(statechainId: String): List { + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("SELECT txs FROM backup_txs WHERE statechain_id = ?").use { statement -> + statement.setString(1, statechainId) + val rs = statement.executeQuery() + if (rs.next()) { + val txsJson = rs.getString("txs"); + val backupTxs = Json.decodeFromString(ListSerializer(BackupTx.serializer()), txsJson) + return backupTxs + } else { + throw InternalException("StatechainId $statechainId not found !") + } + } + } + } + + fun updateBackupTxs(statechainId: String, listBackupTx: List) { + + val listBackupTxJson = Json.encodeToString(ListSerializer(BackupTx.serializer()), listBackupTx) + + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("UPDATE backup_txs SET txs = ? WHERE statechain_id = ?").use { statement -> + statement.setString(1, listBackupTxJson) + statement.setString(2, statechainId) + statement.executeUpdate() + } + } + } + + fun insertOrUpdateBackupTxs(statechainId: String, listBackupTx: List) { + + DriverManager.getConnection(databaseUrl).use { conn -> + conn.prepareStatement("DELETE FROM backup_txs WHERE statechain_id = ?").use { statement -> + statement.setString(1, statechainId) + statement.executeUpdate() + } + + val listBackupTxJson = Json.encodeToString(ListSerializer(BackupTx.serializer()), listBackupTx) + + conn.prepareStatement("INSERT INTO backup_txs (statechain_id, txs) VALUES (?, ?)").use { statement -> + statement.setString(1, statechainId) + statement.setString(2, listBackupTxJson) + statement.executeUpdate() + } + } + + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/Transaction.kt b/clients/apps/kotlin/src/main/kotlin/Transaction.kt new file mode 100644 index 00000000..83324aa3 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/Transaction.kt @@ -0,0 +1,127 @@ +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* + +class Transaction() { + + companion object { + + private suspend fun signFirst(clientConfig: ClientConfig, signFirstRequestPayload: SignFirstRequestPayload): String { + + val url = clientConfig.statechainEntity + "/" + "sign/first" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val signFirstResponsePayload: SignFirstResponsePayload = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(signFirstRequestPayload) + }.body() + + httpClient.close() + + var serverPubNonceHex = signFirstResponsePayload.serverPubnonce + + if (serverPubNonceHex.startsWith("0x")) { + serverPubNonceHex = serverPubNonceHex.substring(2) + } + + return serverPubNonceHex + } + + private suspend fun signSecond(clientConfig: ClientConfig, partialSigRequest: PartialSignatureRequestPayload) : String { + + val url = clientConfig.statechainEntity + "/" + "sign/second" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val partialSignatureResponsePayload: PartialSignatureResponsePayload = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(partialSigRequest) + }.body() + + httpClient.close() + + var partialSigHex = partialSignatureResponsePayload.partialSig + + if (partialSigHex.startsWith("0x")) { + partialSigHex = partialSigHex.substring(2) + } + + return partialSigHex + } + + suspend fun create( + coin: Coin, + clientConfig: ClientConfig, + blockHeight: UInt?, + qtBackupTx: UInt, + toAddress: String, + network: String, + isWithdrawal: Boolean, + feeRateSatsPerByte: UInt, + initlock: UInt, + interval: UInt + ) : String { + val coinNonce = createAndCommitNonces(coin) + coin.secretNonce = coinNonce.secretNonce + coin.publicNonce = coinNonce.publicNonce + coin.blindingFactor = coinNonce.blindingFactor + + coin.serverPublicNonce = signFirst(clientConfig, coinNonce.signFirstRequestPayload) + + var newBlockHeight: UInt = 0u + + if (blockHeight == null) { + val electrumClient = getElectrumClient(clientConfig) + + val blockHeader = electrumClient.blockchainHeadersSubscribe() + newBlockHeight = blockHeader.height.toUInt(); + + electrumClient.closeConnection() + } else { + newBlockHeight = blockHeight + } + + val partialSigRequest = getPartialSigRequest( + coin, + newBlockHeight, + initlock, + interval, + feeRateSatsPerByte, + qtBackupTx, + toAddress, + network, + isWithdrawal) + + val serverPartialSigRequest = partialSigRequest.partialSignatureRequestPayload + + val serverPartialSig = signSecond(clientConfig, serverPartialSigRequest) + + val clientPartialSig = partialSigRequest.clientPartialSig + val msg = partialSigRequest.msg + val session = partialSigRequest.encodedSession + val outputPubkey = partialSigRequest.outputPubkey + + val signature = createSignature(msg, clientPartialSig, serverPartialSig, session, outputPubkey) + + val encodedUnsignedTx = partialSigRequest.encodedUnsignedTx + + val signedTx = newBackupTransaction(encodedUnsignedTx, signature) + + return signedTx + } + } + +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/TransferReceive.kt b/clients/apps/kotlin/src/main/kotlin/TransferReceive.kt new file mode 100644 index 00000000..ecc41d0a --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/TransferReceive.kt @@ -0,0 +1,376 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.* +import org.bitcoinj.core.NetworkParameters +import org.electrumj.Util +import org.electrumj.dto.BlockchainScripthashListUnspentResponseEntry + +class TransferReceive: CliktCommand(help = "Retrieve coins from server") { + + private val walletName: String by argument(help = "Name of the wallet") + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + private suspend fun sendTransferReceiverRequestPayload( + transferReceiverRequestPayload: TransferReceiverRequestPayload) : String + { + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val url = "${appContext.clientConfig.statechainEntity}/transfer/receiver" + + while (true) { + val response = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(transferReceiverRequestPayload) + } + + if (response.status == HttpStatusCode.BadRequest) { + + val transferReceiverErrorResponsePayload : TransferReceiverErrorResponsePayload = response.body() + + if (transferReceiverErrorResponsePayload.code == TransferReceiverError.EXPIRED_BATCH_TIME_ERROR) { + throw Exception(transferReceiverErrorResponsePayload.message) + } else if (transferReceiverErrorResponsePayload.code == TransferReceiverError.STATECOIN_BATCH_LOCKED_ERROR) { + println("Statecoin batch still locked. Waiting until expiration or unlock.") + delay(5000) + continue + } + } + + if (response.status == HttpStatusCode.OK) { + val transferReceiverPostResponsePayload : TransferReceiverPostResponsePayload = response.body() + return transferReceiverPostResponsePayload.serverPubkey + } else { + throw Exception("Failed to update transfer message") + } + } + } + + private fun verifyTx0OutputIsUnspentAndConfirmed( + coin: Coin, tx0Outpoint: TxOutpoint, tx0Hex: String, walletNetwork: String) : Pair + { + val tx0outputAddress = getOutputAddressFromTx0(tx0Outpoint, tx0Hex, walletNetwork) + + val electrumClient = getElectrumClient(appContext.clientConfig) + + // TODO: check wallet network + val netParam = NetworkParameters.fromID(NetworkParameters.ID_TESTNET) + val scripthash: String = Util.scripthash(netParam, tx0outputAddress) + + val responseEntries: List = + electrumClient.blockchainScripthashListUnspent(scripthash) + + var status = CoinStatus.UNCONFIRMED; + + responseEntries.forEach { entry -> + if (entry.txHash == tx0Outpoint.txid && entry.txPos == tx0Outpoint.vout.toLong()) { + val blockHeader = electrumClient.blockchainHeadersSubscribe() + val blockheight = blockHeader.height + + val confirmations = blockheight - entry.height + 1 + + if (confirmations >= appContext.clientConfig.confirmationTarget) { + status = CoinStatus.CONFIRMED; + } + + return Pair(true, status) + } + } + + return Pair(false, status) + } + + private suspend fun unlockStatecoin(statechainId: String, signedStatechainId: String, authPubkey: String) { + val url = "${appContext.clientConfig.statechainEntity}/transfer/unlock" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val transferUnlockRequestPayload = TransferUnlockRequestPayload ( + statechainId, + signedStatechainId, + authPubkey + ) + + val response = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(transferUnlockRequestPayload) + } + + if (response.status != HttpStatusCode.OK) { + throw Exception("Failed to unlock transfer message") + } + + httpClient.close() + } + + private fun getTx0(tx0Txid: String) : String { + val electrumClient = getElectrumClient(appContext.clientConfig) + + var txHex = electrumClient.blockchainTransactionGetNoVerbose(tx0Txid); + + electrumClient.closeConnection() + + return txHex + } + + private suspend fun processEncryptedMessage( + coin: Coin, + encMessage: String, + network: String, + serverInfo: InfoConfig, + activities: MutableList) : String + { + + val clientAuthKey = coin.authPrivkey + val newUserPubkey = coin.userPubkey + + + val transferMsg = fiiDecryptTransferMsg(encMessage, clientAuthKey) + val tx0Outpoint = getTx0Outpoint(transferMsg.backupTransactions) + val tx0Hex = getTx0(tx0Outpoint.txid) + + val isTransferSignatureValid = ffiVerifyTransferSignature(newUserPubkey, tx0Outpoint, transferMsg) + + if (!isTransferSignatureValid) { + throw Exception("Invalid transfer signature") + } + + val statechainInfo = getStatechainInfo(appContext.clientConfig, transferMsg.statechainId) + + if (statechainInfo == null) { + throw Exception("Statechain info not found") + } + + val isTx0OutputPubkeyValid = fiiValidateTx0OutputPubkey(statechainInfo.enclavePublicKey, transferMsg, tx0Outpoint, tx0Hex, network) + + if (!isTx0OutputPubkeyValid) { + throw Exception("Invalid tx0 output pubkey") + } + + val latestBackupTxPaysToUserPubkey = fiiVerifyLatestBackupTxPaysToUserPubkey(transferMsg, newUserPubkey, network) + + if (!latestBackupTxPaysToUserPubkey) { + throw Exception("Latest Backup Tx does not pay to the expected public key") + } + + if (statechainInfo.numSigs.toInt() != transferMsg.backupTransactions.size) { + throw Exception("num_sigs is not correct") + } + + val isTx0OutputUnspent = verifyTx0OutputIsUnspentAndConfirmed(coin, tx0Outpoint, tx0Hex, network); + if (!isTx0OutputUnspent.first) { + throw Exception("tx0 output is spent or not confirmed") + } + + val currentFeeRateSatsPerByte: UInt = if (serverInfo.feeRateSatsPerByte > appContext.clientConfig.maxFeeRate.toUInt()) { + appContext.clientConfig.maxFeeRate.toUInt() + } else { + serverInfo.feeRateSatsPerByte.toUInt() + } + + val feeRateTolerance = appContext.clientConfig.feeRateTolerance.toUInt() + + val previousLockTime: UInt = ffiValidateSignatureScheme( + transferMsg, + statechainInfo, + tx0Hex, + feeRateTolerance, + currentFeeRateSatsPerByte, + serverInfo.interval + ) + + val transferReceiverRequestPayload = fiiCreateTransferReceiverRequestPayload(statechainInfo, transferMsg, coin) + + val signedStatechainIdForUnlock = signMessage(transferMsg.statechainId, coin) + + unlockStatecoin(transferMsg.statechainId, signedStatechainIdForUnlock, coin.authPubkey) + + var serverPublicKeyHex = "" + + try { + serverPublicKeyHex = sendTransferReceiverRequestPayload(transferReceiverRequestPayload) + } catch (e: Exception) { + throw Exception("Error: ${e.message}") + } + + val newKeyInfo = getNewKeyInfo(serverPublicKeyHex, coin, transferMsg.statechainId, tx0Outpoint, tx0Hex, network) + + coin.serverPubkey = serverPublicKeyHex + coin.aggregatedPubkey = newKeyInfo.aggregatePubkey + coin.aggregatedAddress = newKeyInfo.aggregateAddress + coin.statechainId = transferMsg.statechainId + coin.signedStatechainId = newKeyInfo.signedStatechainId + coin.amount = newKeyInfo.amount + coin.utxoTxid = tx0Outpoint.txid + coin.utxoVout = tx0Outpoint.vout + coin.locktime = previousLockTime + coin.status = isTx0OutputUnspent.second + + val utxo = "${tx0Outpoint.txid}:${tx0Outpoint.vout}" + + val activity = createActivity(utxo, newKeyInfo.amount, "Receive") + activities.add(activity) + + appContext.sqliteManager.insertOrUpdateBackupTxs(transferMsg.statechainId, transferMsg.backupTransactions) + + + return transferMsg.statechainId + } + + private suspend fun getMsgAddr(authPubkey: String) : List { + val url = "${appContext.clientConfig.statechainEntity}/transfer/get_msg_addr/${authPubkey}" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val response: GetMsgAddrResponsePayload = httpClient.get(url).body(); + + httpClient.close() + + return response.listEncTransferMsg + } + + private suspend fun execute() { + + val wallet = appContext.sqliteManager.loadWallet(walletName) + + CoinUpdate.execute(wallet, appContext) + + + + val infoConfig = getInfoConfig(appContext.clientConfig) + + val uniqueAuthPubkeys = mutableSetOf() + + wallet.coins.forEach { coin -> + uniqueAuthPubkeys.add(coin.authPubkey) + } + + val encMsgsPerAuthPubkey = mutableMapOf>() + + for (authPubkey in uniqueAuthPubkeys) { + try { + val encMessages = getMsgAddr(authPubkey) + if (encMessages.isEmpty()) { + println("No messages") + continue + } + + encMsgsPerAuthPubkey[authPubkey] = encMessages + } catch (err: Exception) { + err.printStackTrace() + } + } + + val receivedStatechainIds = mutableListOf() + + val tempCoins = wallet.coins.toMutableList() + val tempActivities = wallet.activities.toMutableList() + + for ((authPubkey, encMessages) in encMsgsPerAuthPubkey) { + for (encMessage in encMessages) { + val coin = tempCoins.find { it.authPubkey == authPubkey && it.status == CoinStatus.INITIALISED } + + if (coin != null) { + try { + val statechainIdAdded = processEncryptedMessage(coin, encMessage, wallet.network, infoConfig, tempActivities) + if (statechainIdAdded != null) { + receivedStatechainIds.add(statechainIdAdded) + } + } catch (error: Exception) { + println("Error: ${error.message}") + continue + } + } else { + try { + val newCoin = duplicateCoinToInitializedState(wallet, authPubkey) + if (newCoin != null) { + val statechainIdAdded = processEncryptedMessage(newCoin, encMessage, wallet.network, infoConfig, tempActivities) + if (statechainIdAdded != null) { + tempCoins.add(newCoin) + receivedStatechainIds.add(statechainIdAdded) + } + } + } catch (error: Exception) { + println("Error: ${error.message}") + continue + } + } + } + } + + wallet.coins = tempCoins + wallet.activities = tempActivities + + + + + + /* + + wallet.coins.forEach { coin -> + if (coin.status != CoinStatus.INITIALISED) { + return@forEach // Continue in Kotlin's forEach + } + + // Log information - assuming use of a logging library or println for simplicity +// println("----\nuser_pubkey: ${coin.userPubkey}") +// println("auth_pubkey: ${coin.authPubkey}") +// println("statechain_id: ${coin.statechainId}") +// println("coin.amount: ${coin.amount}") +// println("coin.status: ${coin.status}") + + val encMessages = getMsgAddr(coin.authPubkey) + + if (encMessages.isEmpty()) { + return@forEach // Continue in Kotlin's forEach + } + + val statechainIdsAdded = processEncryptedMessage(coin, encMessages, infoConfig, wallet) + receivedStatechainIds.addAll(statechainIdsAdded) + } + + + */ + + appContext.sqliteManager.updateWallet(wallet) + + val json = buildJsonObject { + putJsonArray("receivedStatechainIds") { + receivedStatechainIds.forEach{ id -> add(id) } + } + } + + println(Json.encodeToString(json)) + + } + override fun run() { + runBlocking { execute() } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/TransferSend.kt b/clients/apps/kotlin/src/main/kotlin/TransferSend.kt new file mode 100644 index 00000000..09abbe76 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/TransferSend.kt @@ -0,0 +1,211 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.options.option +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +class TransferSend: CliktCommand(help = "Send the specified coin to an statechain address") { + + private val walletName: String by argument(help = "Name of the wallet") + + private val statechainId: String by argument(help = "Statechain id") + + private val toAddress: String by argument(help = "Address to send the funds") + + private val batchId: String? by option("-b", "--generate-batch-id", help = "Optional batch ID for the transaction") + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + private suspend fun getNewX1(statechainId: String, signedStatechainId: String, newAuthPubkey: String, batchId: String?): String { + + val transferSenderRequestPayload = TransferSenderRequestPayload( + statechainId, + signedStatechainId, + newAuthPubkey, + batchId + ) + + val url = "${appContext.clientConfig.statechainEntity}/transfer/sender" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val transferSenderResponsePayload: TransferSenderResponsePayload = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(transferSenderRequestPayload) + }.body() + + httpClient.close() + + return transferSenderResponsePayload.x1 + } + + private suspend fun execute() { + val wallet = appContext.sqliteManager.loadWallet(walletName) + + CoinUpdate.execute(wallet, appContext) + + if (!validateAddress(toAddress, wallet.network)) { + throw Exception("Invalid address") + } + + var backupTxs = appContext.sqliteManager.getBackupTxs(statechainId) + + if (backupTxs.isEmpty()) { + throw Exception("There is no backup transaction for the statechain id $statechainId") + } + + val newTxN = backupTxs.count() + 1 + + val coinsWithStatechainId = wallet.coins.filter { c -> c.statechainId == statechainId } + + if (coinsWithStatechainId.isEmpty()) { + throw Exception("There is no coin for the statechain id $statechainId") + } + + // If the user sends to himself, he will have two coins with the same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + val coin = coinsWithStatechainId.minByOrNull { it.locktime!! } + ?: throw Exception("No coins available after sorting by locktime") + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw Exception("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}") + } + + if (coin.locktime == null) { + throw Exception("Coin.locktime is null"); + } + + val electrumClient = getElectrumClient(appContext.clientConfig) + + val blockHeader = electrumClient.blockchainHeadersSubscribe() + val currentBlockheight = blockHeader.height.toUInt() + + electrumClient.closeConnection() + + if (currentBlockheight > coin.locktime!!) { + throw Exception("The coin is expired. Coin locktime is ${coin.locktime} and current blockheight is $currentBlockheight"); + } + + val statechainId = coin.statechainId + val signedStatechainId = coin.signedStatechainId + + val isWithdrawal = false + val qtBackupTx = backupTxs.size + + // Sorting backup transactions by tx_n in ascending order + backupTxs = backupTxs.sortedBy { it.txN } + + val bkpTx1 = backupTxs.firstOrNull() ?: throw Exception("No backup transactions available") + + val blockHeight = getBlockheight(bkpTx1) + + val decodedTransferAddress = decodeStatechainAddress(toAddress) + val newAuthPubkey = decodedTransferAddress.authPubkey + + val newX1 = getNewX1(statechainId!!, signedStatechainId!!, newAuthPubkey, batchId) + + val infoConfig = getInfoConfig(appContext.clientConfig) + + val initlock = infoConfig.initlock; + val interval = infoConfig.interval; + + val feeRateSatsPerByte: UInt = if (infoConfig.feeRateSatsPerByte > appContext.clientConfig.maxFeeRate.toUInt()) { + appContext.clientConfig.maxFeeRate.toUInt() + } else { + infoConfig.feeRateSatsPerByte.toUInt() + } + + val signedTx = Transaction.create( + coin, + appContext.clientConfig, + blockHeight, + qtBackupTx.toUInt(), + toAddress, + wallet.network, + false, + feeRateSatsPerByte, + initlock, + interval + ) + + val backupTx = BackupTx( + txN = newTxN.toUInt(), + tx = signedTx, + clientPublicNonce = coin.publicNonce ?: throw Exception("publicNonce is null"), + serverPublicNonce = coin.serverPublicNonce ?: throw Exception("serverPublicNonce is null"), + clientPublicKey = coin.userPubkey, + serverPublicKey = coin.serverPubkey ?: throw Exception("serverPubkey is null"), + blindingFactor = coin.blindingFactor ?: throw Exception("blindingFactor is null") + ) + + backupTxs = backupTxs.plus(backupTx) + + val inputTxid = coin.utxoTxid!! + val inputVout = coin.utxoVout!! + val clientSeckey = coin.userPrivkey + val recipientAddress = toAddress + + val transferSignature = createTransferSignature(recipientAddress, inputTxid, inputVout, clientSeckey); + + val transferUpdateMsgRequestPayload = createTransferUpdateMsg(newX1, recipientAddress, coin, transferSignature, backupTxs) + + val url = "${appContext.clientConfig.statechainEntity}/transfer/update_msg" + + val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } + } + + val response = httpClient.post(url) { + contentType(ContentType.Application.Json) + setBody(transferUpdateMsgRequestPayload) + } + + if (response.status != HttpStatusCode.OK) { + throw Exception("Failed to update transfer message") + } + + httpClient.close() + + appContext.sqliteManager.updateBackupTxs(coin.statechainId!!, backupTxs) + + val activityUtxo = "${inputTxid}:${inputVout}" + + val activity = createActivity(activityUtxo, coin.amount!!, "Transfer") + + wallet.activities = wallet.activities.plus(activity) + coin.status = CoinStatus.IN_TRANSFER + + appContext.sqliteManager.updateWallet(wallet) + + val json = buildJsonObject { + put("status", "Transfer sent") + } + + println(Json.encodeToString(json)) + } + + override fun run() { + runBlocking { execute() } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/Withdraw.kt b/clients/apps/kotlin/src/main/kotlin/Withdraw.kt new file mode 100644 index 00000000..cc7efad4 --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/Withdraw.kt @@ -0,0 +1,143 @@ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.convert +import com.github.ajalt.clikt.parameters.arguments.optional +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +class Withdraw: CliktCommand(help = "Withdraw funds from a statecoin to a BTC address") { + + private val walletName: String by argument(help = "Name of the wallet") + + private val statechainId: String by argument(help = "Statechain id") + + private val toAddress: String by argument(help = "Address to send the funds") + + private val feeRate: UInt? by argument(help = "Fee Rate").convert { it.toUInt() }.optional() + + private val appContext: AppContext by lazy { + requireNotNull(currentContext.findObject() as? AppContext) { + "ClientConfig not found in context" + } + } + + private suspend fun execute() { + val wallet = appContext.sqliteManager.loadWallet(walletName) + + CoinUpdate.execute(wallet, appContext) + + if (!validateAddress(toAddress, wallet.network)) { + throw Exception("Invalid address") + } + + var backupTxs = appContext.sqliteManager.getBackupTxs(statechainId) + + if (backupTxs.isEmpty()) { + throw Exception("No backup transaction associated with this statechain ID were found") + } + + val qtBackupTx = backupTxs.count() + + val newTxN = qtBackupTx + 1 + + val coin = wallet.coins + .filter { tx -> tx.statechainId == statechainId.toString() } // Filter coins with the specified statechainId + .minByOrNull { tx -> tx.locktime!! } // Find the one with the lowest locktime + + if (coin == null) { + throw Exception("No coins associated with this statechain ID were found") + } + + if (coin.amount == null) { + throw Exception("coin.amount is None") + } + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw Exception("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}"); + } + + val infoConfig = getInfoConfig(appContext.clientConfig) + + var feeRateSatsPerByte: UInt = 0u + + if (feeRate == null) { + feeRateSatsPerByte = if (infoConfig.feeRateSatsPerByte > appContext.clientConfig.maxFeeRate.toUInt()) { + appContext.clientConfig.maxFeeRate.toUInt() + } else { + infoConfig.feeRateSatsPerByte.toUInt() + } + } else { + feeRateSatsPerByte = feeRate as UInt + } + + val signedTx = Transaction.create( + coin, + appContext.clientConfig, + null, + qtBackupTx.toUInt(), + toAddress, + wallet.network, + false, + feeRateSatsPerByte, + infoConfig.initlock, + infoConfig.interval + ) + + if (coin.publicNonce == null) { + throw Exception("coin.publicNonce is None") + } + + if (coin.blindingFactor == null) { + throw Exception("coin.blindingFactor is None") + } + + if (coin.statechainId == null) { + throw Exception("coin.statechainId is None") + } + + val backupTx = BackupTx( + txN = newTxN.toUInt(), + tx = signedTx, + clientPublicNonce = coin.publicNonce ?: throw Exception("publicNonce is null"), + serverPublicNonce = coin.serverPublicNonce ?: throw Exception("serverPublicNonce is null"), + clientPublicKey = coin.userPubkey, + serverPublicKey = coin.serverPubkey ?: throw Exception("serverPubkey is null"), + blindingFactor = coin.blindingFactor ?: throw Exception("blindingFactor is null") + ) + + backupTxs = backupTxs.plus(backupTx) + + appContext.sqliteManager.updateBackupTxs(coin.statechainId!!, backupTxs) + + val electrumClient = getElectrumClient(appContext.clientConfig) + + val txId = electrumClient.blockchainTransactionBroadcast(signedTx) + + electrumClient.closeConnection() + + coin.txWithdraw = txId + coin.withdrawalAddress = toAddress + coin.status = CoinStatus.WITHDRAWING + + val activity = createActivity(txId, coin.amount!!, "Withdraw") + + wallet.activities = wallet.activities.plus(activity) + + appContext.sqliteManager.updateWallet(wallet) + + completeWithdraw(appContext.clientConfig, coin.statechainId!!, coin.signedStatechainId!!) + + val json = buildJsonObject { + put("txId", txId) + } + + println(Json.encodeToString(json)) + } + + override fun run() { + runBlocking { execute() } + } +} \ No newline at end of file diff --git a/clients/apps/kotlin/src/main/kotlin/mercurylib.kt b/clients/apps/kotlin/src/main/kotlin/mercurylib.kt new file mode 100644 index 00000000..41e4238e --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlin/mercurylib.kt @@ -0,0 +1,4611 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Callback +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.ptr.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicLong + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + + @JvmField var len: Long = 0 + + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + + class ByReference : RustBuffer(), Structure.ByReference + + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + + companion object { + internal fun alloc(size: ULong = 0UL) = + uniffiRustCall { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.ffi_mercurylib_rustbuffer_alloc(size.toLong(), status) + }.also { + if (it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=$size)") + } + } + + internal fun create( + capacity: ULong, + len: ULong, + data: Pointer?, + ): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity.toLong() + buf.len = len.toLong() + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = + uniffiRustCall { status -> + UniffiLib.INSTANCE.ffi_mercurylib_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} + +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): ULong + + // Write a Kotlin type to a `ByteBuffer` + fun write( + value: KotlinType, + buf: ByteBuffer, + ) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = + rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position().toLong()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer : FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + +@Structure.FieldOrder("code", "error_buf") +internal open class UniffiRustCallStatus : Structure() { + @JvmField var code: Byte = 0 + + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue : UniffiRustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == UNIFFI_CALL_SUCCESS + } + + fun isError(): Boolean { + return code == UNIFFI_CALL_ERROR + } + + fun isPanic(): Boolean { + return code == UNIFFI_CALL_UNEXPECTED_ERROR + } + + companion object { + fun create( + code: Byte, + errorBuf: RustBuffer.ByValue, + ): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface UniffiRustCallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun uniffiRustCallWithError( + errorHandler: UniffiRustCallStatusErrorHandler, + callback: (UniffiRustCallStatus) -> U, +): U { + var status = UniffiRustCallStatus() + val return_value = callback(status) + uniffiCheckCallStatus(errorHandler, status) + return return_value +} + +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun uniffiCheckCallStatus( + errorHandler: UniffiRustCallStatusErrorHandler, + status: UniffiRustCallStatus, +) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object UniffiNullRustCallStatusErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { + return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback) +} + +internal inline fun uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch (e: Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } +} + +internal inline fun uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue, +) { + try { + writeReturn(makeCall()) + } catch (e: Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } + } +} + +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap { + private val map = ConcurrentHashMap() + private val counter = java.util.concurrent.atomic.AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T { + return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + } + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T { + return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") + } +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "mercurylib" +} + +private inline fun loadIndirect(componentName: String): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// Define FFI callback types +internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback { + fun callback( + `data`: Long, + `pollResult`: Byte, + ) +} + +internal interface UniffiForeignFutureFree : com.sun.jna.Callback { + fun callback(`handle`: Long) +} + +internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback { + fun callback(`handle`: Long) +} + +@Structure.FieldOrder("handle", "free") +internal open class UniffiForeignFuture( + @JvmField internal var `handle`: Long = 0.toLong(), + @JvmField internal var `free`: UniffiForeignFutureFree? = null, +) : Structure() { + class UniffiByValue( + `handle`: Long = 0.toLong(), + `free`: UniffiForeignFutureFree? = null, + ) : UniffiForeignFuture(`handle`, `free`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFuture) { + `handle` = other.`handle` + `free` = other.`free` + } +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU8(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU8.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI8(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI8.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU16(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU16.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI16(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI16.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU32(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI32(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU64(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI64(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF32( + @JvmField internal var `returnValue`: Float = 0.0f, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Float = 0.0f, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructF32(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructF32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF64( + @JvmField internal var `returnValue`: Double = 0.0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Double = 0.0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructF64(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructF64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructPointer( + @JvmField internal var `returnValue`: Pointer = Pointer.NULL, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Pointer = Pointer.NULL, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructPointer(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructPointer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompletePointer : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructPointer.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructRustBuffer( + @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructRustBuffer(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructRustBuffer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructRustBuffer.UniffiByValue, + ) +} + +@Structure.FieldOrder("callStatus") +internal open class UniffiForeignFutureStructVoid( + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructVoid(`callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructVoid) { + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructVoid.UniffiByValue, + ) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface UniffiLib : Library { + companion object { + internal val INSTANCE: UniffiLib by lazy { + loadIndirect(componentName = "mercurylib") + .also { lib: UniffiLib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + } + } + } + + fun uniffi_mercurylib_fn_func_create_aggregated_address( + `coin`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_and_commit_nonces( + `coin`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_cpfp_tx( + `backupTx`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `toAddress`: RustBuffer.ByValue, + `feeRateSatsPerByte`: Long, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_deposit_msg1( + `coin`: RustBuffer.ByValue, + `tokenId`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_signature( + `msg`: RustBuffer.ByValue, + `clientPartialSigHex`: RustBuffer.ByValue, + `serverPartialSigHex`: RustBuffer.ByValue, + `sessionHex`: RustBuffer.ByValue, + `outputPubkeyHex`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_transfer_signature( + `recipientAddress`: RustBuffer.ByValue, + `inputTxid`: RustBuffer.ByValue, + `inputVout`: Int, + `clientSeckey`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_transfer_update_msg( + `x1`: RustBuffer.ByValue, + `recipientAddress`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `transferSignature`: RustBuffer.ByValue, + `backupTransactions`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_decode_statechain_address( + `scAddress`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_duplicate_coin_to_initialized_state( + `wallet`: RustBuffer.ByValue, + `authPubkey`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_ffi_validate_signature_scheme( + `ffiTransferMsg`: RustBuffer.ByValue, + `statechainInfo`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `feeRateTolerance`: Int, + `currentFeeRateSatsPerByte`: Int, + `interval`: Int, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun uniffi_mercurylib_fn_func_ffi_verify_transfer_signature( + `newUserPubkey`: RustBuffer.ByValue, + `tx0Outpoint`: RustBuffer.ByValue, + `ffiTransferMsg`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_fii_create_transfer_receiver_request_payload( + `statechainInfo`: RustBuffer.ByValue, + `ffiTransferMsg`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_fii_decrypt_transfer_msg( + `encryptedMessage`: RustBuffer.ByValue, + `privateKeyWif`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_fii_validate_tx0_output_pubkey( + `enclavePublicKey`: RustBuffer.ByValue, + `ffiTransferMsg`: RustBuffer.ByValue, + `tx0Outpoint`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_fii_verify_latest_backup_tx_pays_to_user_pubkey( + `ffiTransferMsg`: RustBuffer.ByValue, + `clientPubkeyShare`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_generate_mnemonic(uniffi_out_err: UniffiRustCallStatus): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_blockheight( + `bkpTx`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun uniffi_mercurylib_fn_func_get_new_coin( + `wallet`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_new_key_info( + `serverPublicKeyHex`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `statechainId`: RustBuffer.ByValue, + `tx0Outpoint`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_output_address_from_tx0( + `tx0Outpoint`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_partial_sig_request( + `coin`: RustBuffer.ByValue, + `blockHeight`: Int, + `initlock`: Int, + `interval`: Int, + `feeRateSatsPerByte`: Int, + `qtBackupTx`: Int, + `toAddress`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + `isWithdrawal`: Byte, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_tx0_outpoint( + `backupTransactions`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_user_backup_address( + `coin`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_handle_deposit_msg_1_response( + `coin`: RustBuffer.ByValue, + `depositMsg1Response`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_is_enclave_pubkey_part_of_coin( + `coin`: RustBuffer.ByValue, + `enclavePubkey`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_latest_backup_tx_pays_to_user_pubkey( + `backupTxs`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_new_backup_transaction( + `encodedUnsignedTx`: RustBuffer.ByValue, + `signatureHex`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_sign_message( + `message`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_validate_address( + `address`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_verify_blinded_musig_scheme( + `backupTx`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `statechainInfo`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_mercurylib_fn_func_verify_transaction_signature( + `txNHex`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `feeRateTolerance`: Int, + `currentFeeRateSatsPerByte`: Int, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun ffi_mercurylib_rustbuffer_alloc( + `size`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rustbuffer_from_bytes( + `bytes`: ForeignBytes.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rustbuffer_free( + `buf`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun ffi_mercurylib_rustbuffer_reserve( + `buf`: RustBuffer.ByValue, + `additional`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rust_future_poll_u8( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u8( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun ffi_mercurylib_rust_future_poll_i8( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i8( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun ffi_mercurylib_rust_future_poll_u16( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u16( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Short + + fun ffi_mercurylib_rust_future_poll_i16( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i16( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Short + + fun ffi_mercurylib_rust_future_poll_u32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun ffi_mercurylib_rust_future_poll_i32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun ffi_mercurylib_rust_future_poll_u64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Long + + fun ffi_mercurylib_rust_future_poll_i64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Long + + fun ffi_mercurylib_rust_future_poll_f32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_f32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_f32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_f32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Float + + fun ffi_mercurylib_rust_future_poll_f64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_f64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_f64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_f64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Double + + fun ffi_mercurylib_rust_future_poll_pointer( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_pointer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_pointer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_pointer( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun ffi_mercurylib_rust_future_poll_rust_buffer( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_rust_buffer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_rust_buffer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_rust_buffer( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rust_future_poll_void( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_void(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_void(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_void( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_mercurylib_checksum_func_create_aggregated_address(): Short + + fun uniffi_mercurylib_checksum_func_create_and_commit_nonces(): Short + + fun uniffi_mercurylib_checksum_func_create_cpfp_tx(): Short + + fun uniffi_mercurylib_checksum_func_create_deposit_msg1(): Short + + fun uniffi_mercurylib_checksum_func_create_signature(): Short + + fun uniffi_mercurylib_checksum_func_create_transfer_signature(): Short + + fun uniffi_mercurylib_checksum_func_create_transfer_update_msg(): Short + + fun uniffi_mercurylib_checksum_func_decode_statechain_address(): Short + + fun uniffi_mercurylib_checksum_func_duplicate_coin_to_initialized_state(): Short + + fun uniffi_mercurylib_checksum_func_ffi_validate_signature_scheme(): Short + + fun uniffi_mercurylib_checksum_func_ffi_verify_transfer_signature(): Short + + fun uniffi_mercurylib_checksum_func_fii_create_transfer_receiver_request_payload(): Short + + fun uniffi_mercurylib_checksum_func_fii_decrypt_transfer_msg(): Short + + fun uniffi_mercurylib_checksum_func_fii_validate_tx0_output_pubkey(): Short + + fun uniffi_mercurylib_checksum_func_fii_verify_latest_backup_tx_pays_to_user_pubkey(): Short + + fun uniffi_mercurylib_checksum_func_generate_mnemonic(): Short + + fun uniffi_mercurylib_checksum_func_get_blockheight(): Short + + fun uniffi_mercurylib_checksum_func_get_new_coin(): Short + + fun uniffi_mercurylib_checksum_func_get_new_key_info(): Short + + fun uniffi_mercurylib_checksum_func_get_output_address_from_tx0(): Short + + fun uniffi_mercurylib_checksum_func_get_partial_sig_request(): Short + + fun uniffi_mercurylib_checksum_func_get_tx0_outpoint(): Short + + fun uniffi_mercurylib_checksum_func_get_user_backup_address(): Short + + fun uniffi_mercurylib_checksum_func_handle_deposit_msg_1_response(): Short + + fun uniffi_mercurylib_checksum_func_is_enclave_pubkey_part_of_coin(): Short + + fun uniffi_mercurylib_checksum_func_latest_backup_tx_pays_to_user_pubkey(): Short + + fun uniffi_mercurylib_checksum_func_new_backup_transaction(): Short + + fun uniffi_mercurylib_checksum_func_sign_message(): Short + + fun uniffi_mercurylib_checksum_func_validate_address(): Short + + fun uniffi_mercurylib_checksum_func_verify_blinded_musig_scheme(): Short + + fun uniffi_mercurylib_checksum_func_verify_transaction_signature(): Short + + fun ffi_mercurylib_uniffi_contract_version(): Int +} + +private fun uniffiCheckContractApiVersion(lib: UniffiLib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 26 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_mercurylib_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: UniffiLib) { + if (lib.uniffi_mercurylib_checksum_func_create_aggregated_address() != 44269.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_and_commit_nonces() != 16584.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_cpfp_tx() != 63811.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_deposit_msg1() != 9767.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_signature() != 53021.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_transfer_signature() != 61677.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_transfer_update_msg() != 6918.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_decode_statechain_address() != 7125.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_duplicate_coin_to_initialized_state() != 30591.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_ffi_validate_signature_scheme() != 26248.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_ffi_verify_transfer_signature() != 18534.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_create_transfer_receiver_request_payload() != 58308.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_decrypt_transfer_msg() != 44515.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_validate_tx0_output_pubkey() != 51706.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_verify_latest_backup_tx_pays_to_user_pubkey() != 46083.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_generate_mnemonic() != 62910.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_blockheight() != 5222.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_new_coin() != 45841.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_new_key_info() != 64987.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_output_address_from_tx0() != 62309.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_partial_sig_request() != 13111.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_tx0_outpoint() != 21467.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_user_backup_address() != 29075.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_handle_deposit_msg_1_response() != 64110.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_is_enclave_pubkey_part_of_coin() != 37041.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_latest_backup_tx_pays_to_user_pubkey() != 19689.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_new_backup_transaction() != 56642.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_sign_message() != 9994.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_validate_address() != 16334.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_verify_blinded_musig_scheme() != 42963.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_verify_transaction_signature() != 32006.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// Async support + +// Public interface members begin here. + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */ +object NoPointer + +public object FfiConverterUByte : FfiConverter { + override fun lift(value: Byte): UByte { + return value.toUByte() + } + + override fun read(buf: ByteBuffer): UByte { + return lift(buf.get()) + } + + override fun lower(value: UByte): Byte { + return value.toByte() + } + + override fun allocationSize(value: UByte) = 1UL + + override fun write( + value: UByte, + buf: ByteBuffer, + ) { + buf.put(value.toByte()) + } +} + +public object FfiConverterUInt : FfiConverter { + override fun lift(value: Int): UInt { + return value.toUInt() + } + + override fun read(buf: ByteBuffer): UInt { + return lift(buf.getInt()) + } + + override fun lower(value: UInt): Int { + return value.toInt() + } + + override fun allocationSize(value: UInt) = 4UL + + override fun write( + value: UInt, + buf: ByteBuffer, + ) { + buf.putInt(value.toInt()) + } +} + +public object FfiConverterULong : FfiConverter { + override fun lift(value: Long): ULong { + return value.toULong() + } + + override fun read(buf: ByteBuffer): ULong { + return lift(buf.getLong()) + } + + override fun lower(value: ULong): Long { + return value.toLong() + } + + override fun allocationSize(value: ULong) = 8UL + + override fun write( + value: ULong, + buf: ByteBuffer, + ) { + buf.putLong(value.toLong()) + } +} + +public object FfiConverterBoolean : FfiConverter { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1UL + + override fun write( + value: Boolean, + buf: ByteBuffer, + ) { + buf.put(lower(value)) + } +} + +public object FfiConverterString : FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len.toInt()) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL + return sizeForLength + sizeForString + } + + override fun write( + value: String, + buf: ByteBuffer, + ) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} + +public object FfiConverterByteArray : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ByteArray { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr + } + + override fun allocationSize(value: ByteArray): ULong { + return 4UL + value.size.toULong() + } + + override fun write( + value: ByteArray, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + buf.put(value) + } +} + +@Serializable +data class Activity( + var `utxo`: kotlin.String, + var `amount`: kotlin.UInt, + var `action`: kotlin.String, + var `date`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeActivity : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Activity { + return Activity( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: Activity) = + ( + FfiConverterString.allocationSize(value.`utxo`) + + FfiConverterUInt.allocationSize(value.`amount`) + + FfiConverterString.allocationSize(value.`action`) + + FfiConverterString.allocationSize(value.`date`) + ) + + override fun write( + value: Activity, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`utxo`, buf) + FfiConverterUInt.write(value.`amount`, buf) + FfiConverterString.write(value.`action`, buf) + FfiConverterString.write(value.`date`, buf) + } +} + +@Serializable +data class AggregatedPublicKey( + var `aggregatePubkey`: kotlin.String, + var `aggregateAddress`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeAggregatedPublicKey : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AggregatedPublicKey { + return AggregatedPublicKey( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: AggregatedPublicKey) = + ( + FfiConverterString.allocationSize(value.`aggregatePubkey`) + + FfiConverterString.allocationSize(value.`aggregateAddress`) + ) + + override fun write( + value: AggregatedPublicKey, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`aggregatePubkey`, buf) + FfiConverterString.write(value.`aggregateAddress`, buf) + } +} + +@Serializable +data class BackupTx( + var `txN`: kotlin.UInt, + var `tx`: kotlin.String, + var `clientPublicNonce`: kotlin.String, + var `serverPublicNonce`: kotlin.String, + var `clientPublicKey`: kotlin.String, + var `serverPublicKey`: kotlin.String, + var `blindingFactor`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeBackupTx : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BackupTx { + return BackupTx( + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: BackupTx) = + ( + FfiConverterUInt.allocationSize(value.`txN`) + + FfiConverterString.allocationSize(value.`tx`) + + FfiConverterString.allocationSize(value.`clientPublicNonce`) + + FfiConverterString.allocationSize(value.`serverPublicNonce`) + + FfiConverterString.allocationSize(value.`clientPublicKey`) + + FfiConverterString.allocationSize(value.`serverPublicKey`) + + FfiConverterString.allocationSize(value.`blindingFactor`) + ) + + override fun write( + value: BackupTx, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`txN`, buf) + FfiConverterString.write(value.`tx`, buf) + FfiConverterString.write(value.`clientPublicNonce`, buf) + FfiConverterString.write(value.`serverPublicNonce`, buf) + FfiConverterString.write(value.`clientPublicKey`, buf) + FfiConverterString.write(value.`serverPublicKey`, buf) + FfiConverterString.write(value.`blindingFactor`, buf) + } +} + +@Serializable +data class Coin( + var `index`: kotlin.UInt, + var `userPrivkey`: kotlin.String, + var `userPubkey`: kotlin.String, + var `authPrivkey`: kotlin.String, + var `authPubkey`: kotlin.String, + var `derivationPath`: kotlin.String, + var `fingerprint`: kotlin.String, + /** + * The coin address is the user_pubkey || auth_pubkey + * Used to transfer the coin to another wallet + */ + var `address`: kotlin.String, + /** + * The backup address is the address used in backup transactions + * The backup address is the p2tr address of the user_pubkey + */ + var `backupAddress`: kotlin.String, + var `serverPubkey`: kotlin.String?, + var `aggregatedPubkey`: kotlin.String?, + /** + * The aggregated address is the P2TR address from aggregated_pubkey + */ + var `aggregatedAddress`: kotlin.String?, + var `utxoTxid`: kotlin.String?, + var `utxoVout`: kotlin.UInt?, + var `amount`: kotlin.UInt?, + var `statechainId`: kotlin.String?, + var `signedStatechainId`: kotlin.String?, + var `locktime`: kotlin.UInt?, + var `secretNonce`: kotlin.String?, + var `publicNonce`: kotlin.String?, + var `blindingFactor`: kotlin.String?, + var `serverPublicNonce`: kotlin.String?, + var `txCpfp`: kotlin.String?, + var `txWithdraw`: kotlin.String?, + var `withdrawalAddress`: kotlin.String?, + var `status`: CoinStatus, +) { + companion object +} + +public object FfiConverterTypeCoin : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Coin { + return Coin( + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterTypeCoinStatus.read(buf), + ) + } + + override fun allocationSize(value: Coin) = + ( + FfiConverterUInt.allocationSize(value.`index`) + + FfiConverterString.allocationSize(value.`userPrivkey`) + + FfiConverterString.allocationSize(value.`userPubkey`) + + FfiConverterString.allocationSize(value.`authPrivkey`) + + FfiConverterString.allocationSize(value.`authPubkey`) + + FfiConverterString.allocationSize(value.`derivationPath`) + + FfiConverterString.allocationSize(value.`fingerprint`) + + FfiConverterString.allocationSize(value.`address`) + + FfiConverterString.allocationSize(value.`backupAddress`) + + FfiConverterOptionalString.allocationSize(value.`serverPubkey`) + + FfiConverterOptionalString.allocationSize(value.`aggregatedPubkey`) + + FfiConverterOptionalString.allocationSize(value.`aggregatedAddress`) + + FfiConverterOptionalString.allocationSize(value.`utxoTxid`) + + FfiConverterOptionalUInt.allocationSize(value.`utxoVout`) + + FfiConverterOptionalUInt.allocationSize(value.`amount`) + + FfiConverterOptionalString.allocationSize(value.`statechainId`) + + FfiConverterOptionalString.allocationSize(value.`signedStatechainId`) + + FfiConverterOptionalUInt.allocationSize(value.`locktime`) + + FfiConverterOptionalString.allocationSize(value.`secretNonce`) + + FfiConverterOptionalString.allocationSize(value.`publicNonce`) + + FfiConverterOptionalString.allocationSize(value.`blindingFactor`) + + FfiConverterOptionalString.allocationSize(value.`serverPublicNonce`) + + FfiConverterOptionalString.allocationSize(value.`txCpfp`) + + FfiConverterOptionalString.allocationSize(value.`txWithdraw`) + + FfiConverterOptionalString.allocationSize(value.`withdrawalAddress`) + + FfiConverterTypeCoinStatus.allocationSize(value.`status`) + ) + + override fun write( + value: Coin, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`index`, buf) + FfiConverterString.write(value.`userPrivkey`, buf) + FfiConverterString.write(value.`userPubkey`, buf) + FfiConverterString.write(value.`authPrivkey`, buf) + FfiConverterString.write(value.`authPubkey`, buf) + FfiConverterString.write(value.`derivationPath`, buf) + FfiConverterString.write(value.`fingerprint`, buf) + FfiConverterString.write(value.`address`, buf) + FfiConverterString.write(value.`backupAddress`, buf) + FfiConverterOptionalString.write(value.`serverPubkey`, buf) + FfiConverterOptionalString.write(value.`aggregatedPubkey`, buf) + FfiConverterOptionalString.write(value.`aggregatedAddress`, buf) + FfiConverterOptionalString.write(value.`utxoTxid`, buf) + FfiConverterOptionalUInt.write(value.`utxoVout`, buf) + FfiConverterOptionalUInt.write(value.`amount`, buf) + FfiConverterOptionalString.write(value.`statechainId`, buf) + FfiConverterOptionalString.write(value.`signedStatechainId`, buf) + FfiConverterOptionalUInt.write(value.`locktime`, buf) + FfiConverterOptionalString.write(value.`secretNonce`, buf) + FfiConverterOptionalString.write(value.`publicNonce`, buf) + FfiConverterOptionalString.write(value.`blindingFactor`, buf) + FfiConverterOptionalString.write(value.`serverPublicNonce`, buf) + FfiConverterOptionalString.write(value.`txCpfp`, buf) + FfiConverterOptionalString.write(value.`txWithdraw`, buf) + FfiConverterOptionalString.write(value.`withdrawalAddress`, buf) + FfiConverterTypeCoinStatus.write(value.`status`, buf) + } +} + +data class CoinNonce( + var `secretNonce`: kotlin.String, + var `publicNonce`: kotlin.String, + var `blindingFactor`: kotlin.String, + var `signFirstRequestPayload`: SignFirstRequestPayload, +) { + companion object +} + +public object FfiConverterTypeCoinNonce : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CoinNonce { + return CoinNonce( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeSignFirstRequestPayload.read(buf), + ) + } + + override fun allocationSize(value: CoinNonce) = + ( + FfiConverterString.allocationSize(value.`secretNonce`) + + FfiConverterString.allocationSize(value.`publicNonce`) + + FfiConverterString.allocationSize(value.`blindingFactor`) + + FfiConverterTypeSignFirstRequestPayload.allocationSize(value.`signFirstRequestPayload`) + ) + + override fun write( + value: CoinNonce, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`secretNonce`, buf) + FfiConverterString.write(value.`publicNonce`, buf) + FfiConverterString.write(value.`blindingFactor`, buf) + FfiConverterTypeSignFirstRequestPayload.write(value.`signFirstRequestPayload`, buf) + } +} + +class CoinStatusParseError { + override fun equals(other: Any?): Boolean { + return other is CoinStatusParseError + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + companion object +} + +public object FfiConverterTypeCoinStatusParseError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CoinStatusParseError { + return CoinStatusParseError() + } + + override fun allocationSize(value: CoinStatusParseError) = 0UL + + override fun write( + value: CoinStatusParseError, + buf: ByteBuffer, + ) { + } +} + +data class DecodedScAddress( + var `version`: kotlin.UByte, + var `userPubkey`: kotlin.String, + var `authPubkey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDecodedSCAddress : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DecodedScAddress { + return DecodedScAddress( + FfiConverterUByte.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DecodedScAddress) = + ( + FfiConverterUByte.allocationSize(value.`version`) + + FfiConverterString.allocationSize(value.`userPubkey`) + + FfiConverterString.allocationSize(value.`authPubkey`) + ) + + override fun write( + value: DecodedScAddress, + buf: ByteBuffer, + ) { + FfiConverterUByte.write(value.`version`, buf) + FfiConverterString.write(value.`userPubkey`, buf) + FfiConverterString.write(value.`authPubkey`, buf) + } +} + +@Serializable +data class DepositInitResult( + var `serverPubkey`: kotlin.String, + var `statechainId`: kotlin.String, + var `signedStatechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDepositInitResult : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DepositInitResult { + return DepositInitResult( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DepositInitResult) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + ) + + override fun write( + value: DepositInitResult, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + } +} + +@Serializable +data class DepositMsg1( + @SerialName("auth_key") + var `authKey`: kotlin.String, + @SerialName("token_id") + var `tokenId`: kotlin.String, + @SerialName("signed_token_id") + var `signedTokenId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDepositMsg1 : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DepositMsg1 { + return DepositMsg1( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DepositMsg1) = + ( + FfiConverterString.allocationSize(value.`authKey`) + + FfiConverterString.allocationSize(value.`tokenId`) + + FfiConverterString.allocationSize(value.`signedTokenId`) + ) + + override fun write( + value: DepositMsg1, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`authKey`, buf) + FfiConverterString.write(value.`tokenId`, buf) + FfiConverterString.write(value.`signedTokenId`, buf) + } +} + +@Serializable +data class DepositMsg1Response( + @SerialName("server_pubkey") + var `serverPubkey`: kotlin.String, + @SerialName("statechain_id") + var `statechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDepositMsg1Response : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DepositMsg1Response { + return DepositMsg1Response( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DepositMsg1Response) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + + FfiConverterString.allocationSize(value.`statechainId`) + ) + + override fun write( + value: DepositMsg1Response, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + FfiConverterString.write(value.`statechainId`, buf) + } +} + +data class FfiTransferMsg( + var `statechainId`: kotlin.String, + var `transferSignature`: kotlin.String, + var `backupTransactions`: List, + var `t1`: kotlin.ByteArray, + var `userPublicKey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeFFITransferMsg : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiTransferMsg { + return FfiTransferMsg( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterSequenceTypeBackupTx.read(buf), + FfiConverterByteArray.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: FfiTransferMsg) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`transferSignature`) + + FfiConverterSequenceTypeBackupTx.allocationSize(value.`backupTransactions`) + + FfiConverterByteArray.allocationSize(value.`t1`) + + FfiConverterString.allocationSize(value.`userPublicKey`) + ) + + override fun write( + value: FfiTransferMsg, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`transferSignature`, buf) + FfiConverterSequenceTypeBackupTx.write(value.`backupTransactions`, buf) + FfiConverterByteArray.write(value.`t1`, buf) + FfiConverterString.write(value.`userPublicKey`, buf) + } +} + +@Serializable +data class GetMsgAddrResponsePayload( + @SerialName("list_enc_transfer_msg") + var `listEncTransferMsg`: List, +) { + companion object +} + +public object FfiConverterTypeGetMsgAddrResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): GetMsgAddrResponsePayload { + return GetMsgAddrResponsePayload( + FfiConverterSequenceString.read(buf), + ) + } + + override fun allocationSize(value: GetMsgAddrResponsePayload) = + ( + FfiConverterSequenceString.allocationSize(value.`listEncTransferMsg`) + ) + + override fun write( + value: GetMsgAddrResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterSequenceString.write(value.`listEncTransferMsg`, buf) + } +} + +data class InfoConfig( + var `initlock`: kotlin.UInt, + var `interval`: kotlin.UInt, + var `feeRateSatsPerByte`: kotlin.ULong, +) { + companion object +} + +public object FfiConverterTypeInfoConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): InfoConfig { + return InfoConfig( + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: InfoConfig) = + ( + FfiConverterUInt.allocationSize(value.`initlock`) + + FfiConverterUInt.allocationSize(value.`interval`) + + FfiConverterULong.allocationSize(value.`feeRateSatsPerByte`) + ) + + override fun write( + value: InfoConfig, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`initlock`, buf) + FfiConverterUInt.write(value.`interval`, buf) + FfiConverterULong.write(value.`feeRateSatsPerByte`, buf) + } +} + +data class KeyListResponsePayload( + var `listKeyinfo`: List, +) { + companion object +} + +public object FfiConverterTypeKeyListResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): KeyListResponsePayload { + return KeyListResponsePayload( + FfiConverterSequenceTypePubKeyInfo.read(buf), + ) + } + + override fun allocationSize(value: KeyListResponsePayload) = + ( + FfiConverterSequenceTypePubKeyInfo.allocationSize(value.`listKeyinfo`) + ) + + override fun write( + value: KeyListResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterSequenceTypePubKeyInfo.write(value.`listKeyinfo`, buf) + } +} + +@Serializable +data class KeyUpdateResponsePayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + var `t2`: kotlin.String, + var `x1`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeKeyUpdateResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): KeyUpdateResponsePayload { + return KeyUpdateResponsePayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: KeyUpdateResponsePayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`t2`) + + FfiConverterString.allocationSize(value.`x1`) + ) + + override fun write( + value: KeyUpdateResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`t2`, buf) + FfiConverterString.write(value.`x1`, buf) + } +} + +data class NewKeyInfo( + var `aggregatePubkey`: kotlin.String, + var `aggregateAddress`: kotlin.String, + var `signedStatechainId`: kotlin.String, + var `amount`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeNewKeyInfo : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): NewKeyInfo { + return NewKeyInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: NewKeyInfo) = + ( + FfiConverterString.allocationSize(value.`aggregatePubkey`) + + FfiConverterString.allocationSize(value.`aggregateAddress`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + + FfiConverterUInt.allocationSize(value.`amount`) + ) + + override fun write( + value: NewKeyInfo, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`aggregatePubkey`, buf) + FfiConverterString.write(value.`aggregateAddress`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + FfiConverterUInt.write(value.`amount`, buf) + } +} + +data class PartialSignatureMsg1( + var `msg`: kotlin.String, + var `outputPubkey`: kotlin.String, + var `clientPartialSig`: kotlin.String, + var `encodedSession`: kotlin.String, + var `encodedUnsignedTx`: kotlin.String, + var `partialSignatureRequestPayload`: PartialSignatureRequestPayload, +) { + companion object +} + +public object FfiConverterTypePartialSignatureMsg1 : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PartialSignatureMsg1 { + return PartialSignatureMsg1( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypePartialSignatureRequestPayload.read(buf), + ) + } + + override fun allocationSize(value: PartialSignatureMsg1) = + ( + FfiConverterString.allocationSize(value.`msg`) + + FfiConverterString.allocationSize(value.`outputPubkey`) + + FfiConverterString.allocationSize(value.`clientPartialSig`) + + FfiConverterString.allocationSize(value.`encodedSession`) + + FfiConverterString.allocationSize(value.`encodedUnsignedTx`) + + FfiConverterTypePartialSignatureRequestPayload.allocationSize(value.`partialSignatureRequestPayload`) + ) + + override fun write( + value: PartialSignatureMsg1, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`msg`, buf) + FfiConverterString.write(value.`outputPubkey`, buf) + FfiConverterString.write(value.`clientPartialSig`, buf) + FfiConverterString.write(value.`encodedSession`, buf) + FfiConverterString.write(value.`encodedUnsignedTx`, buf) + FfiConverterTypePartialSignatureRequestPayload.write(value.`partialSignatureRequestPayload`, buf) + } +} + +@Serializable +data class PartialSignatureRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("negate_seckey") + var `negateSeckey`: kotlin.UByte, + var `session`: kotlin.String, + @SerialName("signed_statechain_id") + var `signedStatechainId`: kotlin.String, + @SerialName("server_pub_nonce") + var `serverPubNonce`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypePartialSignatureRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PartialSignatureRequestPayload { + return PartialSignatureRequestPayload( + FfiConverterString.read(buf), + FfiConverterUByte.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PartialSignatureRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterUByte.allocationSize(value.`negateSeckey`) + + FfiConverterString.allocationSize(value.`session`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + + FfiConverterString.allocationSize(value.`serverPubNonce`) + ) + + override fun write( + value: PartialSignatureRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterUByte.write(value.`negateSeckey`, buf) + FfiConverterString.write(value.`session`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + FfiConverterString.write(value.`serverPubNonce`, buf) + } +} + +@Serializable +data class PartialSignatureResponsePayload( + @SerialName("partial_sig") + var `partialSig`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypePartialSignatureResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PartialSignatureResponsePayload { + return PartialSignatureResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PartialSignatureResponsePayload) = + ( + FfiConverterString.allocationSize(value.`partialSig`) + ) + + override fun write( + value: PartialSignatureResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`partialSig`, buf) + } +} + +data class PubKeyInfo( + var `serverPubkey`: kotlin.String, + var `txN`: kotlin.UInt, + var `updatedAt`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypePubKeyInfo : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PubKeyInfo { + return PubKeyInfo( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PubKeyInfo) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + + FfiConverterUInt.allocationSize(value.`txN`) + + FfiConverterString.allocationSize(value.`updatedAt`) + ) + + override fun write( + value: PubKeyInfo, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + FfiConverterUInt.write(value.`txN`, buf) + FfiConverterString.write(value.`updatedAt`, buf) + } +} + +@Serializable +data class ServerConfig( + var `initlock`: kotlin.UInt, + var `interval`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeServerConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ServerConfig { + return ServerConfig( + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: ServerConfig) = + ( + FfiConverterUInt.allocationSize(value.`initlock`) + + FfiConverterUInt.allocationSize(value.`interval`) + ) + + override fun write( + value: ServerConfig, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`initlock`, buf) + FfiConverterUInt.write(value.`interval`, buf) + } +} + +data class ServerPublicNonceResponsePayload( + var `serverPubnonce`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeServerPublicNonceResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ServerPublicNonceResponsePayload { + return ServerPublicNonceResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: ServerPublicNonceResponsePayload) = + ( + FfiConverterString.allocationSize(value.`serverPubnonce`) + ) + + override fun write( + value: ServerPublicNonceResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubnonce`, buf) + } +} + +@Serializable +data class Settings( + var `network`: kotlin.String, + var `blockExplorerUrl`: kotlin.String?, + var `torProxyHost`: kotlin.String?, + var `torProxyPort`: kotlin.String?, + var `torProxyControlPassword`: kotlin.String?, + var `torProxyControlPort`: kotlin.String?, + var `statechainEntityApi`: kotlin.String, + var `torStatechainEntityApi`: kotlin.String?, + var `electrumProtocol`: kotlin.String, + var `electrumHost`: kotlin.String, + var `electrumPort`: kotlin.String, + var `electrumType`: kotlin.String, + var `notifications`: kotlin.Boolean, + var `tutorials`: kotlin.Boolean, +) { + companion object +} + +public object FfiConverterTypeSettings : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Settings { + return Settings( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + ) + } + + override fun allocationSize(value: Settings) = + ( + FfiConverterString.allocationSize(value.`network`) + + FfiConverterOptionalString.allocationSize(value.`blockExplorerUrl`) + + FfiConverterOptionalString.allocationSize(value.`torProxyHost`) + + FfiConverterOptionalString.allocationSize(value.`torProxyPort`) + + FfiConverterOptionalString.allocationSize(value.`torProxyControlPassword`) + + FfiConverterOptionalString.allocationSize(value.`torProxyControlPort`) + + FfiConverterString.allocationSize(value.`statechainEntityApi`) + + FfiConverterOptionalString.allocationSize(value.`torStatechainEntityApi`) + + FfiConverterString.allocationSize(value.`electrumProtocol`) + + FfiConverterString.allocationSize(value.`electrumHost`) + + FfiConverterString.allocationSize(value.`electrumPort`) + + FfiConverterString.allocationSize(value.`electrumType`) + + FfiConverterBoolean.allocationSize(value.`notifications`) + + FfiConverterBoolean.allocationSize(value.`tutorials`) + ) + + override fun write( + value: Settings, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`network`, buf) + FfiConverterOptionalString.write(value.`blockExplorerUrl`, buf) + FfiConverterOptionalString.write(value.`torProxyHost`, buf) + FfiConverterOptionalString.write(value.`torProxyPort`, buf) + FfiConverterOptionalString.write(value.`torProxyControlPassword`, buf) + FfiConverterOptionalString.write(value.`torProxyControlPort`, buf) + FfiConverterString.write(value.`statechainEntityApi`, buf) + FfiConverterOptionalString.write(value.`torStatechainEntityApi`, buf) + FfiConverterString.write(value.`electrumProtocol`, buf) + FfiConverterString.write(value.`electrumHost`, buf) + FfiConverterString.write(value.`electrumPort`, buf) + FfiConverterString.write(value.`electrumType`, buf) + FfiConverterBoolean.write(value.`notifications`, buf) + FfiConverterBoolean.write(value.`tutorials`, buf) + } +} + +@Serializable +data class SignFirstRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("signed_statechain_id") + var `signedStatechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeSignFirstRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SignFirstRequestPayload { + return SignFirstRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SignFirstRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + ) + + override fun write( + value: SignFirstRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + } +} + +@Serializable +data class SignFirstResponsePayload( + @SerialName("server_pubnonce") + var `serverPubnonce`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeSignFirstResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SignFirstResponsePayload { + return SignFirstResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SignFirstResponsePayload) = + ( + FfiConverterString.allocationSize(value.`serverPubnonce`) + ) + + override fun write( + value: SignFirstResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubnonce`, buf) + } +} + +data class StatechainBackupTxs( + var `statechainId`: kotlin.String, + var `backupTxs`: List, +) { + companion object +} + +public object FfiConverterTypeStatechainBackupTxs : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): StatechainBackupTxs { + return StatechainBackupTxs( + FfiConverterString.read(buf), + FfiConverterSequenceTypeBackupTx.read(buf), + ) + } + + override fun allocationSize(value: StatechainBackupTxs) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterSequenceTypeBackupTx.allocationSize(value.`backupTxs`) + ) + + override fun write( + value: StatechainBackupTxs, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterSequenceTypeBackupTx.write(value.`backupTxs`, buf) + } +} + +@Serializable +data class StatechainInfo( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("server_pubnonce") + var `serverPubnonce`: kotlin.String, + var `challenge`: kotlin.String, + @SerialName("tx_n") + var `txN`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeStatechainInfo : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): StatechainInfo { + return StatechainInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: StatechainInfo) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`serverPubnonce`) + + FfiConverterString.allocationSize(value.`challenge`) + + FfiConverterUInt.allocationSize(value.`txN`) + ) + + override fun write( + value: StatechainInfo, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`serverPubnonce`, buf) + FfiConverterString.write(value.`challenge`, buf) + FfiConverterUInt.write(value.`txN`, buf) + } +} + +@Serializable +data class StatechainInfoResponsePayload( + @SerialName("enclave_public_key") + var `enclavePublicKey`: kotlin.String, + @SerialName("num_sigs") + var `numSigs`: kotlin.UInt, + @SerialName("statechain_info") + var `statechainInfo`: List, + @SerialName("x1_pub") + var `x1Pub`: kotlin.String?, +) { + companion object +} + +public object FfiConverterTypeStatechainInfoResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): StatechainInfoResponsePayload { + return StatechainInfoResponsePayload( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterSequenceTypeStatechainInfo.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: StatechainInfoResponsePayload) = + ( + FfiConverterString.allocationSize(value.`enclavePublicKey`) + + FfiConverterUInt.allocationSize(value.`numSigs`) + + FfiConverterSequenceTypeStatechainInfo.allocationSize(value.`statechainInfo`) + + FfiConverterOptionalString.allocationSize(value.`x1Pub`) + ) + + override fun write( + value: StatechainInfoResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`enclavePublicKey`, buf) + FfiConverterUInt.write(value.`numSigs`, buf) + FfiConverterSequenceTypeStatechainInfo.write(value.`statechainInfo`, buf) + FfiConverterOptionalString.write(value.`x1Pub`, buf) + } +} + +@Serializable +data class Token( + @SerialName("btc_payment_address") + var `btcPaymentAddress`: kotlin.String, + var `fee`: kotlin.String, + @SerialName("lightning_invoice") + var `lightningInvoice`: kotlin.String, + @SerialName("processor_id") + var `processorId`: kotlin.String, + @SerialName("token_id") + var `tokenId`: kotlin.String, + var `confirmed`: kotlin.Boolean, + var `spent`: kotlin.Boolean, + var `expiry`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeToken : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Token { + return Token( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: Token) = + ( + FfiConverterString.allocationSize(value.`btcPaymentAddress`) + + FfiConverterString.allocationSize(value.`fee`) + + FfiConverterString.allocationSize(value.`lightningInvoice`) + + FfiConverterString.allocationSize(value.`processorId`) + + FfiConverterString.allocationSize(value.`tokenId`) + + FfiConverterBoolean.allocationSize(value.`confirmed`) + + FfiConverterBoolean.allocationSize(value.`spent`) + + FfiConverterString.allocationSize(value.`expiry`) + ) + + override fun write( + value: Token, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`btcPaymentAddress`, buf) + FfiConverterString.write(value.`fee`, buf) + FfiConverterString.write(value.`lightningInvoice`, buf) + FfiConverterString.write(value.`processorId`, buf) + FfiConverterString.write(value.`tokenId`, buf) + FfiConverterBoolean.write(value.`confirmed`, buf) + FfiConverterBoolean.write(value.`spent`, buf) + FfiConverterString.write(value.`expiry`, buf) + } +} + +@Serializable +data class TransferReceiverErrorResponsePayload( + var `code`: TransferReceiverError, + var `message`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferReceiverErrorResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferReceiverErrorResponsePayload { + return TransferReceiverErrorResponsePayload( + FfiConverterTypeTransferReceiverError.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferReceiverErrorResponsePayload) = + ( + FfiConverterTypeTransferReceiverError.allocationSize(value.`code`) + + FfiConverterString.allocationSize(value.`message`) + ) + + override fun write( + value: TransferReceiverErrorResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterTypeTransferReceiverError.write(value.`code`, buf) + FfiConverterString.write(value.`message`, buf) + } +} + +@Serializable +data class TransferReceiverPostResponsePayload( + @SerialName("server_pubkey") + var `serverPubkey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferReceiverPostResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferReceiverPostResponsePayload { + return TransferReceiverPostResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferReceiverPostResponsePayload) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + ) + + override fun write( + value: TransferReceiverPostResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + } +} + +@Serializable +data class TransferReceiverRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("batch_data") + var `batchData`: kotlin.String?, + var `t2`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferReceiverRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferReceiverRequestPayload { + return TransferReceiverRequestPayload( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferReceiverRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterOptionalString.allocationSize(value.`batchData`) + + FfiConverterString.allocationSize(value.`t2`) + + FfiConverterString.allocationSize(value.`authSig`) + ) + + override fun write( + value: TransferReceiverRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterOptionalString.write(value.`batchData`, buf) + FfiConverterString.write(value.`t2`, buf) + FfiConverterString.write(value.`authSig`, buf) + } +} + +@Serializable +data class TransferSenderRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, + @SerialName("new_user_auth_key") + var `newUserAuthKey`: kotlin.String, + @SerialName("batch_id") + var `batchId`: kotlin.String?, +) { + companion object +} + +public object FfiConverterTypeTransferSenderRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferSenderRequestPayload { + return TransferSenderRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: TransferSenderRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`authSig`) + + FfiConverterString.allocationSize(value.`newUserAuthKey`) + + FfiConverterOptionalString.allocationSize(value.`batchId`) + ) + + override fun write( + value: TransferSenderRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`authSig`, buf) + FfiConverterString.write(value.`newUserAuthKey`, buf) + FfiConverterOptionalString.write(value.`batchId`, buf) + } +} + +@Serializable +data class TransferSenderResponsePayload( + var `x1`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferSenderResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferSenderResponsePayload { + return TransferSenderResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferSenderResponsePayload) = + ( + FfiConverterString.allocationSize(value.`x1`) + ) + + override fun write( + value: TransferSenderResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`x1`, buf) + } +} + +@Serializable +data class TransferUnlockRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, + @SerialName("auth_pub_key") + var `authPubKey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferUnlockRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferUnlockRequestPayload { + return TransferUnlockRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferUnlockRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`authSig`) + + FfiConverterString.allocationSize(value.`authPubKey`) + ) + + override fun write( + value: TransferUnlockRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`authSig`, buf) + FfiConverterString.write(value.`authPubKey`, buf) + } +} + +@Serializable +data class TransferUpdateMsgRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, + @SerialName("new_user_auth_key") + var `newUserAuthKey`: kotlin.String, + @SerialName("enc_transfer_msg") + var `encTransferMsg`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferUpdateMsgRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferUpdateMsgRequestPayload { + return TransferUpdateMsgRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferUpdateMsgRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`authSig`) + + FfiConverterString.allocationSize(value.`newUserAuthKey`) + + FfiConverterString.allocationSize(value.`encTransferMsg`) + ) + + override fun write( + value: TransferUpdateMsgRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`authSig`, buf) + FfiConverterString.write(value.`newUserAuthKey`, buf) + FfiConverterString.write(value.`encTransferMsg`, buf) + } +} + +data class TxOutpoint( + var `txid`: kotlin.String, + var `vout`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeTxOutpoint : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TxOutpoint { + return TxOutpoint( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: TxOutpoint) = + ( + FfiConverterString.allocationSize(value.`txid`) + + FfiConverterUInt.allocationSize(value.`vout`) + ) + + override fun write( + value: TxOutpoint, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`txid`, buf) + FfiConverterUInt.write(value.`vout`, buf) + } +} + +@Serializable +data class Wallet( + var `name`: kotlin.String, + var `mnemonic`: kotlin.String, + var `version`: kotlin.String, + var `stateEntityEndpoint`: kotlin.String, + var `electrumEndpoint`: kotlin.String, + var `network`: kotlin.String, + var `blockheight`: kotlin.UInt, + var `initlock`: kotlin.UInt, + var `interval`: kotlin.UInt, + var `tokens`: List, + var `activities`: List, + var `coins`: List, + var `settings`: Settings, +) { + companion object +} + +public object FfiConverterTypeWallet : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Wallet { + return Wallet( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterSequenceTypeToken.read(buf), + FfiConverterSequenceTypeActivity.read(buf), + FfiConverterSequenceTypeCoin.read(buf), + FfiConverterTypeSettings.read(buf), + ) + } + + override fun allocationSize(value: Wallet) = + ( + FfiConverterString.allocationSize(value.`name`) + + FfiConverterString.allocationSize(value.`mnemonic`) + + FfiConverterString.allocationSize(value.`version`) + + FfiConverterString.allocationSize(value.`stateEntityEndpoint`) + + FfiConverterString.allocationSize(value.`electrumEndpoint`) + + FfiConverterString.allocationSize(value.`network`) + + FfiConverterUInt.allocationSize(value.`blockheight`) + + FfiConverterUInt.allocationSize(value.`initlock`) + + FfiConverterUInt.allocationSize(value.`interval`) + + FfiConverterSequenceTypeToken.allocationSize(value.`tokens`) + + FfiConverterSequenceTypeActivity.allocationSize(value.`activities`) + + FfiConverterSequenceTypeCoin.allocationSize(value.`coins`) + + FfiConverterTypeSettings.allocationSize(value.`settings`) + ) + + override fun write( + value: Wallet, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`name`, buf) + FfiConverterString.write(value.`mnemonic`, buf) + FfiConverterString.write(value.`version`, buf) + FfiConverterString.write(value.`stateEntityEndpoint`, buf) + FfiConverterString.write(value.`electrumEndpoint`, buf) + FfiConverterString.write(value.`network`, buf) + FfiConverterUInt.write(value.`blockheight`, buf) + FfiConverterUInt.write(value.`initlock`, buf) + FfiConverterUInt.write(value.`interval`, buf) + FfiConverterSequenceTypeToken.write(value.`tokens`, buf) + FfiConverterSequenceTypeActivity.write(value.`activities`, buf) + FfiConverterSequenceTypeCoin.write(value.`coins`, buf) + FfiConverterTypeSettings.write(value.`settings`, buf) + } +} + +@Serializable +data class WithdrawCompletePayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("signed_statechain_id") + var `signedStatechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeWithdrawCompletePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): WithdrawCompletePayload { + return WithdrawCompletePayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: WithdrawCompletePayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + ) + + override fun write( + value: WithdrawCompletePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + } +} + +enum class CoinStatus { + INITIALISED, + IN_MEMPOOL, + UNCONFIRMED, + CONFIRMED, + IN_TRANSFER, + WITHDRAWING, + TRANSFERRED, + WITHDRAWN, + ; + + companion object +} + +public object FfiConverterTypeCoinStatus : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = + try { + CoinStatus.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: CoinStatus) = 4UL + + override fun write( + value: CoinStatus, + buf: ByteBuffer, + ) { + buf.putInt(value.ordinal + 1) + } +} + +sealed class MercuryException : Exception() { + class Bip39Exception() : MercuryException() { + override val message + get() = "" + } + + class Bip32Exception() : MercuryException() { + override val message + get() = "" + } + + class NetworkConversionException() : MercuryException() { + override val message + get() = "" + } + + class Secp256k1UpstreamException() : MercuryException() { + override val message + get() = "" + } + + class KeyException() : MercuryException() { + override val message + get() = "" + } + + class Bech32Exception() : MercuryException() { + override val message + get() = "" + } + + class HexException() : MercuryException() { + override val message + get() = "" + } + + class LocktimeNotBlockHeightException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinConsensusEncodeException() : MercuryException() { + override val message + get() = "" + } + + class MusigNonceGenException() : MercuryException() { + override val message + get() = "" + } + + class InvalidStatechainAddressException() : MercuryException() { + override val message + get() = "" + } + + class InvalidBitcoinAddressException() : MercuryException() { + override val message + get() = "" + } + + class StatechainAddressMismatchNetworkException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinAddressMismatchNetworkException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinAddressException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinAbsoluteException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinHashHexException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinPsbtException() : MercuryException() { + override val message + get() = "" + } + + class SighashTypeParseException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinSighashException() : MercuryException() { + override val message + get() = "" + } + + class ParseException() : MercuryException() { + override val message + get() = "" + } + + class MusigSignException() : MercuryException() { + override val message + get() = "" + } + + class SchnorrSignatureValidationException() : MercuryException() { + override val message + get() = "" + } + + class MoreThanOneInputException() : MercuryException() { + override val message + get() = "" + } + + class UnkownNetwork() : MercuryException() { + override val message + get() = "" + } + + class BackupTransactionDoesNotPayUser() : MercuryException() { + override val message + get() = "" + } + + class FeeTooHigh() : MercuryException() { + override val message + get() = "" + } + + class FeeTooLow() : MercuryException() { + override val message + get() = "" + } + + class OutOfRangeException() : MercuryException() { + override val message + get() = "" + } + + class SerdeJsonException() : MercuryException() { + override val message + get() = "" + } + + class SecpException() : MercuryException() { + override val message + get() = "" + } + + class NoBackupTransactionFound() : MercuryException() { + override val message + get() = "" + } + + class Tx1HasMoreThanOneInput() : MercuryException() { + override val message + get() = "" + } + + class InvalidSignature() : MercuryException() { + override val message + get() = "" + } + + class EmptyWitness() : MercuryException() { + override val message + get() = "" + } + + class EmptyWitnessData() : MercuryException() { + override val message + get() = "" + } + + class IncorrectChallenge() : MercuryException() { + override val message + get() = "" + } + + class InvalidT1() : MercuryException() { + override val message + get() = "" + } + + class IncorrectAggregatedPublicKey() : MercuryException() { + override val message + get() = "" + } + + class T1MustBeExactly32BytesException() : MercuryException() { + override val message + get() = "" + } + + class NoX1Pub() : MercuryException() { + override val message + get() = "" + } + + class NoAggregatedPubkeyException() : MercuryException() { + override val message + get() = "" + } + + class CoinNotFound() : MercuryException() { + override val message + get() = "" + } + + class SignatureSchemeValidationException() : MercuryException() { + override val message + get() = "" + } + + class NoPreviousLockTimeException() : MercuryException() { + override val message + get() = "" + } + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): MercuryException = FfiConverterTypeMercuryError.lift(error_buf) + } +} + +public object FfiConverterTypeMercuryError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): MercuryException { + return when (buf.getInt()) { + 1 -> MercuryException.Bip39Exception() + 2 -> MercuryException.Bip32Exception() + 3 -> MercuryException.NetworkConversionException() + 4 -> MercuryException.Secp256k1UpstreamException() + 5 -> MercuryException.KeyException() + 6 -> MercuryException.Bech32Exception() + 7 -> MercuryException.HexException() + 8 -> MercuryException.LocktimeNotBlockHeightException() + 9 -> MercuryException.BitcoinConsensusEncodeException() + 10 -> MercuryException.MusigNonceGenException() + 11 -> MercuryException.InvalidStatechainAddressException() + 12 -> MercuryException.InvalidBitcoinAddressException() + 13 -> MercuryException.StatechainAddressMismatchNetworkException() + 14 -> MercuryException.BitcoinAddressMismatchNetworkException() + 15 -> MercuryException.BitcoinAddressException() + 16 -> MercuryException.BitcoinAbsoluteException() + 17 -> MercuryException.BitcoinHashHexException() + 18 -> MercuryException.BitcoinPsbtException() + 19 -> MercuryException.SighashTypeParseException() + 20 -> MercuryException.BitcoinSighashException() + 21 -> MercuryException.ParseException() + 22 -> MercuryException.MusigSignException() + 23 -> MercuryException.SchnorrSignatureValidationException() + 24 -> MercuryException.MoreThanOneInputException() + 25 -> MercuryException.UnkownNetwork() + 26 -> MercuryException.BackupTransactionDoesNotPayUser() + 27 -> MercuryException.FeeTooHigh() + 28 -> MercuryException.FeeTooLow() + 29 -> MercuryException.OutOfRangeException() + 30 -> MercuryException.SerdeJsonException() + 31 -> MercuryException.SecpException() + 32 -> MercuryException.NoBackupTransactionFound() + 33 -> MercuryException.Tx1HasMoreThanOneInput() + 34 -> MercuryException.InvalidSignature() + 35 -> MercuryException.EmptyWitness() + 36 -> MercuryException.EmptyWitnessData() + 37 -> MercuryException.IncorrectChallenge() + 38 -> MercuryException.InvalidT1() + 39 -> MercuryException.IncorrectAggregatedPublicKey() + 40 -> MercuryException.T1MustBeExactly32BytesException() + 41 -> MercuryException.NoX1Pub() + 42 -> MercuryException.NoAggregatedPubkeyException() + 43 -> MercuryException.CoinNotFound() + 44 -> MercuryException.SignatureSchemeValidationException() + 45 -> MercuryException.NoPreviousLockTimeException() + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: MercuryException): ULong { + return when (value) { + is MercuryException.Bip39Exception -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Bip32Exception -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NetworkConversionException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Secp256k1UpstreamException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.KeyException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Bech32Exception -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.HexException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.LocktimeNotBlockHeightException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinConsensusEncodeException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.MusigNonceGenException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidStatechainAddressException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidBitcoinAddressException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.StatechainAddressMismatchNetworkException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinAddressMismatchNetworkException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinAddressException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinAbsoluteException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinHashHexException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinPsbtException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SighashTypeParseException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinSighashException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.ParseException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.MusigSignException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SchnorrSignatureValidationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.MoreThanOneInputException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.UnkownNetwork -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BackupTransactionDoesNotPayUser -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.FeeTooHigh -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.FeeTooLow -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.OutOfRangeException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SerdeJsonException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SecpException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoBackupTransactionFound -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Tx1HasMoreThanOneInput -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidSignature -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.EmptyWitness -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.EmptyWitnessData -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.IncorrectChallenge -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidT1 -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.IncorrectAggregatedPublicKey -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.T1MustBeExactly32BytesException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoX1Pub -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoAggregatedPubkeyException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.CoinNotFound -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SignatureSchemeValidationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoPreviousLockTimeException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + } + } + + override fun write( + value: MercuryException, + buf: ByteBuffer, + ) { + when (value) { + is MercuryException.Bip39Exception -> { + buf.putInt(1) + Unit + } + is MercuryException.Bip32Exception -> { + buf.putInt(2) + Unit + } + is MercuryException.NetworkConversionException -> { + buf.putInt(3) + Unit + } + is MercuryException.Secp256k1UpstreamException -> { + buf.putInt(4) + Unit + } + is MercuryException.KeyException -> { + buf.putInt(5) + Unit + } + is MercuryException.Bech32Exception -> { + buf.putInt(6) + Unit + } + is MercuryException.HexException -> { + buf.putInt(7) + Unit + } + is MercuryException.LocktimeNotBlockHeightException -> { + buf.putInt(8) + Unit + } + is MercuryException.BitcoinConsensusEncodeException -> { + buf.putInt(9) + Unit + } + is MercuryException.MusigNonceGenException -> { + buf.putInt(10) + Unit + } + is MercuryException.InvalidStatechainAddressException -> { + buf.putInt(11) + Unit + } + is MercuryException.InvalidBitcoinAddressException -> { + buf.putInt(12) + Unit + } + is MercuryException.StatechainAddressMismatchNetworkException -> { + buf.putInt(13) + Unit + } + is MercuryException.BitcoinAddressMismatchNetworkException -> { + buf.putInt(14) + Unit + } + is MercuryException.BitcoinAddressException -> { + buf.putInt(15) + Unit + } + is MercuryException.BitcoinAbsoluteException -> { + buf.putInt(16) + Unit + } + is MercuryException.BitcoinHashHexException -> { + buf.putInt(17) + Unit + } + is MercuryException.BitcoinPsbtException -> { + buf.putInt(18) + Unit + } + is MercuryException.SighashTypeParseException -> { + buf.putInt(19) + Unit + } + is MercuryException.BitcoinSighashException -> { + buf.putInt(20) + Unit + } + is MercuryException.ParseException -> { + buf.putInt(21) + Unit + } + is MercuryException.MusigSignException -> { + buf.putInt(22) + Unit + } + is MercuryException.SchnorrSignatureValidationException -> { + buf.putInt(23) + Unit + } + is MercuryException.MoreThanOneInputException -> { + buf.putInt(24) + Unit + } + is MercuryException.UnkownNetwork -> { + buf.putInt(25) + Unit + } + is MercuryException.BackupTransactionDoesNotPayUser -> { + buf.putInt(26) + Unit + } + is MercuryException.FeeTooHigh -> { + buf.putInt(27) + Unit + } + is MercuryException.FeeTooLow -> { + buf.putInt(28) + Unit + } + is MercuryException.OutOfRangeException -> { + buf.putInt(29) + Unit + } + is MercuryException.SerdeJsonException -> { + buf.putInt(30) + Unit + } + is MercuryException.SecpException -> { + buf.putInt(31) + Unit + } + is MercuryException.NoBackupTransactionFound -> { + buf.putInt(32) + Unit + } + is MercuryException.Tx1HasMoreThanOneInput -> { + buf.putInt(33) + Unit + } + is MercuryException.InvalidSignature -> { + buf.putInt(34) + Unit + } + is MercuryException.EmptyWitness -> { + buf.putInt(35) + Unit + } + is MercuryException.EmptyWitnessData -> { + buf.putInt(36) + Unit + } + is MercuryException.IncorrectChallenge -> { + buf.putInt(37) + Unit + } + is MercuryException.InvalidT1 -> { + buf.putInt(38) + Unit + } + is MercuryException.IncorrectAggregatedPublicKey -> { + buf.putInt(39) + Unit + } + is MercuryException.T1MustBeExactly32BytesException -> { + buf.putInt(40) + Unit + } + is MercuryException.NoX1Pub -> { + buf.putInt(41) + Unit + } + is MercuryException.NoAggregatedPubkeyException -> { + buf.putInt(42) + Unit + } + is MercuryException.CoinNotFound -> { + buf.putInt(43) + Unit + } + is MercuryException.SignatureSchemeValidationException -> { + buf.putInt(44) + Unit + } + is MercuryException.NoPreviousLockTimeException -> { + buf.putInt(45) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +@Serializable +enum class TransferReceiverError { + @SerialName("StatecoinBatchLockedError") + STATECOIN_BATCH_LOCKED_ERROR, + @SerialName("ExpiredBatchTimeError") + EXPIRED_BATCH_TIME_ERROR, + ; + + companion object +} + +public object FfiConverterTypeTransferReceiverError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = + try { + TransferReceiverError.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: TransferReceiverError) = 4UL + + override fun write( + value: TransferReceiverError, + buf: ByteBuffer, + ) { + buf.putInt(value.ordinal + 1) + } +} + +public object FfiConverterOptionalUInt : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.UInt? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterUInt.read(buf) + } + + override fun allocationSize(value: kotlin.UInt?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterUInt.allocationSize(value) + } + } + + override fun write( + value: kotlin.UInt?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterUInt.write(value, buf) + } + } +} + +public object FfiConverterOptionalString : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.String? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterString.read(buf) + } + + override fun allocationSize(value: kotlin.String?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterString.allocationSize(value) + } + } + + override fun write( + value: kotlin.String?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterString.write(value, buf) + } + } +} + +public object FfiConverterSequenceString : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterString.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterString.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeActivity : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeActivity.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeActivity.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeActivity.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeBackupTx : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeBackupTx.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeBackupTx.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeBackupTx.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeCoin : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeCoin.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeCoin.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeCoin.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypePubKeyInfo : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypePubKeyInfo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypePubKeyInfo.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypePubKeyInfo.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeStatechainInfo : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeStatechainInfo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeStatechainInfo.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeStatechainInfo.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeToken : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeToken.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeToken.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeToken.write(it, buf) + } + } +} + +@Throws(MercuryException::class) +fun `createAggregatedAddress`( + `coin`: Coin, + `network`: kotlin.String, +): AggregatedPublicKey { + return FfiConverterTypeAggregatedPublicKey.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_aggregated_address( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createAndCommitNonces`(`coin`: Coin): CoinNonce { + return FfiConverterTypeCoinNonce.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_and_commit_nonces( + FfiConverterTypeCoin.lower(`coin`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createCpfpTx`( + `backupTx`: BackupTx, + `coin`: Coin, + `toAddress`: kotlin.String, + `feeRateSatsPerByte`: kotlin.ULong, + `network`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_cpfp_tx( + FfiConverterTypeBackupTx.lower(`backupTx`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`toAddress`), + FfiConverterULong.lower(`feeRateSatsPerByte`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createDepositMsg1`( + `coin`: Coin, + `tokenId`: kotlin.String, +): DepositMsg1 { + return FfiConverterTypeDepositMsg1.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_deposit_msg1( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`tokenId`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createSignature`( + `msg`: kotlin.String, + `clientPartialSigHex`: kotlin.String, + `serverPartialSigHex`: kotlin.String, + `sessionHex`: kotlin.String, + `outputPubkeyHex`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_signature( + FfiConverterString.lower(`msg`), + FfiConverterString.lower(`clientPartialSigHex`), + FfiConverterString.lower(`serverPartialSigHex`), + FfiConverterString.lower(`sessionHex`), + FfiConverterString.lower(`outputPubkeyHex`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createTransferSignature`( + `recipientAddress`: kotlin.String, + `inputTxid`: kotlin.String, + `inputVout`: kotlin.UInt, + `clientSeckey`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_transfer_signature( + FfiConverterString.lower(`recipientAddress`), + FfiConverterString.lower(`inputTxid`), + FfiConverterUInt.lower(`inputVout`), + FfiConverterString.lower(`clientSeckey`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createTransferUpdateMsg`( + `x1`: kotlin.String, + `recipientAddress`: kotlin.String, + `coin`: Coin, + `transferSignature`: kotlin.String, + `backupTransactions`: List, +): TransferUpdateMsgRequestPayload { + return FfiConverterTypeTransferUpdateMsgRequestPayload.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_transfer_update_msg( + FfiConverterString.lower(`x1`), + FfiConverterString.lower(`recipientAddress`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`transferSignature`), + FfiConverterSequenceTypeBackupTx.lower(`backupTransactions`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `decodeStatechainAddress`(`scAddress`: kotlin.String): DecodedScAddress { + return FfiConverterTypeDecodedSCAddress.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_decode_statechain_address( + FfiConverterString.lower(`scAddress`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `duplicateCoinToInitializedState`( + `wallet`: Wallet, + `authPubkey`: kotlin.String, +): Coin { + return FfiConverterTypeCoin.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_duplicate_coin_to_initialized_state( + FfiConverterTypeWallet.lower(`wallet`), + FfiConverterString.lower(`authPubkey`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `ffiValidateSignatureScheme`( + `ffiTransferMsg`: FfiTransferMsg, + `statechainInfo`: StatechainInfoResponsePayload, + `tx0Hex`: kotlin.String, + `feeRateTolerance`: kotlin.UInt, + `currentFeeRateSatsPerByte`: kotlin.UInt, + `interval`: kotlin.UInt, +): kotlin.UInt { + return FfiConverterUInt.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_ffi_validate_signature_scheme( + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterTypeStatechainInfoResponsePayload.lower(`statechainInfo`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterUInt.lower(`feeRateTolerance`), + FfiConverterUInt.lower(`currentFeeRateSatsPerByte`), + FfiConverterUInt.lower(`interval`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `ffiVerifyTransferSignature`( + `newUserPubkey`: kotlin.String, + `tx0Outpoint`: TxOutpoint, + `ffiTransferMsg`: FfiTransferMsg, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_ffi_verify_transfer_signature( + FfiConverterString.lower(`newUserPubkey`), + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiCreateTransferReceiverRequestPayload`( + `statechainInfo`: StatechainInfoResponsePayload, + `ffiTransferMsg`: FfiTransferMsg, + `coin`: Coin, +): TransferReceiverRequestPayload { + return FfiConverterTypeTransferReceiverRequestPayload.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_create_transfer_receiver_request_payload( + FfiConverterTypeStatechainInfoResponsePayload.lower(`statechainInfo`), + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterTypeCoin.lower(`coin`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiDecryptTransferMsg`( + `encryptedMessage`: kotlin.String, + `privateKeyWif`: kotlin.String, +): FfiTransferMsg { + return FfiConverterTypeFFITransferMsg.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_decrypt_transfer_msg( + FfiConverterString.lower(`encryptedMessage`), + FfiConverterString.lower(`privateKeyWif`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiValidateTx0OutputPubkey`( + `enclavePublicKey`: kotlin.String, + `ffiTransferMsg`: FfiTransferMsg, + `tx0Outpoint`: TxOutpoint, + `tx0Hex`: kotlin.String, + `network`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_validate_tx0_output_pubkey( + FfiConverterString.lower(`enclavePublicKey`), + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiVerifyLatestBackupTxPaysToUserPubkey`( + `ffiTransferMsg`: FfiTransferMsg, + `clientPubkeyShare`: kotlin.String, + `network`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_verify_latest_backup_tx_pays_to_user_pubkey( + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterString.lower(`clientPubkeyShare`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `generateMnemonic`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_generate_mnemonic( + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getBlockheight`(`bkpTx`: BackupTx): kotlin.UInt { + return FfiConverterUInt.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_blockheight( + FfiConverterTypeBackupTx.lower(`bkpTx`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getNewCoin`(`wallet`: Wallet): Coin { + return FfiConverterTypeCoin.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_new_coin( + FfiConverterTypeWallet.lower(`wallet`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getNewKeyInfo`( + `serverPublicKeyHex`: kotlin.String, + `coin`: Coin, + `statechainId`: kotlin.String, + `tx0Outpoint`: TxOutpoint, + `tx0Hex`: kotlin.String, + `network`: kotlin.String, +): NewKeyInfo { + return FfiConverterTypeNewKeyInfo.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_new_key_info( + FfiConverterString.lower(`serverPublicKeyHex`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`statechainId`), + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getOutputAddressFromTx0`( + `tx0Outpoint`: TxOutpoint, + `tx0Hex`: kotlin.String, + `network`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_output_address_from_tx0( + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getPartialSigRequest`( + `coin`: Coin, + `blockHeight`: kotlin.UInt, + `initlock`: kotlin.UInt, + `interval`: kotlin.UInt, + `feeRateSatsPerByte`: kotlin.UInt, + `qtBackupTx`: kotlin.UInt, + `toAddress`: kotlin.String, + `network`: kotlin.String, + `isWithdrawal`: kotlin.Boolean, +): PartialSignatureMsg1 { + return FfiConverterTypePartialSignatureMsg1.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_partial_sig_request( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterUInt.lower(`blockHeight`), + FfiConverterUInt.lower(`initlock`), + FfiConverterUInt.lower(`interval`), + FfiConverterUInt.lower(`feeRateSatsPerByte`), + FfiConverterUInt.lower(`qtBackupTx`), + FfiConverterString.lower(`toAddress`), + FfiConverterString.lower(`network`), + FfiConverterBoolean.lower(`isWithdrawal`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getTx0Outpoint`(`backupTransactions`: List): TxOutpoint { + return FfiConverterTypeTxOutpoint.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_tx0_outpoint( + FfiConverterSequenceTypeBackupTx.lower(`backupTransactions`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getUserBackupAddress`( + `coin`: Coin, + `network`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_user_backup_address( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `handleDepositMsg1Response`( + `coin`: Coin, + `depositMsg1Response`: DepositMsg1Response, +): DepositInitResult { + return FfiConverterTypeDepositInitResult.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_handle_deposit_msg_1_response( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterTypeDepositMsg1Response.lower(`depositMsg1Response`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `isEnclavePubkeyPartOfCoin`( + `coin`: Coin, + `enclavePubkey`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_is_enclave_pubkey_part_of_coin( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`enclavePubkey`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `latestBackupTxPaysToUserPubkey`( + `backupTxs`: List, + `coin`: Coin, + `network`: kotlin.String, +): BackupTx { + return FfiConverterTypeBackupTx.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_latest_backup_tx_pays_to_user_pubkey( + FfiConverterSequenceTypeBackupTx.lower(`backupTxs`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `newBackupTransaction`( + `encodedUnsignedTx`: kotlin.String, + `signatureHex`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_new_backup_transaction( + FfiConverterString.lower(`encodedUnsignedTx`), + FfiConverterString.lower(`signatureHex`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `signMessage`( + `message`: kotlin.String, + `coin`: Coin, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_sign_message( + FfiConverterString.lower(`message`), + FfiConverterTypeCoin.lower(`coin`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `validateAddress`( + `address`: kotlin.String, + `network`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_validate_address( + FfiConverterString.lower(`address`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `verifyBlindedMusigScheme`( + `backupTx`: BackupTx, + `tx0Hex`: kotlin.String, + `statechainInfo`: StatechainInfo, +) = uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_verify_blinded_musig_scheme( + FfiConverterTypeBackupTx.lower(`backupTx`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterTypeStatechainInfo.lower(`statechainInfo`), + _status, + ) +} + +@Throws(MercuryException::class) +fun `verifyTransactionSignature`( + `txNHex`: kotlin.String, + `tx0Hex`: kotlin.String, + `feeRateTolerance`: kotlin.UInt, + `currentFeeRateSatsPerByte`: kotlin.UInt, +) = uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_verify_transaction_signature( + FfiConverterString.lower(`txNHex`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterUInt.lower(`feeRateTolerance`), + FfiConverterUInt.lower(`currentFeeRateSatsPerByte`), + _status, + ) +} diff --git a/clients/apps/kotlin/src/main/kotlinotlin b/clients/apps/kotlin/src/main/kotlinotlin new file mode 100644 index 00000000..41e4238e --- /dev/null +++ b/clients/apps/kotlin/src/main/kotlinotlin @@ -0,0 +1,4611 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Callback +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.ptr.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicLong + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + + @JvmField var len: Long = 0 + + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + + class ByReference : RustBuffer(), Structure.ByReference + + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + + companion object { + internal fun alloc(size: ULong = 0UL) = + uniffiRustCall { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.ffi_mercurylib_rustbuffer_alloc(size.toLong(), status) + }.also { + if (it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=$size)") + } + } + + internal fun create( + capacity: ULong, + len: ULong, + data: Pointer?, + ): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity.toLong() + buf.len = len.toLong() + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = + uniffiRustCall { status -> + UniffiLib.INSTANCE.ffi_mercurylib_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} + +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): ULong + + // Write a Kotlin type to a `ByteBuffer` + fun write( + value: KotlinType, + buf: ByteBuffer, + ) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = + rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position().toLong()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer : FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + +@Structure.FieldOrder("code", "error_buf") +internal open class UniffiRustCallStatus : Structure() { + @JvmField var code: Byte = 0 + + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue : UniffiRustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == UNIFFI_CALL_SUCCESS + } + + fun isError(): Boolean { + return code == UNIFFI_CALL_ERROR + } + + fun isPanic(): Boolean { + return code == UNIFFI_CALL_UNEXPECTED_ERROR + } + + companion object { + fun create( + code: Byte, + errorBuf: RustBuffer.ByValue, + ): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface UniffiRustCallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun uniffiRustCallWithError( + errorHandler: UniffiRustCallStatusErrorHandler, + callback: (UniffiRustCallStatus) -> U, +): U { + var status = UniffiRustCallStatus() + val return_value = callback(status) + uniffiCheckCallStatus(errorHandler, status) + return return_value +} + +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun uniffiCheckCallStatus( + errorHandler: UniffiRustCallStatusErrorHandler, + status: UniffiRustCallStatus, +) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object UniffiNullRustCallStatusErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { + return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback) +} + +internal inline fun uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch (e: Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } +} + +internal inline fun uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue, +) { + try { + writeReturn(makeCall()) + } catch (e: Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } + } +} + +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap { + private val map = ConcurrentHashMap() + private val counter = java.util.concurrent.atomic.AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T { + return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + } + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T { + return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") + } +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "mercurylib" +} + +private inline fun loadIndirect(componentName: String): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// Define FFI callback types +internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback { + fun callback( + `data`: Long, + `pollResult`: Byte, + ) +} + +internal interface UniffiForeignFutureFree : com.sun.jna.Callback { + fun callback(`handle`: Long) +} + +internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback { + fun callback(`handle`: Long) +} + +@Structure.FieldOrder("handle", "free") +internal open class UniffiForeignFuture( + @JvmField internal var `handle`: Long = 0.toLong(), + @JvmField internal var `free`: UniffiForeignFutureFree? = null, +) : Structure() { + class UniffiByValue( + `handle`: Long = 0.toLong(), + `free`: UniffiForeignFutureFree? = null, + ) : UniffiForeignFuture(`handle`, `free`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFuture) { + `handle` = other.`handle` + `free` = other.`free` + } +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU8(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU8.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI8(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI8.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU16(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU16.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI16(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI16.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU32(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI32(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructU64(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructU64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructI64(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructI64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF32( + @JvmField internal var `returnValue`: Float = 0.0f, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Float = 0.0f, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructF32(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructF32.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF64( + @JvmField internal var `returnValue`: Double = 0.0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Double = 0.0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructF64(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructF64.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructPointer( + @JvmField internal var `returnValue`: Pointer = Pointer.NULL, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Pointer = Pointer.NULL, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructPointer(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructPointer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompletePointer : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructPointer.UniffiByValue, + ) +} + +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructRustBuffer( + @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructRustBuffer(`returnValue`, `callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructRustBuffer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructRustBuffer.UniffiByValue, + ) +} + +@Structure.FieldOrder("callStatus") +internal open class UniffiForeignFutureStructVoid( + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ) : UniffiForeignFutureStructVoid(`callStatus`), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructVoid) { + `callStatus` = other.`callStatus` + } +} + +internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + fun callback( + `callbackData`: Long, + `result`: UniffiForeignFutureStructVoid.UniffiByValue, + ) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface UniffiLib : Library { + companion object { + internal val INSTANCE: UniffiLib by lazy { + loadIndirect(componentName = "mercurylib") + .also { lib: UniffiLib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + } + } + } + + fun uniffi_mercurylib_fn_func_create_aggregated_address( + `coin`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_and_commit_nonces( + `coin`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_cpfp_tx( + `backupTx`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `toAddress`: RustBuffer.ByValue, + `feeRateSatsPerByte`: Long, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_deposit_msg1( + `coin`: RustBuffer.ByValue, + `tokenId`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_signature( + `msg`: RustBuffer.ByValue, + `clientPartialSigHex`: RustBuffer.ByValue, + `serverPartialSigHex`: RustBuffer.ByValue, + `sessionHex`: RustBuffer.ByValue, + `outputPubkeyHex`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_transfer_signature( + `recipientAddress`: RustBuffer.ByValue, + `inputTxid`: RustBuffer.ByValue, + `inputVout`: Int, + `clientSeckey`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_create_transfer_update_msg( + `x1`: RustBuffer.ByValue, + `recipientAddress`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `transferSignature`: RustBuffer.ByValue, + `backupTransactions`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_decode_statechain_address( + `scAddress`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_duplicate_coin_to_initialized_state( + `wallet`: RustBuffer.ByValue, + `authPubkey`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_ffi_validate_signature_scheme( + `ffiTransferMsg`: RustBuffer.ByValue, + `statechainInfo`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `feeRateTolerance`: Int, + `currentFeeRateSatsPerByte`: Int, + `interval`: Int, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun uniffi_mercurylib_fn_func_ffi_verify_transfer_signature( + `newUserPubkey`: RustBuffer.ByValue, + `tx0Outpoint`: RustBuffer.ByValue, + `ffiTransferMsg`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_fii_create_transfer_receiver_request_payload( + `statechainInfo`: RustBuffer.ByValue, + `ffiTransferMsg`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_fii_decrypt_transfer_msg( + `encryptedMessage`: RustBuffer.ByValue, + `privateKeyWif`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_fii_validate_tx0_output_pubkey( + `enclavePublicKey`: RustBuffer.ByValue, + `ffiTransferMsg`: RustBuffer.ByValue, + `tx0Outpoint`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_fii_verify_latest_backup_tx_pays_to_user_pubkey( + `ffiTransferMsg`: RustBuffer.ByValue, + `clientPubkeyShare`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_generate_mnemonic(uniffi_out_err: UniffiRustCallStatus): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_blockheight( + `bkpTx`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun uniffi_mercurylib_fn_func_get_new_coin( + `wallet`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_new_key_info( + `serverPublicKeyHex`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `statechainId`: RustBuffer.ByValue, + `tx0Outpoint`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_output_address_from_tx0( + `tx0Outpoint`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_partial_sig_request( + `coin`: RustBuffer.ByValue, + `blockHeight`: Int, + `initlock`: Int, + `interval`: Int, + `feeRateSatsPerByte`: Int, + `qtBackupTx`: Int, + `toAddress`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + `isWithdrawal`: Byte, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_tx0_outpoint( + `backupTransactions`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_get_user_backup_address( + `coin`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_handle_deposit_msg_1_response( + `coin`: RustBuffer.ByValue, + `depositMsg1Response`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_is_enclave_pubkey_part_of_coin( + `coin`: RustBuffer.ByValue, + `enclavePubkey`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_latest_backup_tx_pays_to_user_pubkey( + `backupTxs`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_new_backup_transaction( + `encodedUnsignedTx`: RustBuffer.ByValue, + `signatureHex`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_sign_message( + `message`: RustBuffer.ByValue, + `coin`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun uniffi_mercurylib_fn_func_validate_address( + `address`: RustBuffer.ByValue, + `network`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun uniffi_mercurylib_fn_func_verify_blinded_musig_scheme( + `backupTx`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `statechainInfo`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_mercurylib_fn_func_verify_transaction_signature( + `txNHex`: RustBuffer.ByValue, + `tx0Hex`: RustBuffer.ByValue, + `feeRateTolerance`: Int, + `currentFeeRateSatsPerByte`: Int, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun ffi_mercurylib_rustbuffer_alloc( + `size`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rustbuffer_from_bytes( + `bytes`: ForeignBytes.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rustbuffer_free( + `buf`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun ffi_mercurylib_rustbuffer_reserve( + `buf`: RustBuffer.ByValue, + `additional`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rust_future_poll_u8( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u8( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun ffi_mercurylib_rust_future_poll_i8( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i8(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i8( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Byte + + fun ffi_mercurylib_rust_future_poll_u16( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u16( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Short + + fun ffi_mercurylib_rust_future_poll_i16( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i16(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i16( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Short + + fun ffi_mercurylib_rust_future_poll_u32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun ffi_mercurylib_rust_future_poll_i32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Int + + fun ffi_mercurylib_rust_future_poll_u64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_u64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_u64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_u64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Long + + fun ffi_mercurylib_rust_future_poll_i64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_i64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_i64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_i64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Long + + fun ffi_mercurylib_rust_future_poll_f32( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_f32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_f32(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_f32( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Float + + fun ffi_mercurylib_rust_future_poll_f64( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_f64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_f64(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_f64( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Double + + fun ffi_mercurylib_rust_future_poll_pointer( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_pointer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_pointer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_pointer( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun ffi_mercurylib_rust_future_poll_rust_buffer( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_rust_buffer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_rust_buffer(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_rust_buffer( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + + fun ffi_mercurylib_rust_future_poll_void( + `handle`: Long, + `callback`: UniffiRustFutureContinuationCallback, + `callbackData`: Long, + ): Unit + + fun ffi_mercurylib_rust_future_cancel_void(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_free_void(`handle`: Long): Unit + + fun ffi_mercurylib_rust_future_complete_void( + `handle`: Long, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_mercurylib_checksum_func_create_aggregated_address(): Short + + fun uniffi_mercurylib_checksum_func_create_and_commit_nonces(): Short + + fun uniffi_mercurylib_checksum_func_create_cpfp_tx(): Short + + fun uniffi_mercurylib_checksum_func_create_deposit_msg1(): Short + + fun uniffi_mercurylib_checksum_func_create_signature(): Short + + fun uniffi_mercurylib_checksum_func_create_transfer_signature(): Short + + fun uniffi_mercurylib_checksum_func_create_transfer_update_msg(): Short + + fun uniffi_mercurylib_checksum_func_decode_statechain_address(): Short + + fun uniffi_mercurylib_checksum_func_duplicate_coin_to_initialized_state(): Short + + fun uniffi_mercurylib_checksum_func_ffi_validate_signature_scheme(): Short + + fun uniffi_mercurylib_checksum_func_ffi_verify_transfer_signature(): Short + + fun uniffi_mercurylib_checksum_func_fii_create_transfer_receiver_request_payload(): Short + + fun uniffi_mercurylib_checksum_func_fii_decrypt_transfer_msg(): Short + + fun uniffi_mercurylib_checksum_func_fii_validate_tx0_output_pubkey(): Short + + fun uniffi_mercurylib_checksum_func_fii_verify_latest_backup_tx_pays_to_user_pubkey(): Short + + fun uniffi_mercurylib_checksum_func_generate_mnemonic(): Short + + fun uniffi_mercurylib_checksum_func_get_blockheight(): Short + + fun uniffi_mercurylib_checksum_func_get_new_coin(): Short + + fun uniffi_mercurylib_checksum_func_get_new_key_info(): Short + + fun uniffi_mercurylib_checksum_func_get_output_address_from_tx0(): Short + + fun uniffi_mercurylib_checksum_func_get_partial_sig_request(): Short + + fun uniffi_mercurylib_checksum_func_get_tx0_outpoint(): Short + + fun uniffi_mercurylib_checksum_func_get_user_backup_address(): Short + + fun uniffi_mercurylib_checksum_func_handle_deposit_msg_1_response(): Short + + fun uniffi_mercurylib_checksum_func_is_enclave_pubkey_part_of_coin(): Short + + fun uniffi_mercurylib_checksum_func_latest_backup_tx_pays_to_user_pubkey(): Short + + fun uniffi_mercurylib_checksum_func_new_backup_transaction(): Short + + fun uniffi_mercurylib_checksum_func_sign_message(): Short + + fun uniffi_mercurylib_checksum_func_validate_address(): Short + + fun uniffi_mercurylib_checksum_func_verify_blinded_musig_scheme(): Short + + fun uniffi_mercurylib_checksum_func_verify_transaction_signature(): Short + + fun ffi_mercurylib_uniffi_contract_version(): Int +} + +private fun uniffiCheckContractApiVersion(lib: UniffiLib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 26 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_mercurylib_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: UniffiLib) { + if (lib.uniffi_mercurylib_checksum_func_create_aggregated_address() != 44269.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_and_commit_nonces() != 16584.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_cpfp_tx() != 63811.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_deposit_msg1() != 9767.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_signature() != 53021.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_transfer_signature() != 61677.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_create_transfer_update_msg() != 6918.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_decode_statechain_address() != 7125.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_duplicate_coin_to_initialized_state() != 30591.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_ffi_validate_signature_scheme() != 26248.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_ffi_verify_transfer_signature() != 18534.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_create_transfer_receiver_request_payload() != 58308.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_decrypt_transfer_msg() != 44515.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_validate_tx0_output_pubkey() != 51706.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_fii_verify_latest_backup_tx_pays_to_user_pubkey() != 46083.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_generate_mnemonic() != 62910.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_blockheight() != 5222.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_new_coin() != 45841.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_new_key_info() != 64987.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_output_address_from_tx0() != 62309.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_partial_sig_request() != 13111.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_tx0_outpoint() != 21467.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_get_user_backup_address() != 29075.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_handle_deposit_msg_1_response() != 64110.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_is_enclave_pubkey_part_of_coin() != 37041.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_latest_backup_tx_pays_to_user_pubkey() != 19689.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_new_backup_transaction() != 56642.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_sign_message() != 9994.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_validate_address() != 16334.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_verify_blinded_musig_scheme() != 42963.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_mercurylib_checksum_func_verify_transaction_signature() != 32006.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// Async support + +// Public interface members begin here. + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */ +object NoPointer + +public object FfiConverterUByte : FfiConverter { + override fun lift(value: Byte): UByte { + return value.toUByte() + } + + override fun read(buf: ByteBuffer): UByte { + return lift(buf.get()) + } + + override fun lower(value: UByte): Byte { + return value.toByte() + } + + override fun allocationSize(value: UByte) = 1UL + + override fun write( + value: UByte, + buf: ByteBuffer, + ) { + buf.put(value.toByte()) + } +} + +public object FfiConverterUInt : FfiConverter { + override fun lift(value: Int): UInt { + return value.toUInt() + } + + override fun read(buf: ByteBuffer): UInt { + return lift(buf.getInt()) + } + + override fun lower(value: UInt): Int { + return value.toInt() + } + + override fun allocationSize(value: UInt) = 4UL + + override fun write( + value: UInt, + buf: ByteBuffer, + ) { + buf.putInt(value.toInt()) + } +} + +public object FfiConverterULong : FfiConverter { + override fun lift(value: Long): ULong { + return value.toULong() + } + + override fun read(buf: ByteBuffer): ULong { + return lift(buf.getLong()) + } + + override fun lower(value: ULong): Long { + return value.toLong() + } + + override fun allocationSize(value: ULong) = 8UL + + override fun write( + value: ULong, + buf: ByteBuffer, + ) { + buf.putLong(value.toLong()) + } +} + +public object FfiConverterBoolean : FfiConverter { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1UL + + override fun write( + value: Boolean, + buf: ByteBuffer, + ) { + buf.put(lower(value)) + } +} + +public object FfiConverterString : FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len.toInt()) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL + return sizeForLength + sizeForString + } + + override fun write( + value: String, + buf: ByteBuffer, + ) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} + +public object FfiConverterByteArray : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ByteArray { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr + } + + override fun allocationSize(value: ByteArray): ULong { + return 4UL + value.size.toULong() + } + + override fun write( + value: ByteArray, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + buf.put(value) + } +} + +@Serializable +data class Activity( + var `utxo`: kotlin.String, + var `amount`: kotlin.UInt, + var `action`: kotlin.String, + var `date`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeActivity : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Activity { + return Activity( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: Activity) = + ( + FfiConverterString.allocationSize(value.`utxo`) + + FfiConverterUInt.allocationSize(value.`amount`) + + FfiConverterString.allocationSize(value.`action`) + + FfiConverterString.allocationSize(value.`date`) + ) + + override fun write( + value: Activity, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`utxo`, buf) + FfiConverterUInt.write(value.`amount`, buf) + FfiConverterString.write(value.`action`, buf) + FfiConverterString.write(value.`date`, buf) + } +} + +@Serializable +data class AggregatedPublicKey( + var `aggregatePubkey`: kotlin.String, + var `aggregateAddress`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeAggregatedPublicKey : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AggregatedPublicKey { + return AggregatedPublicKey( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: AggregatedPublicKey) = + ( + FfiConverterString.allocationSize(value.`aggregatePubkey`) + + FfiConverterString.allocationSize(value.`aggregateAddress`) + ) + + override fun write( + value: AggregatedPublicKey, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`aggregatePubkey`, buf) + FfiConverterString.write(value.`aggregateAddress`, buf) + } +} + +@Serializable +data class BackupTx( + var `txN`: kotlin.UInt, + var `tx`: kotlin.String, + var `clientPublicNonce`: kotlin.String, + var `serverPublicNonce`: kotlin.String, + var `clientPublicKey`: kotlin.String, + var `serverPublicKey`: kotlin.String, + var `blindingFactor`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeBackupTx : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): BackupTx { + return BackupTx( + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: BackupTx) = + ( + FfiConverterUInt.allocationSize(value.`txN`) + + FfiConverterString.allocationSize(value.`tx`) + + FfiConverterString.allocationSize(value.`clientPublicNonce`) + + FfiConverterString.allocationSize(value.`serverPublicNonce`) + + FfiConverterString.allocationSize(value.`clientPublicKey`) + + FfiConverterString.allocationSize(value.`serverPublicKey`) + + FfiConverterString.allocationSize(value.`blindingFactor`) + ) + + override fun write( + value: BackupTx, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`txN`, buf) + FfiConverterString.write(value.`tx`, buf) + FfiConverterString.write(value.`clientPublicNonce`, buf) + FfiConverterString.write(value.`serverPublicNonce`, buf) + FfiConverterString.write(value.`clientPublicKey`, buf) + FfiConverterString.write(value.`serverPublicKey`, buf) + FfiConverterString.write(value.`blindingFactor`, buf) + } +} + +@Serializable +data class Coin( + var `index`: kotlin.UInt, + var `userPrivkey`: kotlin.String, + var `userPubkey`: kotlin.String, + var `authPrivkey`: kotlin.String, + var `authPubkey`: kotlin.String, + var `derivationPath`: kotlin.String, + var `fingerprint`: kotlin.String, + /** + * The coin address is the user_pubkey || auth_pubkey + * Used to transfer the coin to another wallet + */ + var `address`: kotlin.String, + /** + * The backup address is the address used in backup transactions + * The backup address is the p2tr address of the user_pubkey + */ + var `backupAddress`: kotlin.String, + var `serverPubkey`: kotlin.String?, + var `aggregatedPubkey`: kotlin.String?, + /** + * The aggregated address is the P2TR address from aggregated_pubkey + */ + var `aggregatedAddress`: kotlin.String?, + var `utxoTxid`: kotlin.String?, + var `utxoVout`: kotlin.UInt?, + var `amount`: kotlin.UInt?, + var `statechainId`: kotlin.String?, + var `signedStatechainId`: kotlin.String?, + var `locktime`: kotlin.UInt?, + var `secretNonce`: kotlin.String?, + var `publicNonce`: kotlin.String?, + var `blindingFactor`: kotlin.String?, + var `serverPublicNonce`: kotlin.String?, + var `txCpfp`: kotlin.String?, + var `txWithdraw`: kotlin.String?, + var `withdrawalAddress`: kotlin.String?, + var `status`: CoinStatus, +) { + companion object +} + +public object FfiConverterTypeCoin : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Coin { + return Coin( + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalUInt.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterTypeCoinStatus.read(buf), + ) + } + + override fun allocationSize(value: Coin) = + ( + FfiConverterUInt.allocationSize(value.`index`) + + FfiConverterString.allocationSize(value.`userPrivkey`) + + FfiConverterString.allocationSize(value.`userPubkey`) + + FfiConverterString.allocationSize(value.`authPrivkey`) + + FfiConverterString.allocationSize(value.`authPubkey`) + + FfiConverterString.allocationSize(value.`derivationPath`) + + FfiConverterString.allocationSize(value.`fingerprint`) + + FfiConverterString.allocationSize(value.`address`) + + FfiConverterString.allocationSize(value.`backupAddress`) + + FfiConverterOptionalString.allocationSize(value.`serverPubkey`) + + FfiConverterOptionalString.allocationSize(value.`aggregatedPubkey`) + + FfiConverterOptionalString.allocationSize(value.`aggregatedAddress`) + + FfiConverterOptionalString.allocationSize(value.`utxoTxid`) + + FfiConverterOptionalUInt.allocationSize(value.`utxoVout`) + + FfiConverterOptionalUInt.allocationSize(value.`amount`) + + FfiConverterOptionalString.allocationSize(value.`statechainId`) + + FfiConverterOptionalString.allocationSize(value.`signedStatechainId`) + + FfiConverterOptionalUInt.allocationSize(value.`locktime`) + + FfiConverterOptionalString.allocationSize(value.`secretNonce`) + + FfiConverterOptionalString.allocationSize(value.`publicNonce`) + + FfiConverterOptionalString.allocationSize(value.`blindingFactor`) + + FfiConverterOptionalString.allocationSize(value.`serverPublicNonce`) + + FfiConverterOptionalString.allocationSize(value.`txCpfp`) + + FfiConverterOptionalString.allocationSize(value.`txWithdraw`) + + FfiConverterOptionalString.allocationSize(value.`withdrawalAddress`) + + FfiConverterTypeCoinStatus.allocationSize(value.`status`) + ) + + override fun write( + value: Coin, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`index`, buf) + FfiConverterString.write(value.`userPrivkey`, buf) + FfiConverterString.write(value.`userPubkey`, buf) + FfiConverterString.write(value.`authPrivkey`, buf) + FfiConverterString.write(value.`authPubkey`, buf) + FfiConverterString.write(value.`derivationPath`, buf) + FfiConverterString.write(value.`fingerprint`, buf) + FfiConverterString.write(value.`address`, buf) + FfiConverterString.write(value.`backupAddress`, buf) + FfiConverterOptionalString.write(value.`serverPubkey`, buf) + FfiConverterOptionalString.write(value.`aggregatedPubkey`, buf) + FfiConverterOptionalString.write(value.`aggregatedAddress`, buf) + FfiConverterOptionalString.write(value.`utxoTxid`, buf) + FfiConverterOptionalUInt.write(value.`utxoVout`, buf) + FfiConverterOptionalUInt.write(value.`amount`, buf) + FfiConverterOptionalString.write(value.`statechainId`, buf) + FfiConverterOptionalString.write(value.`signedStatechainId`, buf) + FfiConverterOptionalUInt.write(value.`locktime`, buf) + FfiConverterOptionalString.write(value.`secretNonce`, buf) + FfiConverterOptionalString.write(value.`publicNonce`, buf) + FfiConverterOptionalString.write(value.`blindingFactor`, buf) + FfiConverterOptionalString.write(value.`serverPublicNonce`, buf) + FfiConverterOptionalString.write(value.`txCpfp`, buf) + FfiConverterOptionalString.write(value.`txWithdraw`, buf) + FfiConverterOptionalString.write(value.`withdrawalAddress`, buf) + FfiConverterTypeCoinStatus.write(value.`status`, buf) + } +} + +data class CoinNonce( + var `secretNonce`: kotlin.String, + var `publicNonce`: kotlin.String, + var `blindingFactor`: kotlin.String, + var `signFirstRequestPayload`: SignFirstRequestPayload, +) { + companion object +} + +public object FfiConverterTypeCoinNonce : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CoinNonce { + return CoinNonce( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypeSignFirstRequestPayload.read(buf), + ) + } + + override fun allocationSize(value: CoinNonce) = + ( + FfiConverterString.allocationSize(value.`secretNonce`) + + FfiConverterString.allocationSize(value.`publicNonce`) + + FfiConverterString.allocationSize(value.`blindingFactor`) + + FfiConverterTypeSignFirstRequestPayload.allocationSize(value.`signFirstRequestPayload`) + ) + + override fun write( + value: CoinNonce, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`secretNonce`, buf) + FfiConverterString.write(value.`publicNonce`, buf) + FfiConverterString.write(value.`blindingFactor`, buf) + FfiConverterTypeSignFirstRequestPayload.write(value.`signFirstRequestPayload`, buf) + } +} + +class CoinStatusParseError { + override fun equals(other: Any?): Boolean { + return other is CoinStatusParseError + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + companion object +} + +public object FfiConverterTypeCoinStatusParseError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): CoinStatusParseError { + return CoinStatusParseError() + } + + override fun allocationSize(value: CoinStatusParseError) = 0UL + + override fun write( + value: CoinStatusParseError, + buf: ByteBuffer, + ) { + } +} + +data class DecodedScAddress( + var `version`: kotlin.UByte, + var `userPubkey`: kotlin.String, + var `authPubkey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDecodedSCAddress : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DecodedScAddress { + return DecodedScAddress( + FfiConverterUByte.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DecodedScAddress) = + ( + FfiConverterUByte.allocationSize(value.`version`) + + FfiConverterString.allocationSize(value.`userPubkey`) + + FfiConverterString.allocationSize(value.`authPubkey`) + ) + + override fun write( + value: DecodedScAddress, + buf: ByteBuffer, + ) { + FfiConverterUByte.write(value.`version`, buf) + FfiConverterString.write(value.`userPubkey`, buf) + FfiConverterString.write(value.`authPubkey`, buf) + } +} + +@Serializable +data class DepositInitResult( + var `serverPubkey`: kotlin.String, + var `statechainId`: kotlin.String, + var `signedStatechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDepositInitResult : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DepositInitResult { + return DepositInitResult( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DepositInitResult) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + ) + + override fun write( + value: DepositInitResult, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + } +} + +@Serializable +data class DepositMsg1( + @SerialName("auth_key") + var `authKey`: kotlin.String, + @SerialName("token_id") + var `tokenId`: kotlin.String, + @SerialName("signed_token_id") + var `signedTokenId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDepositMsg1 : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DepositMsg1 { + return DepositMsg1( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DepositMsg1) = + ( + FfiConverterString.allocationSize(value.`authKey`) + + FfiConverterString.allocationSize(value.`tokenId`) + + FfiConverterString.allocationSize(value.`signedTokenId`) + ) + + override fun write( + value: DepositMsg1, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`authKey`, buf) + FfiConverterString.write(value.`tokenId`, buf) + FfiConverterString.write(value.`signedTokenId`, buf) + } +} + +@Serializable +data class DepositMsg1Response( + @SerialName("server_pubkey") + var `serverPubkey`: kotlin.String, + @SerialName("statechain_id") + var `statechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeDepositMsg1Response : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DepositMsg1Response { + return DepositMsg1Response( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DepositMsg1Response) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + + FfiConverterString.allocationSize(value.`statechainId`) + ) + + override fun write( + value: DepositMsg1Response, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + FfiConverterString.write(value.`statechainId`, buf) + } +} + +data class FfiTransferMsg( + var `statechainId`: kotlin.String, + var `transferSignature`: kotlin.String, + var `backupTransactions`: List, + var `t1`: kotlin.ByteArray, + var `userPublicKey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeFFITransferMsg : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiTransferMsg { + return FfiTransferMsg( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterSequenceTypeBackupTx.read(buf), + FfiConverterByteArray.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: FfiTransferMsg) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`transferSignature`) + + FfiConverterSequenceTypeBackupTx.allocationSize(value.`backupTransactions`) + + FfiConverterByteArray.allocationSize(value.`t1`) + + FfiConverterString.allocationSize(value.`userPublicKey`) + ) + + override fun write( + value: FfiTransferMsg, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`transferSignature`, buf) + FfiConverterSequenceTypeBackupTx.write(value.`backupTransactions`, buf) + FfiConverterByteArray.write(value.`t1`, buf) + FfiConverterString.write(value.`userPublicKey`, buf) + } +} + +@Serializable +data class GetMsgAddrResponsePayload( + @SerialName("list_enc_transfer_msg") + var `listEncTransferMsg`: List, +) { + companion object +} + +public object FfiConverterTypeGetMsgAddrResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): GetMsgAddrResponsePayload { + return GetMsgAddrResponsePayload( + FfiConverterSequenceString.read(buf), + ) + } + + override fun allocationSize(value: GetMsgAddrResponsePayload) = + ( + FfiConverterSequenceString.allocationSize(value.`listEncTransferMsg`) + ) + + override fun write( + value: GetMsgAddrResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterSequenceString.write(value.`listEncTransferMsg`, buf) + } +} + +data class InfoConfig( + var `initlock`: kotlin.UInt, + var `interval`: kotlin.UInt, + var `feeRateSatsPerByte`: kotlin.ULong, +) { + companion object +} + +public object FfiConverterTypeInfoConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): InfoConfig { + return InfoConfig( + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: InfoConfig) = + ( + FfiConverterUInt.allocationSize(value.`initlock`) + + FfiConverterUInt.allocationSize(value.`interval`) + + FfiConverterULong.allocationSize(value.`feeRateSatsPerByte`) + ) + + override fun write( + value: InfoConfig, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`initlock`, buf) + FfiConverterUInt.write(value.`interval`, buf) + FfiConverterULong.write(value.`feeRateSatsPerByte`, buf) + } +} + +data class KeyListResponsePayload( + var `listKeyinfo`: List, +) { + companion object +} + +public object FfiConverterTypeKeyListResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): KeyListResponsePayload { + return KeyListResponsePayload( + FfiConverterSequenceTypePubKeyInfo.read(buf), + ) + } + + override fun allocationSize(value: KeyListResponsePayload) = + ( + FfiConverterSequenceTypePubKeyInfo.allocationSize(value.`listKeyinfo`) + ) + + override fun write( + value: KeyListResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterSequenceTypePubKeyInfo.write(value.`listKeyinfo`, buf) + } +} + +@Serializable +data class KeyUpdateResponsePayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + var `t2`: kotlin.String, + var `x1`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeKeyUpdateResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): KeyUpdateResponsePayload { + return KeyUpdateResponsePayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: KeyUpdateResponsePayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`t2`) + + FfiConverterString.allocationSize(value.`x1`) + ) + + override fun write( + value: KeyUpdateResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`t2`, buf) + FfiConverterString.write(value.`x1`, buf) + } +} + +data class NewKeyInfo( + var `aggregatePubkey`: kotlin.String, + var `aggregateAddress`: kotlin.String, + var `signedStatechainId`: kotlin.String, + var `amount`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeNewKeyInfo : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): NewKeyInfo { + return NewKeyInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: NewKeyInfo) = + ( + FfiConverterString.allocationSize(value.`aggregatePubkey`) + + FfiConverterString.allocationSize(value.`aggregateAddress`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + + FfiConverterUInt.allocationSize(value.`amount`) + ) + + override fun write( + value: NewKeyInfo, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`aggregatePubkey`, buf) + FfiConverterString.write(value.`aggregateAddress`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + FfiConverterUInt.write(value.`amount`, buf) + } +} + +data class PartialSignatureMsg1( + var `msg`: kotlin.String, + var `outputPubkey`: kotlin.String, + var `clientPartialSig`: kotlin.String, + var `encodedSession`: kotlin.String, + var `encodedUnsignedTx`: kotlin.String, + var `partialSignatureRequestPayload`: PartialSignatureRequestPayload, +) { + companion object +} + +public object FfiConverterTypePartialSignatureMsg1 : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PartialSignatureMsg1 { + return PartialSignatureMsg1( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterTypePartialSignatureRequestPayload.read(buf), + ) + } + + override fun allocationSize(value: PartialSignatureMsg1) = + ( + FfiConverterString.allocationSize(value.`msg`) + + FfiConverterString.allocationSize(value.`outputPubkey`) + + FfiConverterString.allocationSize(value.`clientPartialSig`) + + FfiConverterString.allocationSize(value.`encodedSession`) + + FfiConverterString.allocationSize(value.`encodedUnsignedTx`) + + FfiConverterTypePartialSignatureRequestPayload.allocationSize(value.`partialSignatureRequestPayload`) + ) + + override fun write( + value: PartialSignatureMsg1, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`msg`, buf) + FfiConverterString.write(value.`outputPubkey`, buf) + FfiConverterString.write(value.`clientPartialSig`, buf) + FfiConverterString.write(value.`encodedSession`, buf) + FfiConverterString.write(value.`encodedUnsignedTx`, buf) + FfiConverterTypePartialSignatureRequestPayload.write(value.`partialSignatureRequestPayload`, buf) + } +} + +@Serializable +data class PartialSignatureRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("negate_seckey") + var `negateSeckey`: kotlin.UByte, + var `session`: kotlin.String, + @SerialName("signed_statechain_id") + var `signedStatechainId`: kotlin.String, + @SerialName("server_pub_nonce") + var `serverPubNonce`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypePartialSignatureRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PartialSignatureRequestPayload { + return PartialSignatureRequestPayload( + FfiConverterString.read(buf), + FfiConverterUByte.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PartialSignatureRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterUByte.allocationSize(value.`negateSeckey`) + + FfiConverterString.allocationSize(value.`session`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + + FfiConverterString.allocationSize(value.`serverPubNonce`) + ) + + override fun write( + value: PartialSignatureRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterUByte.write(value.`negateSeckey`, buf) + FfiConverterString.write(value.`session`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + FfiConverterString.write(value.`serverPubNonce`, buf) + } +} + +@Serializable +data class PartialSignatureResponsePayload( + @SerialName("partial_sig") + var `partialSig`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypePartialSignatureResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PartialSignatureResponsePayload { + return PartialSignatureResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PartialSignatureResponsePayload) = + ( + FfiConverterString.allocationSize(value.`partialSig`) + ) + + override fun write( + value: PartialSignatureResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`partialSig`, buf) + } +} + +data class PubKeyInfo( + var `serverPubkey`: kotlin.String, + var `txN`: kotlin.UInt, + var `updatedAt`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypePubKeyInfo : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PubKeyInfo { + return PubKeyInfo( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PubKeyInfo) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + + FfiConverterUInt.allocationSize(value.`txN`) + + FfiConverterString.allocationSize(value.`updatedAt`) + ) + + override fun write( + value: PubKeyInfo, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + FfiConverterUInt.write(value.`txN`, buf) + FfiConverterString.write(value.`updatedAt`, buf) + } +} + +@Serializable +data class ServerConfig( + var `initlock`: kotlin.UInt, + var `interval`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeServerConfig : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ServerConfig { + return ServerConfig( + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: ServerConfig) = + ( + FfiConverterUInt.allocationSize(value.`initlock`) + + FfiConverterUInt.allocationSize(value.`interval`) + ) + + override fun write( + value: ServerConfig, + buf: ByteBuffer, + ) { + FfiConverterUInt.write(value.`initlock`, buf) + FfiConverterUInt.write(value.`interval`, buf) + } +} + +data class ServerPublicNonceResponsePayload( + var `serverPubnonce`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeServerPublicNonceResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ServerPublicNonceResponsePayload { + return ServerPublicNonceResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: ServerPublicNonceResponsePayload) = + ( + FfiConverterString.allocationSize(value.`serverPubnonce`) + ) + + override fun write( + value: ServerPublicNonceResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubnonce`, buf) + } +} + +@Serializable +data class Settings( + var `network`: kotlin.String, + var `blockExplorerUrl`: kotlin.String?, + var `torProxyHost`: kotlin.String?, + var `torProxyPort`: kotlin.String?, + var `torProxyControlPassword`: kotlin.String?, + var `torProxyControlPort`: kotlin.String?, + var `statechainEntityApi`: kotlin.String, + var `torStatechainEntityApi`: kotlin.String?, + var `electrumProtocol`: kotlin.String, + var `electrumHost`: kotlin.String, + var `electrumPort`: kotlin.String, + var `electrumType`: kotlin.String, + var `notifications`: kotlin.Boolean, + var `tutorials`: kotlin.Boolean, +) { + companion object +} + +public object FfiConverterTypeSettings : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Settings { + return Settings( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + ) + } + + override fun allocationSize(value: Settings) = + ( + FfiConverterString.allocationSize(value.`network`) + + FfiConverterOptionalString.allocationSize(value.`blockExplorerUrl`) + + FfiConverterOptionalString.allocationSize(value.`torProxyHost`) + + FfiConverterOptionalString.allocationSize(value.`torProxyPort`) + + FfiConverterOptionalString.allocationSize(value.`torProxyControlPassword`) + + FfiConverterOptionalString.allocationSize(value.`torProxyControlPort`) + + FfiConverterString.allocationSize(value.`statechainEntityApi`) + + FfiConverterOptionalString.allocationSize(value.`torStatechainEntityApi`) + + FfiConverterString.allocationSize(value.`electrumProtocol`) + + FfiConverterString.allocationSize(value.`electrumHost`) + + FfiConverterString.allocationSize(value.`electrumPort`) + + FfiConverterString.allocationSize(value.`electrumType`) + + FfiConverterBoolean.allocationSize(value.`notifications`) + + FfiConverterBoolean.allocationSize(value.`tutorials`) + ) + + override fun write( + value: Settings, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`network`, buf) + FfiConverterOptionalString.write(value.`blockExplorerUrl`, buf) + FfiConverterOptionalString.write(value.`torProxyHost`, buf) + FfiConverterOptionalString.write(value.`torProxyPort`, buf) + FfiConverterOptionalString.write(value.`torProxyControlPassword`, buf) + FfiConverterOptionalString.write(value.`torProxyControlPort`, buf) + FfiConverterString.write(value.`statechainEntityApi`, buf) + FfiConverterOptionalString.write(value.`torStatechainEntityApi`, buf) + FfiConverterString.write(value.`electrumProtocol`, buf) + FfiConverterString.write(value.`electrumHost`, buf) + FfiConverterString.write(value.`electrumPort`, buf) + FfiConverterString.write(value.`electrumType`, buf) + FfiConverterBoolean.write(value.`notifications`, buf) + FfiConverterBoolean.write(value.`tutorials`, buf) + } +} + +@Serializable +data class SignFirstRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("signed_statechain_id") + var `signedStatechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeSignFirstRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SignFirstRequestPayload { + return SignFirstRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SignFirstRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + ) + + override fun write( + value: SignFirstRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + } +} + +@Serializable +data class SignFirstResponsePayload( + @SerialName("server_pubnonce") + var `serverPubnonce`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeSignFirstResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SignFirstResponsePayload { + return SignFirstResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SignFirstResponsePayload) = + ( + FfiConverterString.allocationSize(value.`serverPubnonce`) + ) + + override fun write( + value: SignFirstResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubnonce`, buf) + } +} + +data class StatechainBackupTxs( + var `statechainId`: kotlin.String, + var `backupTxs`: List, +) { + companion object +} + +public object FfiConverterTypeStatechainBackupTxs : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): StatechainBackupTxs { + return StatechainBackupTxs( + FfiConverterString.read(buf), + FfiConverterSequenceTypeBackupTx.read(buf), + ) + } + + override fun allocationSize(value: StatechainBackupTxs) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterSequenceTypeBackupTx.allocationSize(value.`backupTxs`) + ) + + override fun write( + value: StatechainBackupTxs, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterSequenceTypeBackupTx.write(value.`backupTxs`, buf) + } +} + +@Serializable +data class StatechainInfo( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("server_pubnonce") + var `serverPubnonce`: kotlin.String, + var `challenge`: kotlin.String, + @SerialName("tx_n") + var `txN`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeStatechainInfo : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): StatechainInfo { + return StatechainInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: StatechainInfo) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`serverPubnonce`) + + FfiConverterString.allocationSize(value.`challenge`) + + FfiConverterUInt.allocationSize(value.`txN`) + ) + + override fun write( + value: StatechainInfo, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`serverPubnonce`, buf) + FfiConverterString.write(value.`challenge`, buf) + FfiConverterUInt.write(value.`txN`, buf) + } +} + +@Serializable +data class StatechainInfoResponsePayload( + @SerialName("enclave_public_key") + var `enclavePublicKey`: kotlin.String, + @SerialName("num_sigs") + var `numSigs`: kotlin.UInt, + @SerialName("statechain_info") + var `statechainInfo`: List, + @SerialName("x1_pub") + var `x1Pub`: kotlin.String?, +) { + companion object +} + +public object FfiConverterTypeStatechainInfoResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): StatechainInfoResponsePayload { + return StatechainInfoResponsePayload( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterSequenceTypeStatechainInfo.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: StatechainInfoResponsePayload) = + ( + FfiConverterString.allocationSize(value.`enclavePublicKey`) + + FfiConverterUInt.allocationSize(value.`numSigs`) + + FfiConverterSequenceTypeStatechainInfo.allocationSize(value.`statechainInfo`) + + FfiConverterOptionalString.allocationSize(value.`x1Pub`) + ) + + override fun write( + value: StatechainInfoResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`enclavePublicKey`, buf) + FfiConverterUInt.write(value.`numSigs`, buf) + FfiConverterSequenceTypeStatechainInfo.write(value.`statechainInfo`, buf) + FfiConverterOptionalString.write(value.`x1Pub`, buf) + } +} + +@Serializable +data class Token( + @SerialName("btc_payment_address") + var `btcPaymentAddress`: kotlin.String, + var `fee`: kotlin.String, + @SerialName("lightning_invoice") + var `lightningInvoice`: kotlin.String, + @SerialName("processor_id") + var `processorId`: kotlin.String, + @SerialName("token_id") + var `tokenId`: kotlin.String, + var `confirmed`: kotlin.Boolean, + var `spent`: kotlin.Boolean, + var `expiry`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeToken : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Token { + return Token( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: Token) = + ( + FfiConverterString.allocationSize(value.`btcPaymentAddress`) + + FfiConverterString.allocationSize(value.`fee`) + + FfiConverterString.allocationSize(value.`lightningInvoice`) + + FfiConverterString.allocationSize(value.`processorId`) + + FfiConverterString.allocationSize(value.`tokenId`) + + FfiConverterBoolean.allocationSize(value.`confirmed`) + + FfiConverterBoolean.allocationSize(value.`spent`) + + FfiConverterString.allocationSize(value.`expiry`) + ) + + override fun write( + value: Token, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`btcPaymentAddress`, buf) + FfiConverterString.write(value.`fee`, buf) + FfiConverterString.write(value.`lightningInvoice`, buf) + FfiConverterString.write(value.`processorId`, buf) + FfiConverterString.write(value.`tokenId`, buf) + FfiConverterBoolean.write(value.`confirmed`, buf) + FfiConverterBoolean.write(value.`spent`, buf) + FfiConverterString.write(value.`expiry`, buf) + } +} + +@Serializable +data class TransferReceiverErrorResponsePayload( + var `code`: TransferReceiverError, + var `message`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferReceiverErrorResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferReceiverErrorResponsePayload { + return TransferReceiverErrorResponsePayload( + FfiConverterTypeTransferReceiverError.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferReceiverErrorResponsePayload) = + ( + FfiConverterTypeTransferReceiverError.allocationSize(value.`code`) + + FfiConverterString.allocationSize(value.`message`) + ) + + override fun write( + value: TransferReceiverErrorResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterTypeTransferReceiverError.write(value.`code`, buf) + FfiConverterString.write(value.`message`, buf) + } +} + +@Serializable +data class TransferReceiverPostResponsePayload( + @SerialName("server_pubkey") + var `serverPubkey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferReceiverPostResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferReceiverPostResponsePayload { + return TransferReceiverPostResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferReceiverPostResponsePayload) = + ( + FfiConverterString.allocationSize(value.`serverPubkey`) + ) + + override fun write( + value: TransferReceiverPostResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`serverPubkey`, buf) + } +} + +@Serializable +data class TransferReceiverRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("batch_data") + var `batchData`: kotlin.String?, + var `t2`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferReceiverRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferReceiverRequestPayload { + return TransferReceiverRequestPayload( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferReceiverRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterOptionalString.allocationSize(value.`batchData`) + + FfiConverterString.allocationSize(value.`t2`) + + FfiConverterString.allocationSize(value.`authSig`) + ) + + override fun write( + value: TransferReceiverRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterOptionalString.write(value.`batchData`, buf) + FfiConverterString.write(value.`t2`, buf) + FfiConverterString.write(value.`authSig`, buf) + } +} + +@Serializable +data class TransferSenderRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, + @SerialName("new_user_auth_key") + var `newUserAuthKey`: kotlin.String, + @SerialName("batch_id") + var `batchId`: kotlin.String?, +) { + companion object +} + +public object FfiConverterTypeTransferSenderRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferSenderRequestPayload { + return TransferSenderRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: TransferSenderRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`authSig`) + + FfiConverterString.allocationSize(value.`newUserAuthKey`) + + FfiConverterOptionalString.allocationSize(value.`batchId`) + ) + + override fun write( + value: TransferSenderRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`authSig`, buf) + FfiConverterString.write(value.`newUserAuthKey`, buf) + FfiConverterOptionalString.write(value.`batchId`, buf) + } +} + +@Serializable +data class TransferSenderResponsePayload( + var `x1`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferSenderResponsePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferSenderResponsePayload { + return TransferSenderResponsePayload( + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferSenderResponsePayload) = + ( + FfiConverterString.allocationSize(value.`x1`) + ) + + override fun write( + value: TransferSenderResponsePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`x1`, buf) + } +} + +@Serializable +data class TransferUnlockRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, + @SerialName("auth_pub_key") + var `authPubKey`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferUnlockRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferUnlockRequestPayload { + return TransferUnlockRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferUnlockRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`authSig`) + + FfiConverterString.allocationSize(value.`authPubKey`) + ) + + override fun write( + value: TransferUnlockRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`authSig`, buf) + FfiConverterString.write(value.`authPubKey`, buf) + } +} + +@Serializable +data class TransferUpdateMsgRequestPayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("auth_sig") + var `authSig`: kotlin.String, + @SerialName("new_user_auth_key") + var `newUserAuthKey`: kotlin.String, + @SerialName("enc_transfer_msg") + var `encTransferMsg`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeTransferUpdateMsgRequestPayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TransferUpdateMsgRequestPayload { + return TransferUpdateMsgRequestPayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: TransferUpdateMsgRequestPayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`authSig`) + + FfiConverterString.allocationSize(value.`newUserAuthKey`) + + FfiConverterString.allocationSize(value.`encTransferMsg`) + ) + + override fun write( + value: TransferUpdateMsgRequestPayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`authSig`, buf) + FfiConverterString.write(value.`newUserAuthKey`, buf) + FfiConverterString.write(value.`encTransferMsg`, buf) + } +} + +data class TxOutpoint( + var `txid`: kotlin.String, + var `vout`: kotlin.UInt, +) { + companion object +} + +public object FfiConverterTypeTxOutpoint : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): TxOutpoint { + return TxOutpoint( + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + ) + } + + override fun allocationSize(value: TxOutpoint) = + ( + FfiConverterString.allocationSize(value.`txid`) + + FfiConverterUInt.allocationSize(value.`vout`) + ) + + override fun write( + value: TxOutpoint, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`txid`, buf) + FfiConverterUInt.write(value.`vout`, buf) + } +} + +@Serializable +data class Wallet( + var `name`: kotlin.String, + var `mnemonic`: kotlin.String, + var `version`: kotlin.String, + var `stateEntityEndpoint`: kotlin.String, + var `electrumEndpoint`: kotlin.String, + var `network`: kotlin.String, + var `blockheight`: kotlin.UInt, + var `initlock`: kotlin.UInt, + var `interval`: kotlin.UInt, + var `tokens`: List, + var `activities`: List, + var `coins`: List, + var `settings`: Settings, +) { + companion object +} + +public object FfiConverterTypeWallet : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Wallet { + return Wallet( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterSequenceTypeToken.read(buf), + FfiConverterSequenceTypeActivity.read(buf), + FfiConverterSequenceTypeCoin.read(buf), + FfiConverterTypeSettings.read(buf), + ) + } + + override fun allocationSize(value: Wallet) = + ( + FfiConverterString.allocationSize(value.`name`) + + FfiConverterString.allocationSize(value.`mnemonic`) + + FfiConverterString.allocationSize(value.`version`) + + FfiConverterString.allocationSize(value.`stateEntityEndpoint`) + + FfiConverterString.allocationSize(value.`electrumEndpoint`) + + FfiConverterString.allocationSize(value.`network`) + + FfiConverterUInt.allocationSize(value.`blockheight`) + + FfiConverterUInt.allocationSize(value.`initlock`) + + FfiConverterUInt.allocationSize(value.`interval`) + + FfiConverterSequenceTypeToken.allocationSize(value.`tokens`) + + FfiConverterSequenceTypeActivity.allocationSize(value.`activities`) + + FfiConverterSequenceTypeCoin.allocationSize(value.`coins`) + + FfiConverterTypeSettings.allocationSize(value.`settings`) + ) + + override fun write( + value: Wallet, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`name`, buf) + FfiConverterString.write(value.`mnemonic`, buf) + FfiConverterString.write(value.`version`, buf) + FfiConverterString.write(value.`stateEntityEndpoint`, buf) + FfiConverterString.write(value.`electrumEndpoint`, buf) + FfiConverterString.write(value.`network`, buf) + FfiConverterUInt.write(value.`blockheight`, buf) + FfiConverterUInt.write(value.`initlock`, buf) + FfiConverterUInt.write(value.`interval`, buf) + FfiConverterSequenceTypeToken.write(value.`tokens`, buf) + FfiConverterSequenceTypeActivity.write(value.`activities`, buf) + FfiConverterSequenceTypeCoin.write(value.`coins`, buf) + FfiConverterTypeSettings.write(value.`settings`, buf) + } +} + +@Serializable +data class WithdrawCompletePayload( + @SerialName("statechain_id") + var `statechainId`: kotlin.String, + @SerialName("signed_statechain_id") + var `signedStatechainId`: kotlin.String, +) { + companion object +} + +public object FfiConverterTypeWithdrawCompletePayload : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): WithdrawCompletePayload { + return WithdrawCompletePayload( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: WithdrawCompletePayload) = + ( + FfiConverterString.allocationSize(value.`statechainId`) + + FfiConverterString.allocationSize(value.`signedStatechainId`) + ) + + override fun write( + value: WithdrawCompletePayload, + buf: ByteBuffer, + ) { + FfiConverterString.write(value.`statechainId`, buf) + FfiConverterString.write(value.`signedStatechainId`, buf) + } +} + +enum class CoinStatus { + INITIALISED, + IN_MEMPOOL, + UNCONFIRMED, + CONFIRMED, + IN_TRANSFER, + WITHDRAWING, + TRANSFERRED, + WITHDRAWN, + ; + + companion object +} + +public object FfiConverterTypeCoinStatus : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = + try { + CoinStatus.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: CoinStatus) = 4UL + + override fun write( + value: CoinStatus, + buf: ByteBuffer, + ) { + buf.putInt(value.ordinal + 1) + } +} + +sealed class MercuryException : Exception() { + class Bip39Exception() : MercuryException() { + override val message + get() = "" + } + + class Bip32Exception() : MercuryException() { + override val message + get() = "" + } + + class NetworkConversionException() : MercuryException() { + override val message + get() = "" + } + + class Secp256k1UpstreamException() : MercuryException() { + override val message + get() = "" + } + + class KeyException() : MercuryException() { + override val message + get() = "" + } + + class Bech32Exception() : MercuryException() { + override val message + get() = "" + } + + class HexException() : MercuryException() { + override val message + get() = "" + } + + class LocktimeNotBlockHeightException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinConsensusEncodeException() : MercuryException() { + override val message + get() = "" + } + + class MusigNonceGenException() : MercuryException() { + override val message + get() = "" + } + + class InvalidStatechainAddressException() : MercuryException() { + override val message + get() = "" + } + + class InvalidBitcoinAddressException() : MercuryException() { + override val message + get() = "" + } + + class StatechainAddressMismatchNetworkException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinAddressMismatchNetworkException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinAddressException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinAbsoluteException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinHashHexException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinPsbtException() : MercuryException() { + override val message + get() = "" + } + + class SighashTypeParseException() : MercuryException() { + override val message + get() = "" + } + + class BitcoinSighashException() : MercuryException() { + override val message + get() = "" + } + + class ParseException() : MercuryException() { + override val message + get() = "" + } + + class MusigSignException() : MercuryException() { + override val message + get() = "" + } + + class SchnorrSignatureValidationException() : MercuryException() { + override val message + get() = "" + } + + class MoreThanOneInputException() : MercuryException() { + override val message + get() = "" + } + + class UnkownNetwork() : MercuryException() { + override val message + get() = "" + } + + class BackupTransactionDoesNotPayUser() : MercuryException() { + override val message + get() = "" + } + + class FeeTooHigh() : MercuryException() { + override val message + get() = "" + } + + class FeeTooLow() : MercuryException() { + override val message + get() = "" + } + + class OutOfRangeException() : MercuryException() { + override val message + get() = "" + } + + class SerdeJsonException() : MercuryException() { + override val message + get() = "" + } + + class SecpException() : MercuryException() { + override val message + get() = "" + } + + class NoBackupTransactionFound() : MercuryException() { + override val message + get() = "" + } + + class Tx1HasMoreThanOneInput() : MercuryException() { + override val message + get() = "" + } + + class InvalidSignature() : MercuryException() { + override val message + get() = "" + } + + class EmptyWitness() : MercuryException() { + override val message + get() = "" + } + + class EmptyWitnessData() : MercuryException() { + override val message + get() = "" + } + + class IncorrectChallenge() : MercuryException() { + override val message + get() = "" + } + + class InvalidT1() : MercuryException() { + override val message + get() = "" + } + + class IncorrectAggregatedPublicKey() : MercuryException() { + override val message + get() = "" + } + + class T1MustBeExactly32BytesException() : MercuryException() { + override val message + get() = "" + } + + class NoX1Pub() : MercuryException() { + override val message + get() = "" + } + + class NoAggregatedPubkeyException() : MercuryException() { + override val message + get() = "" + } + + class CoinNotFound() : MercuryException() { + override val message + get() = "" + } + + class SignatureSchemeValidationException() : MercuryException() { + override val message + get() = "" + } + + class NoPreviousLockTimeException() : MercuryException() { + override val message + get() = "" + } + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): MercuryException = FfiConverterTypeMercuryError.lift(error_buf) + } +} + +public object FfiConverterTypeMercuryError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): MercuryException { + return when (buf.getInt()) { + 1 -> MercuryException.Bip39Exception() + 2 -> MercuryException.Bip32Exception() + 3 -> MercuryException.NetworkConversionException() + 4 -> MercuryException.Secp256k1UpstreamException() + 5 -> MercuryException.KeyException() + 6 -> MercuryException.Bech32Exception() + 7 -> MercuryException.HexException() + 8 -> MercuryException.LocktimeNotBlockHeightException() + 9 -> MercuryException.BitcoinConsensusEncodeException() + 10 -> MercuryException.MusigNonceGenException() + 11 -> MercuryException.InvalidStatechainAddressException() + 12 -> MercuryException.InvalidBitcoinAddressException() + 13 -> MercuryException.StatechainAddressMismatchNetworkException() + 14 -> MercuryException.BitcoinAddressMismatchNetworkException() + 15 -> MercuryException.BitcoinAddressException() + 16 -> MercuryException.BitcoinAbsoluteException() + 17 -> MercuryException.BitcoinHashHexException() + 18 -> MercuryException.BitcoinPsbtException() + 19 -> MercuryException.SighashTypeParseException() + 20 -> MercuryException.BitcoinSighashException() + 21 -> MercuryException.ParseException() + 22 -> MercuryException.MusigSignException() + 23 -> MercuryException.SchnorrSignatureValidationException() + 24 -> MercuryException.MoreThanOneInputException() + 25 -> MercuryException.UnkownNetwork() + 26 -> MercuryException.BackupTransactionDoesNotPayUser() + 27 -> MercuryException.FeeTooHigh() + 28 -> MercuryException.FeeTooLow() + 29 -> MercuryException.OutOfRangeException() + 30 -> MercuryException.SerdeJsonException() + 31 -> MercuryException.SecpException() + 32 -> MercuryException.NoBackupTransactionFound() + 33 -> MercuryException.Tx1HasMoreThanOneInput() + 34 -> MercuryException.InvalidSignature() + 35 -> MercuryException.EmptyWitness() + 36 -> MercuryException.EmptyWitnessData() + 37 -> MercuryException.IncorrectChallenge() + 38 -> MercuryException.InvalidT1() + 39 -> MercuryException.IncorrectAggregatedPublicKey() + 40 -> MercuryException.T1MustBeExactly32BytesException() + 41 -> MercuryException.NoX1Pub() + 42 -> MercuryException.NoAggregatedPubkeyException() + 43 -> MercuryException.CoinNotFound() + 44 -> MercuryException.SignatureSchemeValidationException() + 45 -> MercuryException.NoPreviousLockTimeException() + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: MercuryException): ULong { + return when (value) { + is MercuryException.Bip39Exception -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Bip32Exception -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NetworkConversionException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Secp256k1UpstreamException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.KeyException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Bech32Exception -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.HexException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.LocktimeNotBlockHeightException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinConsensusEncodeException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.MusigNonceGenException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidStatechainAddressException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidBitcoinAddressException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.StatechainAddressMismatchNetworkException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinAddressMismatchNetworkException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinAddressException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinAbsoluteException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinHashHexException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinPsbtException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SighashTypeParseException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BitcoinSighashException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.ParseException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.MusigSignException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SchnorrSignatureValidationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.MoreThanOneInputException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.UnkownNetwork -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.BackupTransactionDoesNotPayUser -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.FeeTooHigh -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.FeeTooLow -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.OutOfRangeException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SerdeJsonException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SecpException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoBackupTransactionFound -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.Tx1HasMoreThanOneInput -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidSignature -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.EmptyWitness -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.EmptyWitnessData -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.IncorrectChallenge -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.InvalidT1 -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.IncorrectAggregatedPublicKey -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.T1MustBeExactly32BytesException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoX1Pub -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoAggregatedPubkeyException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.CoinNotFound -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.SignatureSchemeValidationException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is MercuryException.NoPreviousLockTimeException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + } + } + + override fun write( + value: MercuryException, + buf: ByteBuffer, + ) { + when (value) { + is MercuryException.Bip39Exception -> { + buf.putInt(1) + Unit + } + is MercuryException.Bip32Exception -> { + buf.putInt(2) + Unit + } + is MercuryException.NetworkConversionException -> { + buf.putInt(3) + Unit + } + is MercuryException.Secp256k1UpstreamException -> { + buf.putInt(4) + Unit + } + is MercuryException.KeyException -> { + buf.putInt(5) + Unit + } + is MercuryException.Bech32Exception -> { + buf.putInt(6) + Unit + } + is MercuryException.HexException -> { + buf.putInt(7) + Unit + } + is MercuryException.LocktimeNotBlockHeightException -> { + buf.putInt(8) + Unit + } + is MercuryException.BitcoinConsensusEncodeException -> { + buf.putInt(9) + Unit + } + is MercuryException.MusigNonceGenException -> { + buf.putInt(10) + Unit + } + is MercuryException.InvalidStatechainAddressException -> { + buf.putInt(11) + Unit + } + is MercuryException.InvalidBitcoinAddressException -> { + buf.putInt(12) + Unit + } + is MercuryException.StatechainAddressMismatchNetworkException -> { + buf.putInt(13) + Unit + } + is MercuryException.BitcoinAddressMismatchNetworkException -> { + buf.putInt(14) + Unit + } + is MercuryException.BitcoinAddressException -> { + buf.putInt(15) + Unit + } + is MercuryException.BitcoinAbsoluteException -> { + buf.putInt(16) + Unit + } + is MercuryException.BitcoinHashHexException -> { + buf.putInt(17) + Unit + } + is MercuryException.BitcoinPsbtException -> { + buf.putInt(18) + Unit + } + is MercuryException.SighashTypeParseException -> { + buf.putInt(19) + Unit + } + is MercuryException.BitcoinSighashException -> { + buf.putInt(20) + Unit + } + is MercuryException.ParseException -> { + buf.putInt(21) + Unit + } + is MercuryException.MusigSignException -> { + buf.putInt(22) + Unit + } + is MercuryException.SchnorrSignatureValidationException -> { + buf.putInt(23) + Unit + } + is MercuryException.MoreThanOneInputException -> { + buf.putInt(24) + Unit + } + is MercuryException.UnkownNetwork -> { + buf.putInt(25) + Unit + } + is MercuryException.BackupTransactionDoesNotPayUser -> { + buf.putInt(26) + Unit + } + is MercuryException.FeeTooHigh -> { + buf.putInt(27) + Unit + } + is MercuryException.FeeTooLow -> { + buf.putInt(28) + Unit + } + is MercuryException.OutOfRangeException -> { + buf.putInt(29) + Unit + } + is MercuryException.SerdeJsonException -> { + buf.putInt(30) + Unit + } + is MercuryException.SecpException -> { + buf.putInt(31) + Unit + } + is MercuryException.NoBackupTransactionFound -> { + buf.putInt(32) + Unit + } + is MercuryException.Tx1HasMoreThanOneInput -> { + buf.putInt(33) + Unit + } + is MercuryException.InvalidSignature -> { + buf.putInt(34) + Unit + } + is MercuryException.EmptyWitness -> { + buf.putInt(35) + Unit + } + is MercuryException.EmptyWitnessData -> { + buf.putInt(36) + Unit + } + is MercuryException.IncorrectChallenge -> { + buf.putInt(37) + Unit + } + is MercuryException.InvalidT1 -> { + buf.putInt(38) + Unit + } + is MercuryException.IncorrectAggregatedPublicKey -> { + buf.putInt(39) + Unit + } + is MercuryException.T1MustBeExactly32BytesException -> { + buf.putInt(40) + Unit + } + is MercuryException.NoX1Pub -> { + buf.putInt(41) + Unit + } + is MercuryException.NoAggregatedPubkeyException -> { + buf.putInt(42) + Unit + } + is MercuryException.CoinNotFound -> { + buf.putInt(43) + Unit + } + is MercuryException.SignatureSchemeValidationException -> { + buf.putInt(44) + Unit + } + is MercuryException.NoPreviousLockTimeException -> { + buf.putInt(45) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +@Serializable +enum class TransferReceiverError { + @SerialName("StatecoinBatchLockedError") + STATECOIN_BATCH_LOCKED_ERROR, + @SerialName("ExpiredBatchTimeError") + EXPIRED_BATCH_TIME_ERROR, + ; + + companion object +} + +public object FfiConverterTypeTransferReceiverError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = + try { + TransferReceiverError.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: TransferReceiverError) = 4UL + + override fun write( + value: TransferReceiverError, + buf: ByteBuffer, + ) { + buf.putInt(value.ordinal + 1) + } +} + +public object FfiConverterOptionalUInt : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.UInt? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterUInt.read(buf) + } + + override fun allocationSize(value: kotlin.UInt?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterUInt.allocationSize(value) + } + } + + override fun write( + value: kotlin.UInt?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterUInt.write(value, buf) + } + } +} + +public object FfiConverterOptionalString : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.String? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterString.read(buf) + } + + override fun allocationSize(value: kotlin.String?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterString.allocationSize(value) + } + } + + override fun write( + value: kotlin.String?, + buf: ByteBuffer, + ) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterString.write(value, buf) + } + } +} + +public object FfiConverterSequenceString : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterString.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterString.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeActivity : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeActivity.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeActivity.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeActivity.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeBackupTx : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeBackupTx.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeBackupTx.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeBackupTx.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeCoin : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeCoin.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeCoin.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeCoin.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypePubKeyInfo : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypePubKeyInfo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypePubKeyInfo.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypePubKeyInfo.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeStatechainInfo : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeStatechainInfo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeStatechainInfo.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeStatechainInfo.write(it, buf) + } + } +} + +public object FfiConverterSequenceTypeToken : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeToken.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeToken.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write( + value: List, + buf: ByteBuffer, + ) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeToken.write(it, buf) + } + } +} + +@Throws(MercuryException::class) +fun `createAggregatedAddress`( + `coin`: Coin, + `network`: kotlin.String, +): AggregatedPublicKey { + return FfiConverterTypeAggregatedPublicKey.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_aggregated_address( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createAndCommitNonces`(`coin`: Coin): CoinNonce { + return FfiConverterTypeCoinNonce.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_and_commit_nonces( + FfiConverterTypeCoin.lower(`coin`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createCpfpTx`( + `backupTx`: BackupTx, + `coin`: Coin, + `toAddress`: kotlin.String, + `feeRateSatsPerByte`: kotlin.ULong, + `network`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_cpfp_tx( + FfiConverterTypeBackupTx.lower(`backupTx`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`toAddress`), + FfiConverterULong.lower(`feeRateSatsPerByte`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createDepositMsg1`( + `coin`: Coin, + `tokenId`: kotlin.String, +): DepositMsg1 { + return FfiConverterTypeDepositMsg1.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_deposit_msg1( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`tokenId`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createSignature`( + `msg`: kotlin.String, + `clientPartialSigHex`: kotlin.String, + `serverPartialSigHex`: kotlin.String, + `sessionHex`: kotlin.String, + `outputPubkeyHex`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_signature( + FfiConverterString.lower(`msg`), + FfiConverterString.lower(`clientPartialSigHex`), + FfiConverterString.lower(`serverPartialSigHex`), + FfiConverterString.lower(`sessionHex`), + FfiConverterString.lower(`outputPubkeyHex`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createTransferSignature`( + `recipientAddress`: kotlin.String, + `inputTxid`: kotlin.String, + `inputVout`: kotlin.UInt, + `clientSeckey`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_transfer_signature( + FfiConverterString.lower(`recipientAddress`), + FfiConverterString.lower(`inputTxid`), + FfiConverterUInt.lower(`inputVout`), + FfiConverterString.lower(`clientSeckey`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `createTransferUpdateMsg`( + `x1`: kotlin.String, + `recipientAddress`: kotlin.String, + `coin`: Coin, + `transferSignature`: kotlin.String, + `backupTransactions`: List, +): TransferUpdateMsgRequestPayload { + return FfiConverterTypeTransferUpdateMsgRequestPayload.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_create_transfer_update_msg( + FfiConverterString.lower(`x1`), + FfiConverterString.lower(`recipientAddress`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`transferSignature`), + FfiConverterSequenceTypeBackupTx.lower(`backupTransactions`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `decodeStatechainAddress`(`scAddress`: kotlin.String): DecodedScAddress { + return FfiConverterTypeDecodedSCAddress.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_decode_statechain_address( + FfiConverterString.lower(`scAddress`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `duplicateCoinToInitializedState`( + `wallet`: Wallet, + `authPubkey`: kotlin.String, +): Coin { + return FfiConverterTypeCoin.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_duplicate_coin_to_initialized_state( + FfiConverterTypeWallet.lower(`wallet`), + FfiConverterString.lower(`authPubkey`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `ffiValidateSignatureScheme`( + `ffiTransferMsg`: FfiTransferMsg, + `statechainInfo`: StatechainInfoResponsePayload, + `tx0Hex`: kotlin.String, + `feeRateTolerance`: kotlin.UInt, + `currentFeeRateSatsPerByte`: kotlin.UInt, + `interval`: kotlin.UInt, +): kotlin.UInt { + return FfiConverterUInt.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_ffi_validate_signature_scheme( + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterTypeStatechainInfoResponsePayload.lower(`statechainInfo`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterUInt.lower(`feeRateTolerance`), + FfiConverterUInt.lower(`currentFeeRateSatsPerByte`), + FfiConverterUInt.lower(`interval`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `ffiVerifyTransferSignature`( + `newUserPubkey`: kotlin.String, + `tx0Outpoint`: TxOutpoint, + `ffiTransferMsg`: FfiTransferMsg, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_ffi_verify_transfer_signature( + FfiConverterString.lower(`newUserPubkey`), + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiCreateTransferReceiverRequestPayload`( + `statechainInfo`: StatechainInfoResponsePayload, + `ffiTransferMsg`: FfiTransferMsg, + `coin`: Coin, +): TransferReceiverRequestPayload { + return FfiConverterTypeTransferReceiverRequestPayload.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_create_transfer_receiver_request_payload( + FfiConverterTypeStatechainInfoResponsePayload.lower(`statechainInfo`), + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterTypeCoin.lower(`coin`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiDecryptTransferMsg`( + `encryptedMessage`: kotlin.String, + `privateKeyWif`: kotlin.String, +): FfiTransferMsg { + return FfiConverterTypeFFITransferMsg.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_decrypt_transfer_msg( + FfiConverterString.lower(`encryptedMessage`), + FfiConverterString.lower(`privateKeyWif`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiValidateTx0OutputPubkey`( + `enclavePublicKey`: kotlin.String, + `ffiTransferMsg`: FfiTransferMsg, + `tx0Outpoint`: TxOutpoint, + `tx0Hex`: kotlin.String, + `network`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_validate_tx0_output_pubkey( + FfiConverterString.lower(`enclavePublicKey`), + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `fiiVerifyLatestBackupTxPaysToUserPubkey`( + `ffiTransferMsg`: FfiTransferMsg, + `clientPubkeyShare`: kotlin.String, + `network`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_fii_verify_latest_backup_tx_pays_to_user_pubkey( + FfiConverterTypeFFITransferMsg.lower(`ffiTransferMsg`), + FfiConverterString.lower(`clientPubkeyShare`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `generateMnemonic`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_generate_mnemonic( + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getBlockheight`(`bkpTx`: BackupTx): kotlin.UInt { + return FfiConverterUInt.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_blockheight( + FfiConverterTypeBackupTx.lower(`bkpTx`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getNewCoin`(`wallet`: Wallet): Coin { + return FfiConverterTypeCoin.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_new_coin( + FfiConverterTypeWallet.lower(`wallet`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getNewKeyInfo`( + `serverPublicKeyHex`: kotlin.String, + `coin`: Coin, + `statechainId`: kotlin.String, + `tx0Outpoint`: TxOutpoint, + `tx0Hex`: kotlin.String, + `network`: kotlin.String, +): NewKeyInfo { + return FfiConverterTypeNewKeyInfo.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_new_key_info( + FfiConverterString.lower(`serverPublicKeyHex`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`statechainId`), + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getOutputAddressFromTx0`( + `tx0Outpoint`: TxOutpoint, + `tx0Hex`: kotlin.String, + `network`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_output_address_from_tx0( + FfiConverterTypeTxOutpoint.lower(`tx0Outpoint`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getPartialSigRequest`( + `coin`: Coin, + `blockHeight`: kotlin.UInt, + `initlock`: kotlin.UInt, + `interval`: kotlin.UInt, + `feeRateSatsPerByte`: kotlin.UInt, + `qtBackupTx`: kotlin.UInt, + `toAddress`: kotlin.String, + `network`: kotlin.String, + `isWithdrawal`: kotlin.Boolean, +): PartialSignatureMsg1 { + return FfiConverterTypePartialSignatureMsg1.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_partial_sig_request( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterUInt.lower(`blockHeight`), + FfiConverterUInt.lower(`initlock`), + FfiConverterUInt.lower(`interval`), + FfiConverterUInt.lower(`feeRateSatsPerByte`), + FfiConverterUInt.lower(`qtBackupTx`), + FfiConverterString.lower(`toAddress`), + FfiConverterString.lower(`network`), + FfiConverterBoolean.lower(`isWithdrawal`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getTx0Outpoint`(`backupTransactions`: List): TxOutpoint { + return FfiConverterTypeTxOutpoint.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_tx0_outpoint( + FfiConverterSequenceTypeBackupTx.lower(`backupTransactions`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `getUserBackupAddress`( + `coin`: Coin, + `network`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_get_user_backup_address( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `handleDepositMsg1Response`( + `coin`: Coin, + `depositMsg1Response`: DepositMsg1Response, +): DepositInitResult { + return FfiConverterTypeDepositInitResult.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_handle_deposit_msg_1_response( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterTypeDepositMsg1Response.lower(`depositMsg1Response`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `isEnclavePubkeyPartOfCoin`( + `coin`: Coin, + `enclavePubkey`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_is_enclave_pubkey_part_of_coin( + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`enclavePubkey`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `latestBackupTxPaysToUserPubkey`( + `backupTxs`: List, + `coin`: Coin, + `network`: kotlin.String, +): BackupTx { + return FfiConverterTypeBackupTx.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_latest_backup_tx_pays_to_user_pubkey( + FfiConverterSequenceTypeBackupTx.lower(`backupTxs`), + FfiConverterTypeCoin.lower(`coin`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `newBackupTransaction`( + `encodedUnsignedTx`: kotlin.String, + `signatureHex`: kotlin.String, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_new_backup_transaction( + FfiConverterString.lower(`encodedUnsignedTx`), + FfiConverterString.lower(`signatureHex`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `signMessage`( + `message`: kotlin.String, + `coin`: Coin, +): kotlin.String { + return FfiConverterString.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_sign_message( + FfiConverterString.lower(`message`), + FfiConverterTypeCoin.lower(`coin`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `validateAddress`( + `address`: kotlin.String, + `network`: kotlin.String, +): kotlin.Boolean { + return FfiConverterBoolean.lift( + uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_validate_address( + FfiConverterString.lower(`address`), + FfiConverterString.lower(`network`), + _status, + ) + }, + ) +} + +@Throws(MercuryException::class) +fun `verifyBlindedMusigScheme`( + `backupTx`: BackupTx, + `tx0Hex`: kotlin.String, + `statechainInfo`: StatechainInfo, +) = uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_verify_blinded_musig_scheme( + FfiConverterTypeBackupTx.lower(`backupTx`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterTypeStatechainInfo.lower(`statechainInfo`), + _status, + ) +} + +@Throws(MercuryException::class) +fun `verifyTransactionSignature`( + `txNHex`: kotlin.String, + `tx0Hex`: kotlin.String, + `feeRateTolerance`: kotlin.UInt, + `currentFeeRateSatsPerByte`: kotlin.UInt, +) = uniffiRustCallWithError(MercuryException) { _status -> + UniffiLib.INSTANCE.uniffi_mercurylib_fn_func_verify_transaction_signature( + FfiConverterString.lower(`txNHex`), + FfiConverterString.lower(`tx0Hex`), + FfiConverterUInt.lower(`feeRateTolerance`), + FfiConverterUInt.lower(`currentFeeRateSatsPerByte`), + _status, + ) +} diff --git a/clients/apps/nodejs/.gitignore b/clients/apps/nodejs/.gitignore new file mode 100644 index 00000000..faae5bbe --- /dev/null +++ b/clients/apps/nodejs/.gitignore @@ -0,0 +1,133 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +wallet.db* +wallet.json \ No newline at end of file diff --git a/clients/apps/nodejs/client_config.js b/clients/apps/nodejs/client_config.js new file mode 100644 index 00000000..2a06ca89 --- /dev/null +++ b/clients/apps/nodejs/client_config.js @@ -0,0 +1,17 @@ +const config = require('config'); + +const load = () => { + return { + confirmationTarget: config.get('confirmationTarget'), + statechainEntity: config.get('statechainEntity'), + torProxy: config.get('torProxy'), + databaseFile: config.get('databaseFile'), + electrumServer: config.get('electrumServer'), + feeRateTolerance: config.get('feeRateTolerance'), + network: config.get('network'), + electrumType: config.get('electrumType'), + maxFeeRate: config.get('maxFeeRate') + } +} + +module.exports = { load }; diff --git a/clients/apps/nodejs/config/default.json b/clients/apps/nodejs/config/default.json new file mode 100644 index 00000000..26caa8a0 --- /dev/null +++ b/clients/apps/nodejs/config/default.json @@ -0,0 +1,11 @@ +{ + "statechainEntity": "http://0.0.0.0:8000", + "electrumServer": "tcp://0.0.0.0:50001", + "electrumType": "electrs", + "network": "regtest", + "feeRateTolerance": 5, + "databaseFile": "wallet.db", + "confirmationTarget": 2, + "torProxy": null, + "maxFeeRate": 1 +} diff --git a/clients/apps/nodejs/config/regtest.json b/clients/apps/nodejs/config/regtest.json new file mode 100644 index 00000000..4e432016 --- /dev/null +++ b/clients/apps/nodejs/config/regtest.json @@ -0,0 +1,16 @@ +{ + "statechainEntity": "http://0.0.0.0:8000", + "__statechainEntity": "http://j23wevaeducxuy3zahd6bpn4x76cymwz2j3bdixv7ow4awjrg5p6jaid.onion", + "_statechainEntity": "http://45.76.136.11:8500/", + "__electrumServer": "tcp://0.0.0.0:50001", + "electrumServer": "tcp://0.0.0.0:50001", + "electrumType": "electrs", + "_electrumServer": "tcp://0.0.0.0:50001", + "network": "regtest", + "feeRateTolerance": 5, + "databaseFile": "wallet.db", + "confirmationTarget": 2, + "_torProxy": "socks5h://localhost:9050", + "torProxy": null, + "maxFeeRate": 1 +} diff --git a/clients/apps/nodejs/config/signet.json b/clients/apps/nodejs/config/signet.json new file mode 100644 index 00000000..3920ddaa --- /dev/null +++ b/clients/apps/nodejs/config/signet.json @@ -0,0 +1,16 @@ +{ + "statechainEntity": "http://127.0.0.1:8000", + "__statechainEntity": "http://j23wevaeducxuy3zahd6bpn4x76cymwz2j3bdixv7ow4awjrg5p6jaid.onion", + "_statechainEntity": "http://45.76.136.11:8500/", + "__electrumServer": "tcp://signet-electrumx.wakiyamap.dev:50001", + "electrumServer": "ssl://mempool.space:60602", + "electrumType": "electrs", + "_electrumServer": "tcp://localhost:50001", + "network": "signet", + "feeRateTolerance": 5, + "databaseFile": "wallet.db", + "confirmationTarget": 2, + "_torProxy": "socks5h://localhost:9050", + "torProxy": null, + "maxFeeRate": 1 +} diff --git a/clients/apps/nodejs/index.js b/clients/apps/nodejs/index.js new file mode 100644 index 00000000..8363924e --- /dev/null +++ b/clients/apps/nodejs/index.js @@ -0,0 +1,200 @@ +const { Command } = require('commander'); +const program = new Command(); + +const mercurynodejslib = require('mercurynodejslib'); +const client_config = require('./client_config'); + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function main() { + + const clientConfig = client_config.load(); + + program + .name('Statechain nodejs CLI client') + .description('CLI to test the Statechain nodejs client') + .version('0.0.1'); + + program.command('create-wallet') + .description('Create a new wallet') + .argument('', 'name of the wallet') + .action(async (name) => { + + let wallet = await mercurynodejslib.createWallet(clientConfig, name); + + console.log(JSON.stringify(wallet, null, 2)); + + }); + + program.command('new-token') + .description('Get new token.') + .argument('', 'name of the wallet') + .action(async (wallet_name) => { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_name); + console.log(JSON.stringify(token, null, 2)); + + }); + + program.command('list-tokens') + .description("List wallet's tokens") + .argument('', 'name of the wallet') + .action(async (wallet_name) => { + + let tokens = await mercurynodejslib.getWalletTokens(clientConfig, wallet_name); + + console.log(JSON.stringify(tokens, null, 2)); + + }); + + program.command('new-deposit-address') + .description('Get new deposit address. Used to fund a new statecoin.') + .argument('', 'name of the wallet') + .argument('', 'amount to deposit') + .action(async (wallet_name, amount) => { + + const address_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_name, amount); + + console.log(JSON.stringify(address_info, null, 2)); + }); + + program.command('broadcast-backup-transaction') + .description('Broadcast a backup transaction via CPFP') + .argument('', 'name of the wallet') + .argument('', 'statechain id of the coin') + .argument('', 'recipient bitcoin address') + .option('-f, --fee_rate ', '(optional) fee rate in satoshis per byte') + .action(async (wallet_name, statechain_id, to_address, options) => { + + const feeRate = (options && options.fee_rate) || null; + + let tx_ids = await mercurynodejslib.broadcastBackupTransaction(clientConfig, wallet_name, statechain_id, to_address, options); + + console.log(JSON.stringify(tx_ids, null, 2)); + }); + + program.command('list-statecoins') + .description("List wallet's statecoins") + .argument('', 'name of the wallet') + .action(async (wallet_name) => { + + let coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_name); + + console.log(JSON.stringify(coins, null, 2)); + }); + + program.command('withdraw') + .description('Withdraw funds from a statecoin to a BTC address') + .argument('', 'name of the wallet') + .argument('', 'statechain id of the coin') + .argument('', 'recipient bitcoin address') + .option('-f, --fee_rate ', '(optional) fee rate in satoshis per byte') + .action(async (wallet_name, statechain_id, to_address, options) => { + + const feeRate = (options && options.fee_rate) || null; + + const txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet_name, statechain_id, to_address, feeRate); + + console.log(JSON.stringify({ + txid + }, null, 2)); + }); + + program.command('new-transfer-address') + .description('New transfer address for a statecoin') + .argument('', 'name of the wallet') + .option('-b, --generate-batch-id', 'optional batch id for the transaction') + .action(async (wallet_name, options) => { + + const generateBatchId = options && options.generateBatchId; + + let res = await mercurynodejslib.newTransferAddress(clientConfig, wallet_name, generateBatchId); + + console.log(JSON.stringify(res, null, 2)); + }); + + program.command('transfer-send') + .description('Send the specified statecoin to an SC address') + .argument('', 'name of the wallet') + .argument('', 'statechain id of the coin') + .argument('', 'recipient bitcoin address') + .option('-b, --batch-id ', 'optional batch id for the transaction') + .action(async (wallet_name, statechain_id, to_address, options) => { + + let batchId = (options && options.batch_id) || null; + + let coin = await mercurynodejslib.transferSend(clientConfig, wallet_name, statechain_id, to_address, batchId); + + console.log(JSON.stringify(coin, null, 2)); + }); + + program.command('transfer-receive') + .description('Retrieve coins from server') + .argument('', 'name of the wallet') + .action(async (wallet_name) => { + + let receivedStatechainIds = []; + + while(true) { + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_name); + receivedStatechainIds = [...receivedStatechainIds, ...transferReceiveResult.receivedStatechainIds]; + + if (transferReceiveResult.isThereBatchLocked) { + console.log("Statecoin batch still locked. Waiting until expiration or unlock."); + await sleep(5000); + } else { + break; + } + } + + console.log(JSON.stringify(receivedStatechainIds, null, 2)); + }); + + program.command('payment-hash') + .description('Get a payment hash for lightning latch') + .argument('', 'name of the wallet') + .argument('', 'statechain id of the coin') + .action(async (wallet_name, statechain_id) => { + + let res = await mercurynodejslib.paymentHash(clientConfig, wallet_name, statechain_id); + + console.log(JSON.stringify(res, null, 2)); + }); + + program.command('confirm-pending-invoice') + .description('Confirm a pending invoice for lightning latch') + .argument('', 'name of the wallet') + .argument('', 'statechain id of the coin') + .action(async (wallet_name, statechain_id) => { + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_name, statechain_id); + + let res = { + message: 'Invoice confirmed' + }; + + console.log(JSON.stringify(res, null, 2)); + }); + + program.command('retrieve-pre-image') + .description('Confirm a pending invoice for lightning latch') + .argument('', 'name of the wallet') + .argument('', 'statechain id of the coin') + .argument('', 'transfer batch id') + .action(async (wallet_name, statechain_id, batch_id) => { + + let res = await mercurynodejslib.retrievePreImage(clientConfig, wallet_name, statechain_id, batch_id); + + console.log(JSON.stringify(res, null, 2)); + }); + + program.parse(); + +} + +(async () => { + await main(); +})(); diff --git a/clients/apps/nodejs/package-lock.json b/clients/apps/nodejs/package-lock.json new file mode 100644 index 00000000..4046531a --- /dev/null +++ b/clients/apps/nodejs/package-lock.json @@ -0,0 +1,2209 @@ +{ + "name": "client-nodejs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client-nodejs", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@mempool/electrum-client": "^1.1.9", + "axios": "^1.5.1", + "bitcoinjs-lib": "^6.1.5", + "commander": "^11.1.0", + "config": "^3.3.9", + "mercury-wasm": "file:../../../wasm/node_pkg/debug", + "mercurynodejslib": "file:../../libs/nodejs", + "socks-proxy-agent": "^8.0.2", + "sqlite3": "^5.1.6", + "tiny-secp256k1": "^2.2.3", + "uuid": "^9.0.1" + }, + "devDependencies": { + "chai": "^5.1.1", + "mocha": "^10.6.0" + } + }, + "../../../wasm/node_pkg/debug": { + "name": "mercury-wasm", + "version": "0.1.0" + }, + "../../libs/nodejs": { + "name": "mercurynodejslib", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@mempool/electrum-client": "^1.1.9", + "axios": "^1.5.1", + "bitcoinjs-lib": "^6.1.5", + "commander": "^11.1.0", + "config": "^3.3.9", + "mercury-wasm": "file:../../../wasm/node_pkg/debug", + "socks-proxy-agent": "^8.0.2", + "sqlite3": "^5.1.6", + "tiny-secp256k1": "^2.2.3", + "uuid": "^9.0.1" + } + }, + "../../wasm/debug/node_pkg": { + "extraneous": true + }, + "../../wasm/node_pkg": { + "name": "mercury-wasm", + "version": "0.1.0", + "extraneous": true + }, + "../../wasm/node_pkg/debug": { + "name": "mercury-wasm", + "version": "0.1.0", + "extraneous": true + }, + "../../wasm/pkg": { + "name": "mercury-wasm", + "version": "0.1.0", + "extraneous": true + }, + "../wasm/pkg": { + "name": "mercury-wasm", + "version": "0.1.0", + "extraneous": true + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mempool/electrum-client": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz", + "integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", + "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/config": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.9.tgz", + "integrity": "sha512-G17nfe+cY7kR0wVpc49NCYvNtelm/pPy8czHoFkAgtV1lkmcp7DHtWCdDu+C9Z7gb2WVqa9Tm3uF9aKaPbCfhg==", + "dependencies": { + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "optional": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/mercury-wasm": { + "resolved": "../../../wasm/node_pkg/debug", + "link": true + }, + "node_modules/mercurynodejslib": { + "resolved": "../../libs/nodejs", + "link": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", + "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sqlite3": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz", + "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^4.2.0", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", + "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/uint8array-tools": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/clients/apps/nodejs/package.json b/clients/apps/nodejs/package.json new file mode 100644 index 00000000..1f90925b --- /dev/null +++ b/clients/apps/nodejs/package.json @@ -0,0 +1,28 @@ +{ + "name": "client-nodejs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "mocha" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@mempool/electrum-client": "^1.1.9", + "axios": "^1.5.1", + "bitcoinjs-lib": "^6.1.5", + "commander": "^11.1.0", + "config": "^3.3.9", + "mercury-wasm": "file:../../../wasm/node_pkg/debug", + "mercurynodejslib": "file:../../libs/nodejs", + "socks-proxy-agent": "^8.0.2", + "sqlite3": "^5.1.6", + "tiny-secp256k1": "^2.2.3", + "uuid": "^9.0.1" + }, + "devDependencies": { + "chai": "^5.1.1", + "mocha": "^10.6.0" + } +} diff --git a/clients/apps/nodejs/test/ta02-duplicate-deposits.mjs b/clients/apps/nodejs/test/ta02-duplicate-deposits.mjs new file mode 100644 index 00000000..92d73111 --- /dev/null +++ b/clients/apps/nodejs/test/ta02-duplicate-deposits.mjs @@ -0,0 +1,166 @@ +import { expect } from 'chai'; +import client_config from '../client_config.js'; +import mercurynodejslib from 'mercurynodejslib'; +import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; +import { createWallet, removeDatabase, getnewaddress, generateBlock, depositCoin } from './test-utils.mjs'; + +describe('TA02 - Duplicated Deposits', function() { + + context('Withdraw Flow', () => { + it('should complete successfully', async () => { + + const clientConfig = client_config.load(); + await removeDatabase(clientConfig); + + let wallet_1_name = "w1"; + let wallet_2_name = "w2"; + let wallet1 = await createWallet(clientConfig, wallet_1_name); + let wallet2 = await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet1.name); + const tokenId = token.token_id; + + const amount1 = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount1); + + await depositCoin(depositInfo.deposit_address, amount1); + + let amount2 = 20000; + await depositCoin(depositInfo.deposit_address, amount2); + + const coreWalletAddress = await getnewaddress(); + + await generateBlock(clientConfig.confirmationTarget, coreWalletAddress); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet1.name); + + expect(listCoins.length).to.equal(2); + + const newCoin = listCoins.find(coin => + coin.aggregated_address == depositInfo.deposit_address && + coin.status == CoinStatus.CONFIRMED + ); + + const duplicatedCoin = listCoins.find(coin => + coin.aggregated_address == depositInfo.deposit_address && + coin.status == CoinStatus.DUPLICATED + ); + + expect(newCoin).to.not.be.null; + expect(duplicatedCoin).to.not.be.null; + + expect(newCoin.duplicate_index).to.equal(0); + expect(duplicatedCoin.duplicate_index).to.equal(1); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet2.name, null); + + try { + await mercurynodejslib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, false, null); + } catch (error) { + expect(error.message).to.include("Coin is duplicated. If you want to proceed, use the command '--force, -f' option. " + + "You will no longer be able to move other duplicate coins with the same statechain_id and this will cause PERMANENT LOSS of these duplicate coin funds."); + } + + const withdrawAddress = "bcrt1qn5948np2j8t68xgpceneg3ua4wcwhwrsqj8scv"; + + let txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet1.name, duplicatedCoin.statechain_id, withdrawAddress, null, 1); + + expect(txid).to.be.string; + + try { + await mercurynodejslib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, false, null); + } catch (error) { + expect(error.message).to.include("There have been withdrawals of other coins with this same statechain_id (possibly duplicates). " + + "This transfer cannot be performed because the recipient would reject it due to the difference in signature count. This coin can be withdrawn, however."); + } + + txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet1.name, duplicatedCoin.statechain_id, withdrawAddress, null, 0); + + expect(txid).to.be.string; + }) + }), + + context('Transfer Flow', () => { + it('should complete successfully', async () => { + + const clientConfig = client_config.load(); + await removeDatabase(clientConfig); + + let wallet_1_name = "w1"; + let wallet_2_name = "w2"; + let wallet1 = await createWallet(clientConfig, wallet_1_name); + let wallet2 = await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet1.name); + const tokenId = token.token_id; + + const amount1 = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount1); + + await depositCoin(depositInfo.deposit_address, amount1); + + let amount2 = 20000; + await depositCoin(depositInfo.deposit_address, amount2); + + const coreWalletAddress = await getnewaddress(); + + await generateBlock(clientConfig.confirmationTarget, coreWalletAddress); + + let listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet1.name); + + expect(listCoins.length).to.equal(2); + + const newCoin = listCoins.find(coin => + coin.aggregated_address == depositInfo.deposit_address && + coin.status == CoinStatus.CONFIRMED + ); + + let duplicatedCoin = listCoins.find(coin => + coin.aggregated_address == depositInfo.deposit_address && + coin.status == CoinStatus.DUPLICATED + ); + + expect(newCoin).to.not.be.null; + expect(duplicatedCoin).to.not.be.null; + + expect(newCoin.duplicate_index).to.equal(0); + expect(duplicatedCoin.duplicate_index).to.equal(1); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet2.name, null); + + let result = await mercurynodejslib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, true, null); + expect(result).to.have.property('statechain_id'); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceiveResult.receivedStatechainIds).contains(newCoin.statechain_id); + expect(transferReceiveResult.receivedStatechainIds.length).to.equal(1); + + listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet1.name); + + const transferredCoin = listCoins.find(coin => + coin.aggregated_address === depositInfo.deposit_address && + coin.status === CoinStatus.TRANSFERRED + ); + + duplicatedCoin = listCoins.find(coin => + coin.aggregated_address === depositInfo.deposit_address && + coin.status === CoinStatus.DUPLICATED + ); + + expect(transferredCoin).to.not.be.null; + expect(transferredCoin.duplicate_index).to.equal(0); + + expect(duplicatedCoin).to.not.be.null; + expect(duplicatedCoin.duplicate_index).to.equal(1); + + try { + const withdrawAddress = "bcrt1qn5948np2j8t68xgpceneg3ua4wcwhwrsqj8scv"; + await mercurynodejslib.withdrawCoin(clientConfig, wallet1.name, duplicatedCoin.statechain_id, withdrawAddress, null, 1); + } catch (error) { + expect(error.response.status).to.equal(401); + expect(error.response.data.message).to.equal("Signature does not match authentication key."); + } + }) + }) +}) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs new file mode 100644 index 00000000..5c588e6a --- /dev/null +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -0,0 +1,74 @@ +import { expect } from 'chai'; +import client_config from '../client_config.js'; +import mercurynodejslib from 'mercurynodejslib'; +import { CoinStatus } from 'mercurynodejslib/coin_enum.js'; +import crypto from 'crypto'; +import { createWallet, removeDatabase, getnewaddress, generateBlock, depositCoin } from './test-utils.mjs'; + +describe('TB04 - Lightning Latch', function() { + + context('Simple Transfer', () => { + it('should complete successfully', async () => { + + + const clientConfig = client_config.load(); + await removeDatabase(clientConfig); + + let wallet_1_name = "w1"; + let wallet_2_name = "w2"; + let wallet1 = await createWallet(clientConfig, wallet_1_name); + let wallet2 = await createWallet(clientConfig, wallet_2_name); + + const token = await mercurynodejslib.newToken(clientConfig, wallet1.name); + const tokenId = token.token_id; + + const amount = 10000; + const depositInfo = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet1.name); + const usedToken = tokenList.find(token => token.token_id === tokenId); + + expect(usedToken.spent).is.true; + + await depositCoin(depositInfo.deposit_address, amount); + + const coreWalletAddress = await getnewaddress(); + + await generateBlock(clientConfig.confirmationTarget, coreWalletAddress); + + const listCoins = await mercurynodejslib.listStatecoins(clientConfig, wallet1.name); + + expect(listCoins.length).to.equal(1); + + const coin = listCoins[0]; + + expect(coin.status).to.equal(CoinStatus.CONFIRMED); + + const paymentHash = await mercurynodejslib.paymentHash(clientConfig, wallet1.name, coin.statechain_id); + + const transferAddress = await mercurynodejslib.newTransferAddress(clientConfig, wallet2.name, null); + + await mercurynodejslib.transferSend(clientConfig, wallet1.name, coin.statechain_id, transferAddress.transfer_receive, false, paymentHash.batchId); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceiveResult.isThereBatchLocked).is.true; + expect(transferReceiveResult.receivedStatechainIds).empty; + + await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet1.name, coin.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceiveResult.isThereBatchLocked).is.false; + expect(transferReceiveResult.receivedStatechainIds).not.empty; + + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet1.name, coin.statechain_id, paymentHash.batchId); + + const hash = crypto.createHash('sha256') + .update(Buffer.from(preimage, 'hex')) + .digest('hex') + + expect(hash).to.equal(paymentHash.hash); + }) + }) +}) diff --git a/clients/apps/nodejs/test/test-utils.mjs b/clients/apps/nodejs/test/test-utils.mjs new file mode 100644 index 00000000..1365bc11 --- /dev/null +++ b/clients/apps/nodejs/test/test-utils.mjs @@ -0,0 +1,50 @@ +import { promisify } from 'node:util'; +import { exec as execCallback } from 'node:child_process'; +import mercurynodejslib from 'mercurynodejslib'; +import { promises as fs } from 'fs'; + +const exec = promisify(execCallback); + +async function createWallet(clientConfig, walletName) { + + let wallet = await mercurynodejslib.createWallet(clientConfig, walletName); + return wallet; + + // TODO: add more assertions + } + + +async function removeDatabase(clientConfig) { + try { + await fs.unlink(clientConfig.databaseFile); + } catch (error) { + if (error.code !== 'ENOENT') { + console.error('Error occurred:', error); + } + // ENOENT means "No such file or directory", so we ignore this error + } +} + +async function getnewaddress() { + const generateBlockCommand = `docker exec $(docker ps -qf "name=lnd_docker-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getnewaddress`; + const { stdout, stderr } = await exec(generateBlockCommand); + if (stderr) { + console.error('Error:', stderr); + return null; + } + return stdout.trim(); +} + +async function generateBlock(numBlocks, address) { + const generateBlockCommand = `docker exec $(docker ps -qf "name=lnd_docker-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} ${address}`; + await exec(generateBlockCommand); +} + +async function depositCoin(deposit_address, amountInSats) { + const amountInBtc = amountInSats / 100000000; + + const sendBitcoinCommand = `docker exec $(docker ps -qf "name=lnd_docker-bitcoind-1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress ${deposit_address} ${amountInBtc}`; + await exec(sendBitcoinCommand); +} + +export { createWallet, removeDatabase, getnewaddress, generateBlock, depositCoin }; \ No newline at end of file diff --git a/clients/apps/nodejs/test_atomic_swap.js b/clients/apps/nodejs/test_atomic_swap.js new file mode 100644 index 00000000..c3bb5f3c --- /dev/null +++ b/clients/apps/nodejs/test_atomic_swap.js @@ -0,0 +1,856 @@ +const assert = require('node:assert/strict'); +const mercurynodejslib = require('mercurynodejslib'); +const { CoinStatus } = require('mercurynodejslib/coin_enum'); +const { sleep, createWallet, generateBlock, depositCoin } = require('./test_utils'); +const client_config = require('./client_config'); + +async function atomicSwapSuccess(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + const generateBatchId = true; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + console.log("transferReceiveResult: ", transferReceiveResult); + assert(transferReceiveResult.isThereBatchLocked === true); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); + + // transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + // received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + // console.log("received_statechain_ids: ", received_statechain_ids_w3); + + // assert(received_statechain_ids_w3.length > 0); + // assert(received_statechain_ids_w3[0] == coin3.statechain_id); +} + +async function atomicSwapWithSecondBatchIdMissing(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + const generateBatchId = true; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin3); + + transfer_address_w3.batchId = ""; + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + let received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w3); + + assert(received_statechain_ids_w3.length > 0); + assert(received_statechain_ids_w3[0] == coin3.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); +} + +async function atomicSwapWithoutFirstBatchId(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + const generateBatchId = true; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, null); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + let received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w3); + + assert(received_statechain_ids_w3.length > 0); + assert(received_statechain_ids_w3[0] == coin3.statechain_id); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); +} + +async function atomicSwapWithTimeout(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + const generateBatchId = true; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + let received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + await sleep(20000); + + assert(transferReceiveResult.isThereBatchLocked === true); + + let errorMessage; + console.error = (msg) => { + errorMessage = msg; + }; + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + let received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + // Assert the captured error message + const expectedMessage = 'Failed to update transfer message'; + assert.ok(errorMessage.includes(expectedMessage)); + + transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin3); + + coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + received_statechain_ids_w4 = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids_w4); + + assert(received_statechain_ids_w4.length > 0); + assert(received_statechain_ids_w4[0] == coin4.statechain_id); + + // transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + // received_statechain_ids_w3 = transferReceiveResult.receivedStatechainIds; + + // console.log("received_statechain_ids: ", received_statechain_ids_w3); + + // assert(received_statechain_ids_w3.length > 0); + // assert(received_statechain_ids_w3[0] == coin3.statechain_id); +} + +async function atomicSwapWithFirstPartySteal(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + const generateBatchId = true; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + let transfer_address_w3_for_steal = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + console.log("transfer address for steal", transfer_address_w3_for_steal); + + let coin_to_steal = undefined; + try { + coin_to_steal = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3_for_steal.transfer_receive, false, transfer_address_w3.batchId); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'expected a string argument, found undefined'; + assert.ok(error.message.includes(expectedMessage)); + } + + console.log("coin to steal transferSend: ", coin_to_steal); + + let received_statechain_ids_w3 = undefined; + try { + received_statechain_ids_w3 = mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + await sleep(3000); + + let received_statechain_ids_w4 = undefined; + try { + received_statechain_ids_w4 = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + + try { + received_statechain_ids_w3 = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } +} + +async function atomicSwapWithSecondPartySteal(clientConfig, wallet_1_name, wallet_2_name, wallet_3_name, wallet_4_name) { + + const amount = 10000; + let token = undefined; + let tokenId = undefined; + let deposit_info = undefined; + let tokenList = undefined; + let usedToken = undefined; + + token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin1 = undefined; + + console.log("coin: ", coin1); + + while (!coin1) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin1 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin1); + + token = await mercurynodejslib.newToken(clientConfig, wallet_2_name); + tokenId = token.token_id; + + deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_2_name, amount); + + tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_2_name); + + usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_2_name, amount, deposit_info); + + let coin2 = undefined; + + console.log("coin: ", coin2); + + while (!coin2) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_2_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + generateBlock(1); + continue; + } + + coin2 = coinsWithStatechainId[0]; + break; + } + + console.log("coin: ", coin2); + + const generateBatchId = true; + + let transfer_address_w3 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_3_name, generateBatchId); + let transfer_address_w4 = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, null); + + let coin3 = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin1.statechain_id, transfer_address_w3.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin3); + + let coin4 = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4.transfer_receive, false, transfer_address_w3.batchId); + console.log("coin transferSend: ", coin4); + + let transfer_address_w4_for_steal = await mercurynodejslib.newTransferAddress(clientConfig, wallet_4_name, generateBatchId); + console.log("transfer address for steal", transfer_address_w4_for_steal); + + let coin_to_steal = undefined; + try { + coin_to_steal = await mercurynodejslib.transferSend(clientConfig, wallet_2_name, coin2.statechain_id, transfer_address_w4_for_steal.transfer_receive, false, transfer_address_w4.batchId); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'expected a string argument, found undefined'; + assert.ok(error.message.includes(expectedMessage)); + } + + console.log("coin to steal transferSend: ", coin_to_steal); + + let received_statechain_ids_w3 = undefined; + try { + received_statechain_ids_w3 = mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + + let received_statechain_ids_w4 = undefined; + try { + received_statechain_ids_w4 = await mercurynodejslib.transferReceive(clientConfig, wallet_4_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } + + try { + received_statechain_ids_w3 = await mercurynodejslib.transferReceive(clientConfig, wallet_3_name); + } catch (error) { + // Assert the captured error message + const expectedMessage = 'num_sigs is not correct'; + assert.ok(error.message.includes(expectedMessage)); + } +} + +(async () => { + + try { + const clientConfig = client_config.load(); + + // Successful test - all transfers complete within batch_time complete. + let wallet_27_name = "w27"; + let wallet_28_name = "w28"; + let wallet_29_name = "w29"; + let wallet_30_name = "w30"; + await createWallet(clientConfig, wallet_27_name); + await createWallet(clientConfig, wallet_28_name); + await createWallet(clientConfig, wallet_29_name); + await createWallet(clientConfig, wallet_30_name); + await atomicSwapSuccess(clientConfig, wallet_27_name, wallet_28_name, wallet_29_name, wallet_30_name); + console.log("Completed test for Successful test - all transfers complete within batch_time complete."); + + // Second party performs transfer-sender with incorrect or missing batch-id. First party should still receive OK. + let wallet_31_name = "w31"; + let wallet_32_name = "w32"; + let wallet_33_name = "w33"; + let wallet_34_name = "w34"; + await createWallet(clientConfig, wallet_31_name); + await createWallet(clientConfig, wallet_32_name); + await createWallet(clientConfig, wallet_33_name); + await createWallet(clientConfig, wallet_34_name); + await atomicSwapWithSecondBatchIdMissing(clientConfig, wallet_31_name, wallet_32_name, wallet_33_name, wallet_34_name); + console.log("Completed test for Second party performs transfer-sender with incorrect or missing batch-id. First party should still receive OK."); + + // First party performs transfer-sender without batch_id. + let wallet_35_name = "w35"; + let wallet_36_name = "w36"; + let wallet_37_name = "w37"; + let wallet_38_name = "w38"; + await createWallet(clientConfig, wallet_35_name); + await createWallet(clientConfig, wallet_36_name); + await createWallet(clientConfig, wallet_37_name); + await createWallet(clientConfig, wallet_38_name); + await atomicSwapWithoutFirstBatchId(clientConfig, wallet_35_name, wallet_36_name, wallet_37_name, wallet_38_name); + console.log("Completed test for First party performs transfer-sender without batch_id."); + + // One party doesn't complete transfer-receiver before the timeout. + // Both wallets should be able to repeat transfer-sender and transfer-receiver back to new addresses without error, + // after the timeout. + let wallet_39_name = "w39"; + let wallet_40_name = "w40"; + let wallet_41_name = "w41"; + let wallet_42_name = "w42"; + await createWallet(clientConfig, wallet_39_name); + await createWallet(clientConfig, wallet_40_name); + await createWallet(clientConfig, wallet_41_name); + await createWallet(clientConfig, wallet_42_name); + await atomicSwapWithTimeout(clientConfig, wallet_39_name, wallet_40_name, wallet_41_name, wallet_42_name); + console.log("Completed test for One party doesn't complete transfer-receiver before the timeout."); + + // First party tries to steal within timeout + // they perform transfer-sender a second time sending back to one of their own addresses - should fail. + let wallet_43_name = "w43"; + let wallet_44_name = "w44"; + let wallet_45_name = "w45"; + let wallet_46_name = "w46"; + await createWallet(clientConfig, wallet_43_name); + await createWallet(clientConfig, wallet_44_name); + await createWallet(clientConfig, wallet_45_name); + await createWallet(clientConfig, wallet_46_name); + await atomicSwapWithFirstPartySteal(clientConfig, wallet_43_name, wallet_44_name, wallet_45_name, wallet_46_name); + console.log("Completed test for First party tries to steal within timeout"); + + // Second party tries to steal within timeout + // they perform transfer-sender a second time sending back to one of their own addresses - should fail. + let wallet_47_name = "w47"; + let wallet_48_name = "w48"; + let wallet_49_name = "w49"; + let wallet_50_name = "w50"; + await createWallet(clientConfig, wallet_47_name); + await createWallet(clientConfig, wallet_48_name); + await createWallet(clientConfig, wallet_49_name); + await createWallet(clientConfig, wallet_50_name); + await atomicSwapWithSecondPartySteal(clientConfig, wallet_47_name, wallet_48_name, wallet_49_name, wallet_50_name); + console.log("Completed test for Second party tries to steal within timeout"); + + process.exit(0); // Exit successfully + } catch (error) { + console.error("Test encountered an error:", error); + process.exit(1); // Exit with failure + } +})(); \ No newline at end of file diff --git a/clients/apps/nodejs/test_basic_workflow.js b/clients/apps/nodejs/test_basic_workflow.js new file mode 100644 index 00000000..35f7303c --- /dev/null +++ b/clients/apps/nodejs/test_basic_workflow.js @@ -0,0 +1,232 @@ +const util = require('node:util'); +const exec = util.promisify(require('node:child_process').exec); +const assert = require('node:assert/strict'); +const { CoinStatus } = require('mercurynodejslib/coin_enum'); + +async function removeDatabase() { + try { + const { stdout, stderr } = await exec('rm wallet.db'); + // console.log('stdout:', stdout); + // console.error('stderr:', stderr); + } catch (e) { + // console.error(e); + } +} + +async function createWallet(wallet_name) { + const { stdout, stderr } = await exec(`node index.js create-wallet ${wallet_name}`); + let wallet = JSON.parse(stdout); + assert.equal(wallet.name, wallet_name); + // console.log('wallet:', wallet); +} + +async function newToken(wallet_name) { + const { stdout, stderr } = await exec(`node index.js new-token ${wallet_name}`); + let json = JSON.parse(stdout); + return json; +} + +async function listTokens(wallet_name) { + const { stdout, stderr } = await exec(`node index.js list-tokens ${wallet_name}`); + let json = JSON.parse(stdout); + return json; +} + +async function getDepositBitcoinAddress(wallet_name, token_id, amount) { + const { stdout, stderr } = await exec(`node index.js new-deposit-address ${wallet_name} ${token_id} ${amount}`); + let json = JSON.parse(stdout); + return json; +} + +async function listStatecoins(wallet_name) { + try { + const { stdout, stderr } = await exec(`node index.js list-statecoins ${wallet_name}`); + let json = JSON.parse(stdout); + return json; + } catch (e) { + console.log('e:', e); + return undefined; + } +} + +async function newTransferAddress(wallet_name) { + const { stdout, stderr } = await exec(`node index.js new-transfer-address ${wallet_name}`); + let json = JSON.parse(stdout); + return json.transfer_receive; +} + +async function transferSend(wallet_name, statechain_id, to_address) { + const { stdout, stderr } = await exec(`node index.js transfer-send ${wallet_name} ${statechain_id} ${to_address}`); + let json = JSON.parse(stdout); + return json; +} + +async function transferReceive(wallet_name) { + const { stdout, stderr } = await exec(`node index.js transfer-receive ${wallet_name}`); + let json = JSON.parse(stdout); + return json; +} + +async function withdraw(wallet_name, statechain_id, to_address) { + const { stdout, stderr } = await exec(`node index.js withdraw ${wallet_name} ${statechain_id} ${to_address}`); + let json = JSON.parse(stdout); + return json.txid; +} + +async function broadcastBackupTransaction(wallet_name, statechain_id, to_address) { + const { stdout, stderr } = await exec(`node index.js broadcast-backup-transaction ${wallet_name} ${statechain_id} ${to_address}`); + let json = JSON.parse(stdout); + return json; +} + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function walletTransfersToItselfAndWithdraw(wallet_name) { + + const amount = 10000; + + const token = await newToken(wallet_name); + const tokenId = token.token_id; + + const deposit_info = await getDepositBitcoinAddress(wallet_name, amount); + + let tokenList = await listTokens(wallet_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + deposit_info["amount"] = amount; + console.log("deposit_coin: ", deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await listStatecoins(wallet_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + continue; + } + + coin = coinsWithStatechainId[0]; + + break; + } + + console.log("coin: ", coin); + + for (let i = 0; i < 10; i++) { + + let transfer_address = await newTransferAddress(wallet_name); + + console.log("transfer_address: ", transfer_address); + + coin = await transferSend(wallet_name, coin.statechain_id, transfer_address); + + console.log("coin transferSend: ", coin); + + let received_statechain_ids = await transferReceive(wallet_name); + + console.log("received_statechain_ids: ", received_statechain_ids); + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + } + + let withdraw_address = "tb1qwrujs6f4gyexsextpf9p50smjtht7p7ypknteu"; + + let txid = await withdraw(wallet_name, coin.statechain_id, withdraw_address); + + console.log("txid: ", txid); + +}; + +async function walletTransfersMultipleTimesToItselfAndBroadcastsBackup(wallet_name) { + for (let i = 0; i < 3; i++) { + await walletTransfersToItselfAndWithdraw(wallet_name); + } +} + +async function walletTransfersToAnotherAndBroadcastsBackupTx(wallet_1_name, wallet_2_name) { + + const amount = 10000; + + const token = await newToken(wallet_1_name); + const tokenId = token.token_id; + + const deposit_info = await getDepositBitcoinAddress(wallet_1_name, amount); + + deposit_info["amount"] = amount; + console.log("deposit_info w1: ", deposit_info); + + let tokenList = await listTokens(wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + console.log("usedToken: ", usedToken); + + assert(usedToken.spent); + + let coin = undefined; + + while (!coin) { + const list_coins = await listStatecoins(wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(5000); + continue; + } + + coin = coinsWithStatechainId[0]; + + break; + } + + let transfer_address = await newTransferAddress(wallet_2_name); + + coin = await transferSend(wallet_1_name, coin.statechain_id, transfer_address); + + let received_statechain_ids = await transferReceive(wallet_2_name); + + console.log("received_statechain_ids: ", received_statechain_ids); + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + + let withdraw_address = "tb1qwrujs6f4gyexsextpf9p50smjtht7p7ypknteu"; + + let txids = await broadcastBackupTransaction(wallet_2_name, received_statechain_ids[0], withdraw_address); + + console.log("txids: ", txids); + +} + +(async () => { + let wallet_1_name = "w1"; + let wallet_2_name = "w2"; + + await removeDatabase(); + await createWallet(wallet_1_name); + await createWallet(wallet_2_name); + + await walletTransfersMultipleTimesToItselfAndBroadcastsBackup(wallet_1_name); + + await walletTransfersToAnotherAndBroadcastsBackupTx(wallet_1_name, wallet_2_name) + + await removeDatabase(); +})(); diff --git a/clients/apps/nodejs/test_basic_workflow2.js b/clients/apps/nodejs/test_basic_workflow2.js new file mode 100644 index 00000000..75bf20ad --- /dev/null +++ b/clients/apps/nodejs/test_basic_workflow2.js @@ -0,0 +1,1074 @@ +const util = require('node:util'); +const exec = util.promisify(require('node:child_process').exec); +const assert = require('node:assert/strict'); +const mercurynodejslib = require('mercurynodejslib'); +const { CoinStatus } = require('mercurynodejslib/coin_enum'); +const client_config = require('./client_config'); +const sqlite_manager = require('../../libs/nodejs/sqlite_manager'); +const mercury_wasm = require('mercury-wasm'); +const transaction = require('../../libs/nodejs/transaction'); +const utils = require('../../libs/nodejs/utils'); +const { getDatabase, sleep, createWallet, getElectrumClient, generateBlock, depositCoin, connectElectr, disconnectElectr, disconnectMercuryServer, connectMercuryServer } = require('./test_utils'); + +async function walletTransfersToItselfAndWithdraw(clientConfig, wallet_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_name); + // console.log(list_coins); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + + break; + } + + for (let i = 0; i < 10; i++) { + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + } + + let withdraw_address = "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"; + + let txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet_name, coin.statechain_id, withdraw_address, null, null); + + // TODO: confirm withdrawal status +} + +async function walletTransfersToItselfTillLocktimeReachesBlockHeightAndWithdraw(clientConfig, wallet_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(5000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + + break; + } + + const electrumClient = await getElectrumClient(clientConfig); + + let block_header = await electrumClient.request('blockchain.headers.subscribe'); + let currentBlockHeight = block_header.height; + + while (coin.locktime <= currentBlockHeight) { + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + + // Fetch the coin again to get the updated locktime + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_name); + coin = list_coins.find(c => c.statechain_id === coin.statechain_id); + } + + let withdraw_address = "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"; + + let txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet_name, coin.statechain_id, withdraw_address, null, null); + + // TODO: confirm withdrawal status +} + +async function walletTransfersToAnotherAndBroadcastsBackupTx(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + + let withdraw_address = "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"; + + let txid = await mercurynodejslib.broadcastBackupTransaction(clientConfig, wallet_2_name, coin.statechain_id, withdraw_address, null); + + // TODO: confirm withdrawal status +} + +async function depositAndRepeatSend(clientConfig, wallet_1_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + for (let i = 0; i < 10; i++) { + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_1_name, null); + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_1_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); +} + +async function transferSenderAfterTransferReceiver(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + + try { + transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + assert.fail("Expected error when transferring from wallet one again, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is TRANSFERRED"), + `Unexpected error message: ${error.message}`); + } +} + +async function depositAndTransfer(clientConfig, wallet_name) { + + for (let i = 0; i < 10; i++) { + const token = await mercurynodejslib.newToken(clientConfig, wallet_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + + break; + } + } + + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_name); + + for (let coin of list_coins) { + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + } +} + +async function interruptBeforeSignFirst(clientConfig, wallet_1_name, wallet_2_name) { + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + console.log("Disconnect mercurylayer_mercury_1 from network"); + await disconnectMercuryServer(); + + try { + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + assert.fail("Expected error when transferring from wallet one, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:8000"), + `Unexpected error message: ${error.message}`); + } + console.log("Connect mercurylayer_mercury_1 from network"); + await connectMercuryServer(); +} + +const new_transaction = async(clientConfig, electrumClient, coin, toAddress, isWithdrawal, qtBackupTx, block_height, network) => { + let coin_nonce = mercury_wasm.createAndCommitNonces(coin); + + let server_pubnonce = await transaction.signFirst(clientConfig, coin_nonce.sign_first_request_payload); + + coin.secret_nonce = coin_nonce.secret_nonce; + coin.public_nonce = coin_nonce.public_nonce; + coin.server_public_nonce = server_pubnonce; + coin.blinding_factor = coin_nonce.blinding_factor; + + const serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + let new_block_height = 0; + if (block_height == null) { + const block_header = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + new_block_height = block_header.height; + } else { + new_block_height = block_height; + } + + const initlock = serverInfo.initlock; + const interval = serverInfo.interval; + const feeRateSatsPerByte = serverInfo.fee_rate_sats_per_byte; + + let partialSigRequest = mercury_wasm.getPartialSigRequest( + coin, + new_block_height, + initlock, + interval, + feeRateSatsPerByte, + qtBackupTx, + toAddress, + network, + isWithdrawal); + + const serverPartialSigRequest = partialSigRequest.partial_signature_request_payload; + + console.log("Disconnect mercurylayer_mercury_1 from network"); + await disconnectMercuryServer(); + + let serverPartialSig; + + try { + serverPartialSig = await transaction.signSecond(clientConfig, serverPartialSigRequest); + assert.fail("Expected error when signing second transaction, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("Server partial signature is not available."), + `Unexpected error message: ${error.message}`); + } + + console.log("Connect mercurylayer_mercury_1 from network"); + await connectMercuryServer(); +} + +async function interruptBeforeSignSecond(clientConfig, wallet_1_name, wallet_2_name) { + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coinDeposited = undefined; + + while (!coinDeposited) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coinDeposited = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + let options = transfer_address; + + let batchId = (options && options.batch_id) || null; + + let wallet = await sqlite_manager.getWallet(db, wallet_1_name); + + const backupTxs = await sqlite_manager.getBackupTxs(db, coinDeposited.statechain_id); + + if (backupTxs.length === 0) { + throw new Error(`There is no backup transaction for the statechain id ${coinDeposited.statechain_id}`); + } + + const new_tx_n = backupTxs.length + 1; + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === coinDeposited.statechain_id + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${coinDeposited.statechain_id}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + if (coin.locktime == null) { + throw new Error("Coin.locktime is null"); + } + + const blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + const currentBlockheight = blockHeader.height; + + if (currentBlockheight > coin.locktime) { + throw new Error(`The coin is expired. Coin locktime is ${coin.locktime} and current blockheight is ${currentBlockheight}`); + } + + const statechain_id = coin.statechain_id; + const signed_statechain_id = coin.signed_statechain_id; + + const isWithdrawal = false; + const qtBackupTx = backupTxs.length; + + backupTxs.sort((a, b) => a.tx_n - b.tx_n); + + const bkp_tx1 = backupTxs[0]; + + const block_height = mercury_wasm.getBlockheight(bkp_tx1); + + const decodedTransferAddress = mercury_wasm.decodeTransferAddress(transfer_address.transfer_receive); + const new_auth_pubkey = decodedTransferAddress.auth_pubkey; + + // const new_x1 = await get_new_x1(clientConfig, statechain_id, signed_statechain_id, new_auth_pubkey, batchId); + + const signed_tx = await new_transaction(clientConfig, electrumClient, coin, transfer_address.transfer_receive, isWithdrawal, qtBackupTx, block_height, wallet.network); + + transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + console.log("coin ", coin); + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + console.log("received_statechain_ids: ", received_statechain_ids); + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); + + // Coin withdrawal + let withdraw_address = "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"; + + let txid = await mercurynodejslib.withdrawCoin(clientConfig, wallet_2_name, coin.statechain_id, withdraw_address, null, null); + + console.log("txid: ", txid); +} + +async function interruptSignWithElectrumUnavailability(clientConfig, wallet_1_name, wallet_2_name) { + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + // console.log("Waiting for coin to be confirmed..."); + // console.log(`Check the address ${deposit_info.deposit_address} ...\n`); + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + await sleep(5000); // wait for Electrum to disconnect + + console.log("Disconnect mercurylayer_electrs_1 from network"); + await disconnectElectr(); + + try { + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + assert.fail("Expected error when transferring from wallet one, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:50001"), + `Unexpected error message: ${error.message}`); + } + console.log("Connect mercurylayer_electrs_1 from network"); + await connectElectr(); + + await sleep(5000); // wait for Electrum to connect +} + +async function interruptTransferReceiveWithElectrumUnavailability(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + await sleep(5000); // wait for Electrum to disconnect + + console.log("Disconnect mercurylayer_electrs_1 from network"); + await disconnectElectr(); + + try { + await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + assert.fail("Expected error when receiving into wallet two, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:50001"), + `Unexpected error message: ${error.message}`); + } + console.log("Connect mercurylayer_electrs_1 from network"); + await connectElectr(); + + await sleep(5000); // wait for Electrum to connect +} + +async function interruptTransferReceiveWithMercuryServerUnavailability(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(5000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + console.log("Disconnect mercurylayer_mercury_1 from network"); + await disconnectMercuryServer(); + + try { + let received_statechain_ids = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + assert.fail("Expected error when receiving into wallet two, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("connect ECONNREFUSED 0.0.0.0:8000"), + `Unexpected error message: ${error.message}`); + } + console.log("Connect mercurylayer_mercury_1 from network"); + await connectMercuryServer(); +} + +async function transferSendAtCoinExpiry(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(1000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + const electrumClient = await getElectrumClient(clientConfig); + + const blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + const currentBlockheight = blockHeader.height; + + const blocksToBeGenerated = coin.locktime - currentBlockheight; + await generateBlock(blocksToBeGenerated); + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + try { + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + assert.fail("Expected error when transferring expired coin, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("The coin is expired."), + `Unexpected error message: ${error.message}`); + } +} + +async function transferReceiveAtCoinExpiry(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(5000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + + const electrumClient = await getElectrumClient(clientConfig); + + const blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + const currentBlockheight = blockHeader.height; + + const blocksToBeGenerated = coin.locktime - currentBlockheight; + await generateBlock(blocksToBeGenerated); + + let errorMessage; + console.error = (msg) => { + errorMessage = msg; + }; + + let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_2_name); + let received_statechain_ids = transferReceiveResult.receivedStatechainIds; + + // Assert the captured error message + const expectedMessage = 'The coin is expired.'; + assert.ok(errorMessage.includes(expectedMessage)); + + assert(received_statechain_ids.length > 0); + assert(received_statechain_ids[0] == coin.statechain_id); +} + +async function transferSendCoinExpiryBySending(clientConfig, wallet_1_name, wallet_2_name) { + + const token = await mercurynodejslib.newToken(clientConfig, wallet_1_name); + const tokenId = token.token_id; + + const amount = 10000; + const deposit_info = await mercurynodejslib.getDepositBitcoinAddress(clientConfig, wallet_1_name, amount); + + let tokenList = await mercurynodejslib.getWalletTokens(clientConfig, wallet_1_name); + + let usedToken = tokenList.find(token => token.token_id === tokenId); + + assert(usedToken.spent); + + await depositCoin(clientConfig, wallet_1_name, amount, deposit_info); + + let coin = undefined; + + while (!coin) { + const list_coins = await mercurynodejslib.listStatecoins(clientConfig, wallet_1_name); + + let coinsWithStatechainId = list_coins.filter(c => { + return c.statechain_id === deposit_info.statechain_id && c.status === CoinStatus.CONFIRMED; + }); + + if (coinsWithStatechainId.length === 0) { + /* console.log("Waiting for coin to be confirmed..."); + console.log(`Check the address ${deposit_info.deposit_address} ...\n`); */ + await sleep(5000); + generateBlock(1); + continue; + } + + coin = coinsWithStatechainId[0]; + break; + } + + const electrumClient = await getElectrumClient(clientConfig); + + const blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + const currentBlockheight = blockHeader.height; + + const serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + const blocksToBeGenerated = coin.locktime - currentBlockheight - serverInfo.interval; + await generateBlock(blocksToBeGenerated); + + let transfer_address = await mercurynodejslib.newTransferAddress(clientConfig, wallet_2_name, null); + + try { + coin = await mercurynodejslib.transferSend(clientConfig, wallet_1_name, coin.statechain_id, transfer_address.transfer_receive, false, null); + assert.fail("Expected error when transferring expired coin, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("The coin is expired."), + `Unexpected error message: ${error.message}`); + } +} + +(async () => { + + try { + const clientConfig = client_config.load(); + + let wallet_1_name = "w1"; + let wallet_2_name = "w2"; + await createWallet(clientConfig, wallet_1_name); + await createWallet(clientConfig, wallet_2_name); + await walletTransfersToItselfAndWithdraw(clientConfig, wallet_1_name); + // await walletTransfersToAnotherAndBroadcastsBackupTx(clientConfig, wallet_1_name, wallet_2_name); + + + // Deposit, repeat send + let wallet_3_name = "w3"; + let wallet_4_name = "w4"; + await createWallet(clientConfig, wallet_3_name); + await createWallet(clientConfig, wallet_4_name); + await depositAndRepeatSend(clientConfig, wallet_3_name); + console.log("Completed test for Deposit, repeat send"); + + // Transfer-sender after transfer-receiver + let wallet_5_name = "w5"; + let wallet_6_name = "w6"; + await createWallet(clientConfig, wallet_5_name); + await createWallet(clientConfig, wallet_6_name); + await transferSenderAfterTransferReceiver(clientConfig, wallet_5_name, wallet_6_name); + console.log("Completed test for Transfer-sender after transfer-receiver"); + + // Deposit of 10 coins in same wallet, and transfer each one 10 times + let wallet_7_name = "w7"; + await createWallet(clientConfig, wallet_7_name); + await depositAndTransfer(clientConfig, wallet_7_name); + console.log("Completed test for Deposit of 100 coins in same wallet, and transfer each one 100 times"); + + // Test for interruption of transferSend before sign first + let wallet_8_name = "w8"; + let wallet_9_name = "w9"; + await createWallet(clientConfig, wallet_8_name); + await createWallet(clientConfig, wallet_9_name); + await interruptBeforeSignFirst(clientConfig, wallet_8_name, wallet_9_name); + console.log("Completed test for interruption of transferSend before sign first"); + + // Test for interruption of transferSend before sign second + let wallet_10_name = "w10"; + let wallet_11_name = "w11"; + await createWallet(clientConfig, wallet_10_name); + await createWallet(clientConfig, wallet_11_name); + await interruptBeforeSignSecond(clientConfig, wallet_10_name, wallet_11_name); + console.log("Completed test for interruption of transferSend before sign second"); + + // Test for interruption of sign with Electrum unavailability + let wallet_12_name = "w12"; + let wallet_13_name = "w13"; + await createWallet(clientConfig, wallet_12_name); + await createWallet(clientConfig, wallet_13_name); + await interruptSignWithElectrumUnavailability(clientConfig, wallet_12_name, wallet_13_name); + console.log("Completed test for interruption of sign with Electrum unavailability"); + + // Test for interruption of transfer receive with Electrum unavailability + let wallet_14_name = "w14"; + let wallet_15_name = "w15"; + await createWallet(clientConfig, wallet_14_name); + await createWallet(clientConfig, wallet_15_name); + await interruptTransferReceiveWithElectrumUnavailability(clientConfig, wallet_14_name, wallet_15_name); + console.log("Completed test for interruption of transfer receive with Electrum unavailability"); + + // Test for interruption of transfer receive with mercury server unavailability + let wallet_16_name = "w16"; + let wallet_17_name = "w17"; + await createWallet(clientConfig, wallet_16_name); + await createWallet(clientConfig, wallet_17_name); + await interruptTransferReceiveWithMercuryServerUnavailability(clientConfig, wallet_16_name, wallet_17_name); + console.log("Completed test for interruption of transfer receive with mercury server unavailability"); + + // Deposit, iterative self transfer + let wallet_18_name = "w18"; + await createWallet(clientConfig, wallet_18_name); + await walletTransfersToItselfTillLocktimeReachesBlockHeightAndWithdraw(clientConfig, wallet_18_name); + console.log("Completed test for Deposit, iterative self transfer"); + + // Send backup tx before expiry + let wallet_19_name = "w19"; + let wallet_20_name = "w20"; + await createWallet(clientConfig, wallet_19_name); + await createWallet(clientConfig, wallet_20_name); + try { + await walletTransfersToAnotherAndBroadcastsBackupTx(clientConfig, wallet_19_name, wallet_20_name) + assert.fail("Expected error when sending backup tx before expiry, but no error was thrown"); + } catch (error) { + console.log("Expected error received: ", error.message); + assert(error.message.includes("The coin is not expired yet."), + `Unexpected error message: ${error.message}`); + } + console.log("Completed test for send backup tx before expiry"); + + // Transfer-sender of coin at expiry + let wallet_21_name = "w21"; + let wallet_22_name = "w22"; + await createWallet(clientConfig, wallet_21_name); + await createWallet(clientConfig, wallet_22_name); + await transferSendAtCoinExpiry(clientConfig, wallet_21_name, wallet_22_name); + console.log("Completed test for Transfer-sender of coin at expiry"); + + // Transfer-receive of coin at expiry + let wallet_23_name = "w23"; + let wallet_24_name = "w24"; + await createWallet(clientConfig, wallet_23_name); + await createWallet(clientConfig, wallet_24_name); + await transferReceiveAtCoinExpiry(clientConfig, wallet_23_name, wallet_24_name); + console.log("Completed test for Transfer-receive of coin at expiry"); + + // Transfer-sender of coin that will make it expired by sending + let wallet_25_name = "w25"; + let wallet_26_name = "w26"; + await createWallet(clientConfig, wallet_25_name); + await createWallet(clientConfig, wallet_26_name); + await transferSendCoinExpiryBySending(clientConfig, wallet_25_name, wallet_26_name); + console.log("Completed test for Transfer-sender of coin that will make it expired by sending"); + + process.exit(0); // Exit successfully + } catch (error) { + console.error("Test encountered an error:", error); + process.exit(1); // Exit with failure + } +})(); diff --git a/clients/apps/nodejs/test_utils.js b/clients/apps/nodejs/test_utils.js new file mode 100644 index 00000000..e6972cc0 --- /dev/null +++ b/clients/apps/nodejs/test_utils.js @@ -0,0 +1,115 @@ + +const util = require('node:util'); +const ElectrumCli = require('@mempool/electrum-client'); +const sqlite3 = require('sqlite3').verbose(); +const mercurynodejslib = require('mercurynodejslib'); +const exec = util.promisify(require('node:child_process').exec); +const assert = require('node:assert/strict'); +const client_config = require('./client_config'); +const sqlite_manager = require('../../libs/nodejs/sqlite_manager'); + +const removeDatabase = async () => { + try { + const clientConfig = client_config.load(); + const { stdout, stderr } = await exec(`rm ${clientConfig.databaseFile}`); + // console.log('stdout:', stdout); + // console.error('stderr:', stderr); + } catch (e) { + console.error(e); + } +} + +const getDatabase = async (clientConfig) => { + const databaseFile = clientConfig.databaseFile; + const db = new sqlite3.Database(databaseFile); + await sqlite_manager.createTables(db); + return db; +} + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const createWallet = async (clientConfig, walletName) => { + + let wallet = await mercurynodejslib.createWallet(clientConfig, walletName); + assert.equal(wallet.name, walletName); + + // TODO: add more assertions +} + +const getElectrumClient = async (clientConfig) => { + + const urlElectrum = clientConfig.electrumServer; + const urlElectrumObject = new URL(urlElectrum); + + const electrumPort = parseInt(urlElectrumObject.port, 10); + const electrumHostname = urlElectrumObject.hostname; + const electrumProtocol = urlElectrumObject.protocol.slice(0, -1); + + const electrumClient = new ElectrumCli(electrumPort, electrumHostname, electrumProtocol); + await electrumClient.connect(); + + return electrumClient; +} + +const generateBlock = async (numBlocks) => { + const generateBlockCommand = `docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress ${numBlocks} "bcrt1qgh48u8aj4jvjkalc28lqujyx2wveck4jsm59x9"`; + await exec(generateBlockCommand); + // console.log(`Generated ${numBlocks} blocks`); + + const clientConfig = client_config.load(); + const electrumClient = await getElectrumClient(clientConfig); + const block_header = await electrumClient.request('blockchain.headers.subscribe'); + const blockheight = block_header.height; + // console.log("Current block height: ", blockheight); +} + +const depositCoin = async (clientConfig, wallet_name, amount, deposit_info) => { + + deposit_info["amount"] = amount; + // console.log("deposit_coin: ", deposit_info); + + const amountInBtc = amount / 100000000; + + // Sending Bitcoin using bitcoin-cli + try { + const sendBitcoinCommand = `docker exec $(docker ps -qf "name=mercurylayer_bitcoind_1") bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress ${deposit_info.deposit_address} ${amountInBtc}`; + await exec(sendBitcoinCommand); + // console.log(`Sent ${amountInBtc} BTC to ${deposit_info.deposit_address}`); + await generateBlock(3); + } catch (error) { + console.error('Error sending Bitcoin:', error.message); + return; + } +} + +const disconnectMercuryServer = async () => { + await exec("docker network disconnect mercurylayer_default mercurylayer_mercury_1"); +} + +const connectMercuryServer = async () => { + await exec("docker network connect mercurylayer_default mercurylayer_mercury_1"); +} + +const disconnectElectr = async () => { + await exec("docker network disconnect mercurylayer_default mercurylayer_electrs_1"); +} + +const connectElectr = async () => { + await exec("docker network connect mercurylayer_default mercurylayer_electrs_1"); +} + +module.exports = { + removeDatabase, + getDatabase, + sleep, + createWallet, + getElectrumClient, + generateBlock, + depositCoin, + connectElectr, + disconnectElectr, + connectMercuryServer, + disconnectMercuryServer +}; diff --git a/clients/apps/rust/.gitignore b/clients/apps/rust/.gitignore new file mode 100644 index 00000000..a0c4a4b8 --- /dev/null +++ b/clients/apps/rust/.gitignore @@ -0,0 +1,3 @@ +/target +wallet.db* +wallet.json diff --git a/clients/apps/rust/Cargo.toml b/clients/apps/rust/Cargo.toml new file mode 100644 index 00000000..3a2837e2 --- /dev/null +++ b/clients/apps/rust/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "client-rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +bech32 = { version = "0.9.1", default-features = false } +bitcoin = { version = "0.30.1", features = ["serde", "base64", "rand-std", "std", "bitcoinconsensus"], default-features = false } +bip39 = "1.2.0" +clap = { version = "4.2.5", features = ["derive"]} +chrono = "0.4.31" +config = "0.13.1" +electrum-client = "0.18.0" +hex = "0.4.3" +rand = "0.8.5" +reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } +schemars = { version = "0.8.12", features = ["chrono", "uuid"] } +secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } +tokio = { version = "1.27.0", features = ["full"] } +uuid = { version = "1.3.1", features = ["v4", "serde"] } +mercuryrustlib = { path = "../../libs/rust" } diff --git a/clients/apps/rust/Readme.md b/clients/apps/rust/Readme.md new file mode 100644 index 00000000..4aaa5b96 --- /dev/null +++ b/clients/apps/rust/Readme.md @@ -0,0 +1,31 @@ +# Mercury Layer Client + +Mercury layer client provides a user interface to the Mercury Layer protocol, via the Mercury Layer server. + +# Running + +1. Run the `enclave` project on localhost +2. Run the `server` project on localhost +3. Run one of the commands below + +# Some commands + +`cargo run create-wallet ` to create a wallet + +`cargo run new-token` to create a new token + +`cargo run new-deposit-address ` creates a deposit address + +`cargo run list-statecoins ` shows wallet coins + +`cargo run new-transfer-address ` generates a statechain address to receive statechain coins + +`cargo run transfer-send ` transfers the specified statechain coin to the specified address + +`cargo run transfer-receive ` scans for new statechain transfers + +`cargo run withdraw ` withdraws the statechain coin to the specified bitcoin address + +`cargo run broadcast-backup-transaction ` broadcasts the backup transaction to the network + +This is a work in progress. Several changes to the project are expected. \ No newline at end of file diff --git a/clients/apps/rust/Settings.toml b/clients/apps/rust/Settings.toml new file mode 100644 index 00000000..56eb6bd8 --- /dev/null +++ b/clients/apps/rust/Settings.toml @@ -0,0 +1,13 @@ +statechain_entity = "http://127.0.0.1:8000" +#statechain_entity = "http://j23wevaeducxuy3zahd6bpn4x76cymwz2j3bdixv7ow4awjrg5p6jaid.onion" +#electrum_server = "tcp://127.0.0.1:50001" +electrum_server = "tcp://signet-electrumx.wakiyamap.dev:50001" +electrum_type = "electrs" +network = "signet" +fee_rate_tolerance = 5 +database_file="wallet.db" +confirmation_target = 2 +#tor_proxy = "socks5h://localhost:9050" +# max fee rate in sat/vbyte +# if the fee rate is higher than this, the transaction will use this max fee rate +max_fee_rate = 1 diff --git a/clients/apps/rust/regtest.Settings.toml b/clients/apps/rust/regtest.Settings.toml new file mode 100644 index 00000000..f342e9ed --- /dev/null +++ b/clients/apps/rust/regtest.Settings.toml @@ -0,0 +1,11 @@ +statechain_entity = "http://127.0.0.1:8000" +electrum_server = "tcp://localhost:50001" +electrum_type = "electrs" +network = "regtest" +fee_rate_tolerance = 5 +database_file="wallet.db" +confirmation_target = 2 +#tor_proxy = "socks5h://localhost:9050" +# max fee rate in sat/vbyte +# if the fee rate is higher than this, the transaction will use this max fee rate +max_fee_rate = 1 diff --git a/clients/apps/rust/rust-toolchain.toml b/clients/apps/rust/rust-toolchain.toml new file mode 100644 index 00000000..624eb0ea --- /dev/null +++ b/clients/apps/rust/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.76.0" diff --git a/clients/apps/rust/src/main.rs b/clients/apps/rust/src/main.rs new file mode 100644 index 00000000..8772573b --- /dev/null +++ b/clients/apps/rust/src/main.rs @@ -0,0 +1,212 @@ +use std::{thread, time::Duration}; + +use anyhow::Result; +use clap::{Parser, Subcommand}; +use serde_json::json; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Create a wallet + CreateWallet { + /// The name of the wallet to create + name: String + }, + /// Get new token. + NewToken { }, + /// Get new deposit address. Used to fund a new statecoin. + NewDepositAddress { wallet_name: String, token_id: String, amount: u32 }, + /// Broadcast the backup transaction to the network + BroadcastBackupTransaction { + wallet_name: String, + statechain_id: String, + to_address: String, + /// Transaction fee rate in sats per byte + fee_rate: Option + }, + /// Broadcast the backup transaction to the network + ListStatecoins { wallet_name: String }, + /// Withdraw funds from a statechain coin to a bitcoin address + Withdraw { + wallet_name: String, + statechain_id: String, + to_address: String, + /// Transaction fee rate in sats per byte + fee_rate: Option, + duplicated_index: Option + }, + /// Generate a transfer address to receive funds + NewTransferAddress { + wallet_name: String, + /// Generate batch id for atomic transfers + #[arg(short='b', long)] + generate_batch_id: bool, + }, + /// Send a statechain coin to a transfer address + TransferSend { + wallet_name: String, + statechain_id: String, + to_address: String, + // Force send (required when the coin is duplicated) + force_send: Option, + /// Batch id for atomic transfers + batch_id: Option, + }, + /// Send a statechain coin to a transfer address + TransferReceive { wallet_name: String }, + /// Create a payment hash for a lightning latch + PaymentHash { + wallet_name: String, + statechain_id: String, + }, + /// Confirm pending invoice + ConfirmPendingInvoice { + wallet_name: String, + statechain_id: String, + }, + /// Retrieve a payment pre-image for a lightning latch + RetrievePreImage { + wallet_name: String, + statechain_id: String, + batch_id: String, + }, +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + + let cli = Cli::parse(); + + let client_config = mercuryrustlib::client_config::load().await; + + match cli.command { + Commands::CreateWallet { name } => { + let wallet = mercuryrustlib::wallet::create_wallet( + &name, + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet).await?; + println!("Wallet created: {:?}", wallet); + }, + Commands::NewToken { } => { + let token_id = mercuryrustlib::deposit::get_token(&client_config).await?; + + let obj = json!({"token": token_id}); + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::NewDepositAddress { wallet_name, token_id, amount } => { + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet_name, &token_id, amount).await?; + + let obj = json!({"address": address}); + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::BroadcastBackupTransaction { wallet_name, statechain_id, to_address, fee_rate } => { + mercuryrustlib::coin_status::update_coins(&client_config, &wallet_name).await?; + mercuryrustlib::broadcast_backup_tx::execute(&client_config, &wallet_name, &statechain_id, &to_address, fee_rate).await?; + }, + Commands::ListStatecoins { wallet_name } => { + mercuryrustlib::coin_status::update_coins(&client_config, &wallet_name).await?; + let wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet_name).await?; + + let mut coins_json = Vec::new(); + + for coin in wallet.coins.iter() { + let obj = json!({ + "coin.user_pubkey": coin.user_pubkey, + "coin.aggregated_address": coin.aggregated_address.as_ref().unwrap_or(&"".to_string()), + "coin.address": coin.address, + "coin.statechain_id": coin.statechain_id.as_ref().unwrap_or(&"".to_string()), + "coin.amount": coin.amount.unwrap_or(0), + "coin.status": coin.status, + "coin.locktime": coin.locktime.unwrap_or(0), + }); + + coins_json.push(obj); + } + + let coins_json_string = serde_json::to_string_pretty(&coins_json).unwrap(); + println!("{}", coins_json_string); + }, + Commands::Withdraw { wallet_name, statechain_id, to_address, fee_rate, duplicated_index } => { + mercuryrustlib::coin_status::update_coins(&client_config, &wallet_name).await?; + mercuryrustlib::withdraw::execute(&client_config, &wallet_name, &statechain_id, &to_address, fee_rate, duplicated_index).await?; + }, + Commands::NewTransferAddress { wallet_name, generate_batch_id } => { + let address = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet_name).await?; + + let mut obj = json!({"new_transfer_address:": address}); + + if generate_batch_id { + // Generate a random batch_id + let batch_id = Some(uuid::Uuid::new_v4().to_string()).unwrap(); + + obj["batch_id"] = json!(batch_id); + } + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::TransferSend { wallet_name, statechain_id, to_address, force_send, batch_id } => { + mercuryrustlib::coin_status::update_coins(&client_config, &wallet_name).await?; + + let force_send = force_send.unwrap_or(false); + + mercuryrustlib::transfer_sender::execute(&client_config, &to_address, &wallet_name, &statechain_id, force_send, batch_id).await?; + + let obj = json!({"Transfer": "sent"}); + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::TransferReceive { wallet_name } => { + mercuryrustlib::coin_status::update_coins(&client_config, &wallet_name).await?; + + let mut received_statechain_ids = Vec::::new(); + + loop { + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet_name).await?; + received_statechain_ids.extend(transfer_receive_result.received_statechain_ids); + + if transfer_receive_result.is_there_batch_locked { + println!("Statecoin batch still locked. Waiting until expiration or unlock."); + thread::sleep(Duration::from_secs(5)); + } else { + break; + } + } + + let obj = json!(received_statechain_ids); + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::PaymentHash { wallet_name, statechain_id} => { + let response = mercuryrustlib::lightning_latch::create_pre_image(&client_config, &wallet_name, &statechain_id).await?; + + let obj = json!(response); + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + }, + Commands::ConfirmPendingInvoice { wallet_name, statechain_id } => { + mercuryrustlib::lightning_latch::confirm_pending_invoice(&client_config, &wallet_name, &statechain_id).await?; + }, + Commands::RetrievePreImage { wallet_name, statechain_id, batch_id } => { + + let pre_image = mercuryrustlib::lightning_latch::retrieve_pre_image(&client_config, &wallet_name, &statechain_id, &batch_id).await?; + + let obj = json!({"pre_image": pre_image}); + + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); + } + } + + client_config.pool.close().await; + + Ok(()) +} diff --git a/clients/libs/nodejs/.gitignore b/clients/libs/nodejs/.gitignore new file mode 100644 index 00000000..faae5bbe --- /dev/null +++ b/clients/libs/nodejs/.gitignore @@ -0,0 +1,133 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +wallet.db* +wallet.json \ No newline at end of file diff --git a/clients/libs/nodejs/broadcast_backup_tx.js b/clients/libs/nodejs/broadcast_backup_tx.js new file mode 100644 index 00000000..619dfe98 --- /dev/null +++ b/clients/libs/nodejs/broadcast_backup_tx.js @@ -0,0 +1,80 @@ +const mercury_wasm = require('mercury-wasm'); + +const sqlite_manager = require('./sqlite_manager'); + +const utils = require('./utils'); + +const { CoinStatus } = require('./coin_enum'); + +const execute = async (clientConfig, electrumClient, db, walletName, statechainId, toAddress, feeRate) => { + + let wallet = await sqlite_manager.getWallet(db, walletName); + + if (!feeRate) { + let feeRateSatsPerByte = await electrumClient.request('blockchain.estimatefee', [3]); + + if (feeRateSatsPerByte <= 0) { + feeRateSatsPerByte = 0.00001; + } + feeRateSatsPerByte = (feeRateSatsPerByte * 100000.0); + + feeRate = (feeRateSatsPerByte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: feeRateSatsPerByte; + } else { + feeRate = parseFloat(feeRate); + } + + let backupTxs = await sqlite_manager.getBackupTxs(db, statechainId); + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + const blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + const currentBlockheight = blockHeader.height; + + if (currentBlockheight <= coin.locktime) { + throw new Error(`The coin is not expired yet. Coin locktime is ${coin.locktime} and current blockheight is ${currentBlockheight}`); + } + + const backupTx = mercury_wasm.latestBackuptxPaysToUserpubkey(backupTxs, coin, wallet.network); + + if (!backupTx) { + throw new Error(`There is no backup transaction for the statechain id ${statechainId}`); + } + + const CpfpTx = mercury_wasm.createCpfpTx(backupTx, coin, toAddress, feeRate, wallet.network); + + let backupTxTxid = await electrumClient.request('blockchain.transaction.broadcast', [backupTx.tx]); + // console.log(`Broadcasting backup transaction: ${backupTxTxid}`); + + let cpfpTxTxid = await electrumClient.request('blockchain.transaction.broadcast', [CpfpTx]); + // console.log(`Broadcasting CPFP transaction: ${cpfpTxTxid}`); + + coin.tx_cpfp = cpfpTxTxid; + coin.withdrawal_address = toAddress; + coin.status = CoinStatus.WITHDRAWING; + + await sqlite_manager.updateWallet(db, wallet); + + utils.completeWithdraw(clientConfig, coin.statechain_id, coin.signed_statechain_id); + + return { + backupTx: backupTxTxid, + cpfpTx: cpfpTxTxid + }; +} + +module.exports = { execute }; \ No newline at end of file diff --git a/clients/libs/nodejs/coin_enum.js b/clients/libs/nodejs/coin_enum.js new file mode 100644 index 00000000..6482dd14 --- /dev/null +++ b/clients/libs/nodejs/coin_enum.js @@ -0,0 +1,14 @@ + +const CoinStatus = { + INITIALISED: "INITIALISED", // address generated but no Tx0 yet + IN_MEMPOOL: "IN_MEMPOOL", // Tx0 in mempool + UNCONFIRMED: "UNCONFIRMED", // Tx0 confirmed but coin not available to be sent + CONFIRMED: "CONFIRMED", // Tx0 confirmed and coin available to be sent + IN_TRANSFER: "IN_TRANSFER", // transfer-sender performed, but receiver hasn't completed transfer-receiver + WITHDRAWING: "WITHDRAWING", // withdrawal tx signed and broadcast but not yet confirmed + TRANSFERRED: "TRANSFERRED", // the coin was transferred + WITHDRAWN: "WITHDRAWN", // the coin was withdrawn + DUPLICATED: "DUPLICATED", // the coin was withdrawn +}; + +module.exports = { CoinStatus }; \ No newline at end of file diff --git a/clients/libs/nodejs/coin_status.js b/clients/libs/nodejs/coin_status.js new file mode 100644 index 00000000..b5a81ed7 --- /dev/null +++ b/clients/libs/nodejs/coin_status.js @@ -0,0 +1,265 @@ + +const sqlite_manager = require('./sqlite_manager'); +const utils = require('./utils'); +const bitcoinjs = require("bitcoinjs-lib"); +const ecc = require("tiny-secp256k1"); +const deposit = require('./deposit'); +const { CoinStatus } = require('./coin_enum'); +const mercury_wasm = require('mercury-wasm'); + +const checkDeposit = async (clientConfig, electrumClient, coin, wallet_network) => { + + if (!coin.statechain_id && !coin.utxo_txid && !coin.utxo_vout) { + if (coin.status != CoinStatus.INITIALISED) { + throw new Error(`Coin does not have a statechain ID, a UTXO and the status is not INITIALISED`); + } else { + return null; + } + } + + bitcoinjs.initEccLib(ecc); + + const network = utils.getNetwork(wallet_network); + + let script = bitcoinjs.address.toOutputScript(coin.aggregated_address, network); + let hash = bitcoinjs.crypto.sha256(script); + let reversedHash = Buffer.from(hash.reverse()); + reversedHash = reversedHash.toString('hex'); + + let utxo = null; + let utxo_list = null; + try { + utxo_list = await electrumClient.request('blockchain.scripthash.listunspent', [reversedHash]); + } catch (error) { + throw new Error("Error getting unspent list from electrs server"); + } + + for (let unspent of utxo_list) { + if (unspent.value === coin.amount) { + utxo = unspent; + break; + } + } + + // No deposit found. No change in the coin status + if (!utxo) { + return null; + } + + // IN_MEMPOOL. there is nothing to do + if (utxo.height == 0 && coin.status == CoinStatus.IN_MEMPOOL) { + return null; + } + + let depositResult = null; + + if (coin.status == CoinStatus.INITIALISED) { + const utxo_txid = utxo.tx_hash; + const utxo_vout = utxo.tx_pos; + + const backup_tx = await deposit.createTx1(clientConfig, electrumClient, coin, wallet_network, utxo_txid, utxo_vout); + + const activity_utxo = `${utxo_txid}:${utxo_vout}`; + + const activity = utils.createActivity(activity_utxo, coin.amount, "Deposit"); + + depositResult = { + activity, + backup_tx + }; + } + + if (utxo.height > 0) { + + const block_header = await electrumClient.request('blockchain.headers.subscribe'); + const blockheight = block_header.height; + + const confirmations = blockheight - utxo.height + 1; + + const confirmationTarget = clientConfig.confirmationTarget; + + coin.status = CoinStatus.UNCONFIRMED; + + if (confirmations >= confirmationTarget) { + coin.status = CoinStatus.CONFIRMED; + } + } + + return depositResult; +} + +const checkTransfer = async (clientConfig, coin) => { + + if (!coin.statechain_id) { + throw new Error(`The coin with the aggregated address ${coin.aggregated_address} does not have a statechain ID`); + } + + let statechainInfo = await utils.getStatechainInfo(clientConfig, coin.statechain_id); + + // if the statechain info is not found, we assume the coin has been transferred + if (!statechainInfo) { + return true; + } + + let enclavePublicKey = statechainInfo.enclave_public_key; + + let isTransferred = !mercury_wasm.isEnclavePubkeyPartOfCoin(coin, enclavePublicKey); + + return isTransferred; +} + +const checkWithdrawal = async (clientConfig, electrumClient, coin, wallet_network) => { + + let txid = undefined; + + if (coin.tx_withdraw) { + txid = coin.tx_withdraw; + } + + if (coin.tx_cpfp) { + if (txid) { + throw new Error(`Coin ${coin.aggregated_address} has both tx_withdraw and tx_cpfp`); + } + txid = coin.tx_cpfp; + } + + if (!txid) { + throw new Error(`Coin ${coin.aggregated_address} has neither tx_withdraw nor tx_cpfp`); + } + + if (!coin.withdrawal_address) { + throw new Error(`Coin ${coin.aggregated_address} has no withdrawal_address`); + } + + bitcoinjs.initEccLib(ecc); + + const network = utils.getNetwork(wallet_network); + + let script = bitcoinjs.address.toOutputScript(coin.withdrawal_address, network); + let hash = bitcoinjs.crypto.sha256(script); + let reversedHash = Buffer.from(hash.reverse()); + reversedHash = reversedHash.toString('hex'); + + let utxo = undefined; + + let utxo_list = await electrumClient.request('blockchain.scripthash.listunspent', [reversedHash]); + + for (let unspent of utxo_list) { + if (unspent.tx_hash === txid) { + utxo = unspent; + break; + } + } + + if (!utxo) { + // sometimes the transaction has not yet been transmitted to the specified Electrum server + // throw new Error(`There is no UTXO with the address ${coin.withdrawal_address} and the txid ${txid}`); + return false; + } + + if (utxo.height > 0) { + const block_header = await electrumClient.request('blockchain.headers.subscribe'); + const blockheight = block_header.height; + + const confirmations = blockheight - utxo.height + 1; + + const confirmationTarget = clientConfig.confirmationTarget; + + return confirmations >= confirmationTarget; + } + + return false; +} + +const checkForDuplicated = async (electrumClient, existingCoins, walletNetwork) => { + + const duplicatedCoinList = []; + + for (const coin of existingCoins) { + + if (![CoinStatus.IN_MEMPOOL, CoinStatus.UNCONFIRMED, CoinStatus.CONFIRMED].includes(coin.status)) { + continue; + } + + const network = utils.getNetwork(walletNetwork); + + let script = bitcoinjs.address.toOutputScript(coin.aggregated_address, network); + let hash = bitcoinjs.crypto.sha256(script); + let reversedHash = Buffer.from(hash.reverse()); + reversedHash = reversedHash.toString('hex'); + + let utxoList = await electrumClient.request('blockchain.scripthash.listunspent', [reversedHash]); + + let maxDuplicatedIndex = Math.max( + ...existingCoins + .filter(c => c.statechain_id === coin.statechain_id) + .map(coin => coin.duplicate_index) + ); + + for (const unspent of utxoList) { + + const utxoExists = existingCoins.some(coin => + coin.utxo_txid === unspent.tx_hash && + coin.utxo_vout === unspent.tx_pos + ); + + if (utxoExists) { + continue; + } + + maxDuplicatedIndex++; + + const duplicatedCoin = { + ...coin, + status: CoinStatus.DUPLICATED, + utxo_txid: unspent.tx_hash, + utxo_vout: unspent.tx_pos, + amount: unspent.value, + duplicate_index: maxDuplicatedIndex + }; + + duplicatedCoinList.push(duplicatedCoin); + } + } + + return duplicatedCoinList; +} + +const updateCoins = async (clientConfig, electrumClient, db, wallet_name) => { + + let wallet = await sqlite_manager.getWallet(db, wallet_name); + + const network = wallet.network; + + for (let i = 0; i < wallet.coins.length; i++) { + let coin = wallet.coins[i]; + + if (coin.status == CoinStatus.INITIALISED || coin.status == CoinStatus.IN_MEMPOOL || coin.status == CoinStatus.UNCONFIRMED) { + let depositResult = await checkDeposit(clientConfig, electrumClient, coin, network); + + if (depositResult) { + wallet.activities.push(depositResult.activity); + await sqlite_manager.insertTransaction(db, coin.statechain_id, [depositResult.backup_tx]); + } + } else if (coin.status === CoinStatus.IN_TRANSFER) { + let is_transferred = await checkTransfer(clientConfig, coin); + + if (is_transferred) { + coin.status = CoinStatus.TRANSFERRED; + } + } else if (coin.status == CoinStatus.WITHDRAWING) { + let is_withdrawn = await checkWithdrawal(clientConfig, electrumClient, coin, network); + + if (is_withdrawn) { + coin.status = CoinStatus.WITHDRAWN; + } + } + } + + const duplicatedCoins = await checkForDuplicated(electrumClient, wallet.coins, wallet.network); + wallet.coins = [...wallet.coins, ...duplicatedCoins]; + + await sqlite_manager.updateWallet(db, wallet); +} + +module.exports = { updateCoins }; \ No newline at end of file diff --git a/clients/libs/nodejs/deposit.js b/clients/libs/nodejs/deposit.js new file mode 100644 index 00000000..64de8b07 --- /dev/null +++ b/clients/libs/nodejs/deposit.js @@ -0,0 +1,169 @@ +const axios = require('axios').default; +const { SocksProxyAgent } = require('socks-proxy-agent'); +const transaction = require('./transaction'); +const mercury_wasm = require('mercury-wasm'); +const sqlite_manager = require('./sqlite_manager'); +const { CoinStatus } = require('./coin_enum'); +const utils = require('./utils'); + +const getDepositBitcoinAddress = async (clientConfig, db, wallet_name, amount) => { + + let wallet = await sqlite_manager.getWallet(db, wallet_name); + + let foundToken = wallet.tokens.find(token => token.confirmed === true && token.spent === false); + + if (!foundToken) { + throw new Error(`There is no token available`); + } + + await init(clientConfig, db, wallet, foundToken.token_id); + + let coin = wallet.coins[wallet.coins.length - 1]; + + let aggregatedPublicKey = mercury_wasm.createAggregatedAddress(coin, wallet.network); + + coin.amount = parseInt(amount, 10); + coin.aggregated_address = aggregatedPublicKey.aggregate_address; + coin.aggregated_pubkey = aggregatedPublicKey.aggregate_pubkey; + + foundToken.spent = true; + + await sqlite_manager.updateWallet(db, wallet); + + return { "deposit_address": coin.aggregated_address, "statechain_id": coin.statechain_id }; +} + +const createTx1 = async (clientConfig, electrumClient, coin, wallet_network, tx0_hash, tx0_vout) => { + + if (coin.status !== CoinStatus.INITIALISED) { + throw new Error(`The coin with the aggregated address ${aggregated_address} is not in the INITIALISED state`); + } + + if ('utxo_txid' in coin && 'input_vout' in coin) { + throw new Error(`The coin with the aggregated address ${aggregated_address} has already been deposited`); + } + + coin.utxo_txid = tx0_hash; + coin.utxo_vout = tx0_vout; + coin.status = CoinStatus.IN_MEMPOOL; + + const toAddress = mercury_wasm.getUserBackupAddress(coin, wallet_network); + const isWithdrawal = false; + const qtBackupTx = 0; + + const serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + let feeRateSatsPerByte = (serverInfo.fee_rate_sats_per_byte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.fee_rate_sats_per_byte; + + let signed_tx = await transaction.new_transaction( + clientConfig, + electrumClient, + coin, + toAddress, + isWithdrawal, + qtBackupTx, + null, + wallet_network, + feeRateSatsPerByte, + serverInfo.initlock, + serverInfo.interval + ); + + let backup_tx = { + tx_n: 1, + tx: signed_tx, + client_public_nonce: coin.public_nonce, + server_public_nonce: coin.server_public_nonce, + client_public_key: coin.user_pubkey, + server_public_key: coin.server_pubkey, + blinding_factor: coin.blinding_factor + }; + + coin.locktime = mercury_wasm.getBlockheight(backup_tx); + + return backup_tx; +} + +const init = async (clientConfig, db, wallet, token_id) => { + + let coin = mercury_wasm.getNewCoin(wallet); + + wallet.coins.push(coin); + + await sqlite_manager.updateWallet(db, wallet); + + // token_id = crypto.randomUUID().replace('-',''); + + let depositMsg1 = mercury_wasm.createDepositMsg1(coin, token_id); + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "deposit/init/pod"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + const response = await axios.post(url, depositMsg1, socksAgent); + + if (response.status != 200) { + throw new Error(`Deposit error: ${response.data}`); + } + + let depositMsg1Response = response.data; + + let depositInitResult = mercury_wasm.handleDepositMsg1Response(coin, depositMsg1Response); + // console.log("depositInitResult:", depositInitResult); + + coin.statechain_id = depositInitResult.statechain_id; + coin.signed_statechain_id = depositInitResult.signed_statechain_id; + coin.server_pubkey = depositInitResult.server_pubkey; + + await sqlite_manager.updateWallet(db, wallet); +} + +const getTokenFromServer = async (clientConfig) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "tokens/token_init"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + const response = await axios.get(url, socksAgent); + + if (response.status != 200) { + throw new Error(`Token error: ${response.data}`); + } + + let token = response.data; + + return token; +} + +const getToken = async (clientConfig, db, walletName) => { + + let wallet = await sqlite_manager.getWallet(db, walletName); + + let token = await getTokenFromServer(clientConfig); + + // for dev purposes + token.confirmed = true; + + wallet.tokens.push(token); + + await sqlite_manager.updateWallet(db, wallet); + + return token; +} + +module.exports = { getDepositBitcoinAddress, createTx1, getToken }; \ No newline at end of file diff --git a/clients/libs/nodejs/index.js b/clients/libs/nodejs/index.js new file mode 100644 index 00000000..5cf3ade3 --- /dev/null +++ b/clients/libs/nodejs/index.js @@ -0,0 +1,264 @@ + +const ElectrumCli = require('@mempool/electrum-client'); + +const deposit = require('./deposit'); +const broadcast_backup_tx = require('./broadcast_backup_tx'); +const withdraw = require('./withdraw'); +const transfer_receive = require('./transfer_receive'); +const transfer_send = require('./transfer_send'); +const coin_status = require('./coin_status'); +const lightningLatch = require('./lightning-latch'); + +const sqlite3 = require('sqlite3').verbose(); + +const sqlite_manager = require('./sqlite_manager'); + +const { v4: uuidv4 } = require('uuid'); + +const wallet_manager = require('./wallet'); + +const getDatabase = async (clientConfig) => { + const databaseFile = clientConfig.databaseFile; + const db = new sqlite3.Database(databaseFile); + await sqlite_manager.createTables(db); + return db; +} + +const getElectrumClient = async (clientConfig) => { + + const urlElectrum = clientConfig.electrumServer; + const urlElectrumObject = new URL(urlElectrum); + + const electrumPort = parseInt(urlElectrumObject.port, 10); + const electrumHostname = urlElectrumObject.hostname; + const electrumProtocol = urlElectrumObject.protocol.slice(0, -1); + + const electrumClient = new ElectrumCli(electrumPort, electrumHostname, electrumProtocol); + await electrumClient.connect(); + + return electrumClient; +} + +const createWallet = async (clientConfig, walletName) => { + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + let wallet = await wallet_manager.createWallet(walletName, clientConfig, electrumClient); + + await sqlite_manager.insertWallet(db, wallet); + + electrumClient.close(); + db.close(); + + return wallet; +} + +const newToken = async (clientConfig, walletName) => { + const db = await getDatabase(clientConfig); + const token = await deposit.getToken(clientConfig, db, walletName); + db.close(); + return token; +} + +const getDepositBitcoinAddress = async (clientConfig, walletName, amount) => { + const db = await getDatabase(clientConfig); + const address_info = await deposit.getDepositBitcoinAddress(clientConfig, db, walletName, amount); + db.close(); + return address_info; +} + +const getWalletTokens = async (clientConfig, walletName) => { + const db = await getDatabase(clientConfig); + let wallet = await sqlite_manager.getWallet(db, walletName); + db.close(); + return wallet.tokens; +} + +const broadcastBackupTransaction = async (clientConfig, walletName, statechainId, toAddress, feeRate) => { + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + let txIds = await broadcast_backup_tx.execute(clientConfig, electrumClient, db, walletName, statechainId, toAddress, feeRate); + + electrumClient.close(); + db.close(); + + return txIds; +} + +const listStatecoins = async (clientConfig, walletName) => { + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + let wallet = await sqlite_manager.getWallet(db, walletName); + + let coins = wallet.coins.map(coin => ({ + statechain_id: coin.statechain_id, + amount: coin.amount, + status: coin.status, + adress: coin.address, + aggregated_address: coin.aggregated_address, + locktime: coin.locktime, + duplicate_index: coin.duplicate_index + })); + + electrumClient.close(); + db.close(); + + return coins; +} + +const withdrawCoin = async (clientConfig, walletName, statechainId, toAddress, feeRate, duplicatedIndex) => { + const db = await getDatabase(clientConfig); + const electrumClient = await getElectrumClient(clientConfig); + + try { + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + return await withdraw.execute( + clientConfig, + electrumClient, + db, + walletName, + statechainId, + toAddress, + feeRate, + duplicatedIndex + ); + } finally { + await Promise.all([ + electrumClient.close(), + db.close() + ]); + } +} + +const newTransferAddress = async (clientConfig, walletName, generateBatchId) => { + + const db = await getDatabase(clientConfig); + + const addr = await transfer_receive.newTransferAddress(db, walletName) + let res = {transfer_receive: addr}; + + if (generateBatchId) { + res.batchId = uuidv4(); + } + + db.close(); + + return res; +} + +const transferSend = async (clientConfig, walletName, statechainId, toAddress, forceSend, batchId) => { + const db = await getDatabase(clientConfig); + const electrumClient = await getElectrumClient(clientConfig); + + try { + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + return await transfer_send.execute( + clientConfig, + electrumClient, + db, + walletName, + statechainId, + toAddress, + forceSend, + batchId + ); + } finally { + await Promise.all([ + electrumClient.close(), + db.close() + ]); + } +} + +const transferReceive = async (clientConfig, walletName) => { + + const db = await getDatabase(clientConfig); + const electrumClient = await getElectrumClient(clientConfig); + + try { + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + return await transfer_receive.execute(clientConfig, electrumClient, db, walletName); + } finally { + await Promise.all([ + electrumClient.close(), + db.close() + ]); + } +} + +const paymentHash = async (clientConfig, walletName, statechainId) => { + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + let paymentHash = await lightningLatch.createPreImage(clientConfig, db, walletName, statechainId); + + electrumClient.close(); + db.close(); + + return paymentHash; +} + +const confirmPendingInvoice = async (clientConfig, walletName, statechainId) => { + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + await lightningLatch.confirmPendingInvoice(clientConfig, db, walletName, statechainId); + + electrumClient.close(); + db.close(); +} + +const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) => { + + const db = await getDatabase(clientConfig); + + const electrumClient = await getElectrumClient(clientConfig); + + await coin_status.updateCoins(clientConfig, electrumClient, db, walletName); + + let preImage = await lightningLatch.retrievePreImage(clientConfig, db, walletName, statechainId, batchId); + + electrumClient.close(); + db.close(); + + return preImage; +} + +module.exports = { + createWallet, + newToken, + getDepositBitcoinAddress, + getWalletTokens, + broadcastBackupTransaction, + listStatecoins, + withdrawCoin, + newTransferAddress, + transferSend, + transferReceive, + paymentHash, + confirmPendingInvoice, + retrievePreImage +}; diff --git a/clients/libs/nodejs/lightning-latch.js b/clients/libs/nodejs/lightning-latch.js new file mode 100644 index 00000000..d391268b --- /dev/null +++ b/clients/libs/nodejs/lightning-latch.js @@ -0,0 +1,138 @@ +const sqlite_manager = require('./sqlite_manager'); +const { v4: uuidv4 } = require('uuid'); +const axios = require('axios').default; +const { SocksProxyAgent } = require('socks-proxy-agent'); +const { CoinStatus } = require('./coin_enum'); + +const createPreImage = async (clientConfig, db, walletName, statechainId) => { + + const batchId = uuidv4(); + + let wallet = await sqlite_manager.getWallet(db, walletName); + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + if (coin.locktime == null) { + throw new Error("Coin.locktime is null"); + } + + let paymentHashPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + batch_id: batchId + }; + + let paymentHash = await sendPaymentHash(clientConfig, paymentHashPayload); + + return { + hash: paymentHash, + batchId: batchId + }; +} + +const sendPaymentHash = async (clientConfig, paymentHashPayload) => { + + const url = `${clientConfig.statechainEntity}/transfer/paymenthash`; + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response = await axios.post(url, paymentHashPayload, socksAgent); + + return response?.data?.hash; +} + +const confirmPendingInvoice = async (clientConfig, db, walletName, statechainId) => { + + let wallet = await sqlite_manager.getWallet(db, walletName); + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + const transferUnlockRequestPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + auth_pub_key: null + }; + + const url = `${clientConfig.statechainEntity}/transfer/unlock`; + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + // If there is an http error an exception will be thrown + await axios.post(url, transferUnlockRequestPayload, socksAgent); +} + +const retrievePreImage = async (clientConfig, db, walletName, statechainId, batchId) => { + + let wallet = await sqlite_manager.getWallet(db, walletName); + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + const transferPreimageRequestPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + previous_user_auth_key: coin.auth_pubkey, + batch_id: batchId, + }; + + const url = `${clientConfig.statechainEntity}/transfer/transfer_preimage`; + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response = await axios.post(url, transferPreimageRequestPayload, socksAgent); + + return { preimage: response?.data?.preimage }; +} + +module.exports = { createPreImage, confirmPendingInvoice, retrievePreImage }; diff --git a/clients/libs/nodejs/package-lock.json b/clients/libs/nodejs/package-lock.json new file mode 100644 index 00000000..dd6a915a --- /dev/null +++ b/clients/libs/nodejs/package-lock.json @@ -0,0 +1,1647 @@ +{ + "name": "mercurynodejslib", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mercurynodejslib", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@mempool/electrum-client": "^1.1.9", + "axios": "^1.5.1", + "bitcoinjs-lib": "^6.1.5", + "commander": "^11.1.0", + "config": "^3.3.9", + "mercury-wasm": "file:../../../wasm/node_pkg/debug", + "socks-proxy-agent": "^8.0.2", + "sqlite3": "^5.1.6", + "tiny-secp256k1": "^2.2.3", + "uuid": "^9.0.1" + } + }, + "../../../wasm/node_pkg/debug": { + "name": "mercury-wasm", + "version": "0.1.0" + }, + "../../wasm/node_pkg/debug": { + "extraneous": true + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, + "node_modules/@mempool/electrum-client": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz", + "integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "optional": true + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", + "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "optional": true + }, + "node_modules/config": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.11.tgz", + "integrity": "sha512-Dhn63ZoWCW5EMg4P0Sl/XNsj/7RLiUIA1x1npCy+m2cRwRHzLnt3UtYtxRDMZW/6oOMdWhCzaGYkOcajGgrAOA==", + "dependencies": { + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "optional": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "optional": true + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/mercury-wasm": { + "resolved": "../../../wasm/node_pkg/debug", + "link": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "optional": true + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", + "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/uint8array-tools": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/clients/libs/nodejs/package.json b/clients/libs/nodejs/package.json new file mode 100644 index 00000000..955545c7 --- /dev/null +++ b/clients/libs/nodejs/package.json @@ -0,0 +1,24 @@ +{ + "name": "mercurynodejslib", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@mempool/electrum-client": "^1.1.9", + "axios": "^1.5.1", + "bitcoinjs-lib": "^6.1.5", + "commander": "^11.1.0", + "config": "^3.3.9", + "mercury-wasm": "file:../../../wasm/node_pkg/debug", + "socks-proxy-agent": "^8.0.2", + "sqlite3": "^5.1.6", + "tiny-secp256k1": "^2.2.3", + "uuid": "^9.0.1" + } +} diff --git a/clients/libs/nodejs/sqlite_manager.js b/clients/libs/nodejs/sqlite_manager.js new file mode 100644 index 00000000..415ae9e6 --- /dev/null +++ b/clients/libs/nodejs/sqlite_manager.js @@ -0,0 +1,66 @@ +// Promisify the db.run method +const run = (db, sql, params) => { + return new Promise((resolve, reject) => { + db.run(sql, params, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +}; + +const createTables = async (db) => { + await run(db, "CREATE TABLE IF NOT EXISTS wallet (wallet_name TEXT NOT NULL UNIQUE, wallet_json TEXT NOT NULL)", []); + await run(db, "CREATE TABLE IF NOT EXISTS backup_txs (statechain_id TEXT NOT NULL, txs TEXT NOT NULL)", []); +} + +const insertWallet = async (db, wallet) => { + await run(db, "INSERT INTO wallet (wallet_name, wallet_json) VALUES (?, ?)", [ wallet.name, JSON.stringify(wallet) ]); +} + +const updateWallet = async (db, wallet) => { + await run(db, "UPDATE wallet SET wallet_json = ? WHERE wallet_name = ?", [ JSON.stringify(wallet), wallet.name ]); +} + +const getWallet = async (db, walletName) => { + return new Promise((resolve, reject) => { + db.get("SELECT wallet_json FROM wallet WHERE wallet_name = ?", [ walletName ], (err, row) => { + if (err) { + reject(err); + } else { + let wallet = JSON.parse(row.wallet_json); + resolve(wallet); + } + }); + }); +} + +const insertTransaction = async (db, statechain_id, txs) => { + await run(db, "INSERT INTO backup_txs (statechain_id, txs) VALUES (?, ?)", [ statechain_id, JSON.stringify(txs) ]); +} + +const updateTransaction = async (db, statechain_id, txs) => { + await run(db, "UPDATE backup_txs SET txs = ? WHERE statechain_id = ?", [ JSON.stringify(txs), statechain_id ]); +} + +const getBackupTxs = async (db, statechainId) => { + return new Promise((resolve, reject) => { + db.get("SELECT txs FROM backup_txs WHERE statechain_id = ?", [ statechainId ], (err, row) => { + if (err) { + reject(err); + } else { + let backupTxs = JSON.parse(row.txs); + resolve(backupTxs); + } + }); + }); +} + +const insertOrUpdateBackupTxs = async (db, statechain_id, txs) => { + await run(db, "DELETE FROM backup_txs WHERE statechain_id = ?", [ statechain_id]); + await run(db, "INSERT INTO backup_txs (statechain_id, txs) VALUES (?, ?)", [ statechain_id, JSON.stringify(txs) ]); +} + +module.exports = { createTables, insertWallet, updateWallet, getWallet, insertTransaction, updateTransaction, getBackupTxs, insertOrUpdateBackupTxs }; \ No newline at end of file diff --git a/clients/libs/nodejs/transaction.js b/clients/libs/nodejs/transaction.js new file mode 100644 index 00000000..62743690 --- /dev/null +++ b/clients/libs/nodejs/transaction.js @@ -0,0 +1,128 @@ +const axios = require('axios').default; +const { SocksProxyAgent } = require('socks-proxy-agent'); +const mercury_wasm = require('mercury-wasm'); + +const new_transaction = async(clientConfig, electrumClient, coin, toAddress, isWithdrawal, qtBackupTx, block_height, network, feeRateSatsPerByte, initlock, interval) => { + let coin_nonce = mercury_wasm.createAndCommitNonces(coin); + + let server_pubnonce = await signFirst(clientConfig, coin_nonce.sign_first_request_payload); + + coin.secret_nonce = coin_nonce.secret_nonce; + coin.public_nonce = coin_nonce.public_nonce; + coin.server_public_nonce = server_pubnonce; + coin.blinding_factor = coin_nonce.blinding_factor; + + let new_block_height = 0; + if (block_height == null) { + const block_header = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + new_block_height = block_header.height; + } else { + new_block_height = block_height; + } + + let partialSigRequest = mercury_wasm.getPartialSigRequest( + coin, + new_block_height, + initlock, + interval, + feeRateSatsPerByte, + qtBackupTx, + toAddress, + network, + isWithdrawal); + + const serverPartialSigRequest = partialSigRequest.partial_signature_request_payload; + + const serverPartialSig = await signSecond(clientConfig, serverPartialSigRequest); + + const clientPartialSig = partialSigRequest.client_partial_sig; + const msg = partialSigRequest.msg; + const session = partialSigRequest.encoded_session; + const outputPubkey = partialSigRequest.output_pubkey; + + const signature = mercury_wasm.createSignature(msg, clientPartialSig, serverPartialSig, session, outputPubkey); + + const encodedUnsignedTx = partialSigRequest.encoded_unsigned_tx; + + const signed_tx = mercury_wasm.newBackupTransaction(encodedUnsignedTx, signature); + + return signed_tx; +} + +const signFirst = async (clientConfig, signFirstRequestPayload) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "sign/first"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response = await axios.post(url, signFirstRequestPayload, socksAgent); + /*try { + response = await axios.post(url, signFirstRequestPayload, socksAgent); + } catch (error) { + if (error.code === 'ECONNREFUSED') { + console.error('Error: Connection refused. The server at 0.0.0.0:8000 is not available.'); + } else { + console.error('An error occurred:', error.message); + } + } + + // Check if response or response.data is undefined + if (!response || !response.data || !response.data.server_pubnonce) { + throw new Error('Server public nonce is not available.'); + }*/ + let server_pubnonce_hex = response.data.server_pubnonce; + + if (server_pubnonce_hex.startsWith("0x")) { + server_pubnonce_hex = server_pubnonce_hex.substring(2); + } + + return server_pubnonce_hex; +} + +const signSecond = async (clientConfig, partialSigRequest) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "sign/second"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response; + try { + response = await axios.post(url, partialSigRequest, socksAgent); + } catch (error) { + if (error.code === 'ECONNREFUSED') { + console.error('Error: Connection refused. The server at 0.0.0.0:8000 is not available.'); + } else { + console.error('An error occurred:', error.message); + } + } + + // Check if response or response data is undefined + if (!response || !response.data || !response.data.partial_sig) { + throw new Error('Server partial signature is not available.'); + } + let server_partial_sig_hex = response.data.partial_sig; + + if (server_partial_sig_hex.startsWith("0x")) { + server_partial_sig_hex = server_partial_sig_hex.substring(2); + } + + return server_partial_sig_hex; +} + +module.exports = { signFirst, signSecond, new_transaction }; \ No newline at end of file diff --git a/clients/libs/nodejs/transfer_receive.js b/clients/libs/nodejs/transfer_receive.js new file mode 100644 index 00000000..0dc2bd69 --- /dev/null +++ b/clients/libs/nodejs/transfer_receive.js @@ -0,0 +1,375 @@ +const sqlite_manager = require('./sqlite_manager'); +const mercury_wasm = require('mercury-wasm'); +const axios = require('axios').default; +const { SocksProxyAgent } = require('socks-proxy-agent'); +const bitcoinjs = require("bitcoinjs-lib"); +const ecc = require("tiny-secp256k1"); +const utils = require('./utils'); +const { CoinStatus } = require('./coin_enum'); + +const newTransferAddress = async (db, wallet_name) => { + + let wallet = await sqlite_manager.getWallet(db, wallet_name); + + let coin = mercury_wasm.getNewCoin(wallet); + + wallet.coins.push(coin); + + await sqlite_manager.updateWallet(db, wallet); + + return coin.address; +} + +const execute = async (clientConfig, electrumClient, db, wallet_name) => { + + let wallet = await sqlite_manager.getWallet(db, wallet_name); + + const serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + let blockHeader = undefined; + try { + blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + } catch (error) { + throw new Error("Error getting block height from electrs server"); + } + const currentBlockheight = blockHeader.height; + + let uniqueAuthPubkeys = new Set(); + + wallet.coins.forEach(coin => { + uniqueAuthPubkeys.add(coin.auth_pubkey); + }); + + let encMsgsPerAuthPubkey = new Map(); + + for (let authPubkey of uniqueAuthPubkeys) { + try { + let encMessages = await getMsgAddr(clientConfig, authPubkey); + if (encMessages.length === 0) { + // console.log("No messages"); + continue; + } + + encMsgsPerAuthPubkey.set(authPubkey, encMessages); + } catch (err) { + // console.error(err); + } + } + + let isThereBatchLocked = false; + let receivedStatechainIds = []; + + let tempCoins = [...wallet.coins]; + let tempActivities = [...wallet.activities]; + + for (let [authPubkey, encMessages] of encMsgsPerAuthPubkey.entries()) { + + for (let encMessage of encMessages) { + + let coin = tempCoins.find(coin => coin.auth_pubkey === authPubkey && coin.status === 'INITIALISED'); + + if (coin) { + try { + let messageResult = await processEncryptedMessage(clientConfig, electrumClient, db, coin, encMessage, wallet.network, serverInfo, tempActivities); + + if (messageResult.isBatchLocked) { + isThereBatchLocked = true; + } + + if (messageResult.statechainId) { + receivedStatechainIds.push(messageResult.statechainId); + } + } catch (error) { + console.error(`Error: ${error.message}`); + continue; + } + + } else { + try { + let newCoin = await mercury_wasm.duplicateCoinToInitializedState(wallet, authPubkey); + + if (newCoin) { + let messageResult = await processEncryptedMessage(clientConfig, electrumClient, db, newCoin, encMessage, wallet.network, serverInfo, tempActivities); + + if (messageResult.isBatchLocked) { + isThereBatchLocked = true; + } + + if (messageResult.statechainId) { + tempCoins.push(newCoin); + receivedStatechainIds.push(messageResult.statechainId); + } + } + } catch (error) { + console.error(`Error: ${error.message}`); + continue; + } + } + + if (currentBlockheight >= coin.locktime) { + console.error(`The coin is expired. Coin locktime is ${coin.locktime} and current blockheight is ${currentBlockheight}`); + } + } + } + + wallet.coins = [...tempCoins]; + wallet.activities = [...tempActivities]; + + await sqlite_manager.updateWallet(db, wallet); + + return { + isThereBatchLocked, + receivedStatechainIds + }; +} + +const getMsgAddr = async (clientConfig, auth_pubkey) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/get_msg_addr/"; + const url = statechain_entity_url + '/' + path + auth_pubkey; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response; + try { + response = await axios.get(url, socksAgent); + } catch (error) { + throw new Error('Failed to get message address from mercury server'); + } + + return response.data.list_enc_transfer_msg; +} + +const processEncryptedMessage = async (clientConfig, electrumClient, db, coin, encMessage, network, serverInfo, activities) => { + let clientAuthKey = coin.auth_privkey; + let newUserPubkey = coin.user_pubkey; + + let transferMsg = mercury_wasm.decryptTransferMsg(encMessage, clientAuthKey); + + let tx0Outpoint = mercury_wasm.getTx0Outpoint(transferMsg.backup_transactions); + + const tx0Hex = await getTx0(electrumClient, tx0Outpoint.txid); + + const isTransferSignatureValid = mercury_wasm.verifyTransferSignature(newUserPubkey, tx0Outpoint, transferMsg); + + if (!isTransferSignatureValid) { + throw new Error("Invalid transfer signature"); + } + + const statechainInfo = await utils.getStatechainInfo(clientConfig, transferMsg.statechain_id); + + if (statechainInfo == null) { + throw new Error("Statechain info not found"); + } + + const isTx0OutputPubkeyValid = mercury_wasm.validateTx0OutputPubkey(statechainInfo.enclave_public_key, transferMsg, tx0Outpoint, tx0Hex, network); + + if (!isTx0OutputPubkeyValid) { + throw new Error("Invalid tx0 output pubkey"); + } + + let latestBackupTxPaysToUserPubkey = mercury_wasm.verifyLatestBackupTxPaysToUserPubkey(transferMsg, newUserPubkey, network); + + if (!latestBackupTxPaysToUserPubkey) { + throw new Error("Latest Backup Tx does not pay to the expected public key"); + } + + if (statechainInfo.num_sigs != transferMsg.backup_transactions.length) { + throw new Error("num_sigs is not correct"); + } + + let isTx0OutputUnspent = await verifyTx0OutputIsUnspentAndConfirmed(clientConfig, electrumClient, tx0Outpoint, tx0Hex, network); + if (!isTx0OutputUnspent.result) { + throw new Error("tx0 output is spent or not confirmed"); + } + + let currentFeeRateSatsPerByte = (serverInfo.fee_rate_sats_per_byte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.fee_rate_sats_per_byte; + + const feeRateTolerance = clientConfig.feeRateTolerance; + + let isSignatureValid = mercury_wasm.validateSignatureScheme( + transferMsg, + statechainInfo, + tx0Hex, + feeRateTolerance, + currentFeeRateSatsPerByte, + serverInfo.interval + ) + + if (!isSignatureValid.result) { + throw new Error(`Invalid signature scheme, ${isSignatureValid.msg}`); + } + + let previousLockTime = isSignatureValid.previousLockTime; + + const transferReceiverRequestPayload = mercury_wasm.createTransferReceiverRequestPayload(statechainInfo, transferMsg, coin); + + let signedStatechainIdForUnlock = mercury_wasm.signMessage(transferMsg.statechain_id, coin); + + await unlockStatecoin(clientConfig, transferMsg.statechain_id, signedStatechainIdForUnlock, coin.auth_pubkey); + + let serverPublicKeyHex = ""; + + try { + const transferReceiverResult = await sendTransferReceiverRequestPayload(clientConfig, transferReceiverRequestPayload); + + if (transferReceiverResult.isBatchLocked) { + return { + isBatchLocked: true, + statechainId: null, + }; + } + + serverPublicKeyHex = transferReceiverResult.serverPubkey; + } catch (error) { + throw new Error(error); + } + + let newKeyInfo = mercury_wasm.getNewKeyInfo(serverPublicKeyHex, coin, transferMsg.statechain_id, tx0Outpoint, tx0Hex, network); + + coin.server_pubkey = serverPublicKeyHex; + coin.aggregated_pubkey = newKeyInfo.aggregate_pubkey; + coin.aggregated_address = newKeyInfo.aggregate_address; + coin.statechain_id = transferMsg.statechain_id; + coin.signed_statechain_id = newKeyInfo.signed_statechain_id; + coin.amount = newKeyInfo.amount; + coin.utxo_txid = tx0Outpoint.txid; + coin.utxo_vout = tx0Outpoint.vout; + coin.locktime = previousLockTime; + coin.status = isTx0OutputUnspent.status; + + let utxo = `${tx0Outpoint.txid}:${tx0Outpoint.vout}`; + + let activity = { + utxo: utxo, + amount: newKeyInfo.amount, + action: "Receive", + date: new Date().toISOString() + }; + + activities.push(activity); + + await sqlite_manager.insertOrUpdateBackupTxs(db, transferMsg.statechain_id, transferMsg.backup_transactions); + + return { + isBatchLocked: false, + statechainId: transferMsg.statechain_id, + }; +} + +const getTx0 = async (electrumClient, tx0_txid) => { + return await electrumClient.request('blockchain.transaction.get', [tx0_txid]); // request(promise) +} + +const verifyTx0OutputIsUnspentAndConfirmed = async (clientConfig, electrumClient, tx0Outpoint, tx0Hex, wallet_network) => { + + let tx0outputAddress = mercury_wasm.getOutputAddressFromTx0(tx0Outpoint, tx0Hex, wallet_network); + + const network = utils.getNetwork(wallet_network); + + bitcoinjs.initEccLib(ecc); + + let script = bitcoinjs.address.toOutputScript(tx0outputAddress, network); + let hash = bitcoinjs.crypto.sha256(script); + let reversedHash = Buffer.from(hash.reverse()); + reversedHash = reversedHash.toString('hex'); + + let utxo_list = await electrumClient.request('blockchain.scripthash.listunspent', [reversedHash]); + + let status = CoinStatus.UNCONFIRMED; + + for (let unspent of utxo_list) { + if (unspent.tx_hash === tx0Outpoint.txid && unspent.tx_pos === tx0Outpoint.vout) { + + const block_header = await electrumClient.request('blockchain.headers.subscribe'); + const blockheight = block_header.height; + + const confirmations = blockheight - unspent.height + 1; + + const confirmationTarget = clientConfig.confirmationTarget; + + if (confirmations >= confirmationTarget) { + status = CoinStatus.CONFIRMED; + } + + return { result: true, status }; + } + } + + return { result: false, status }; +} + +const unlockStatecoin = async (clientConfig, statechainId, signedStatechainId, authPubkey) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/unlock"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let transferUnlockRequestPayload = { + statechain_id: statechainId, + auth_sig: signedStatechainId, + auth_pub_key: authPubkey, + }; + + const response = await axios.post(url, transferUnlockRequestPayload, socksAgent); + + if (response.status != 200) { + throw new Error(`Failed to unlock transfer message`); + } +} + +const sendTransferReceiverRequestPayload = async (clientConfig, transferReceiverRequestPayload) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/receiver"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + try { + const response = await axios.post(url, transferReceiverRequestPayload, socksAgent); + return { + isBatchLocked: false, + serverPubkey: response.data.server_pubkey, + }; + } + catch (error) { + + if (error.response.status == 400) { + if (error.response.data.code == 'ExpiredBatchTimeError') { + throw new Error(`Failed to update transfer message ${error.response.data.message}`); + } else if (error.response.data.code == 'StatecoinBatchLockedError') { + return { + isBatchLocked: true, + serverPubkey: null, + }; + } + } else { + throw new Error(`Failed to update transfer message ${JSON.stringify(error.response.data)}`); + } + } + +} + +module.exports = { newTransferAddress, execute }; diff --git a/clients/libs/nodejs/transfer_send.js b/clients/libs/nodejs/transfer_send.js new file mode 100644 index 00000000..4c0e7750 --- /dev/null +++ b/clients/libs/nodejs/transfer_send.js @@ -0,0 +1,199 @@ +const sqlite_manager = require('./sqlite_manager'); +const mercury_wasm = require('mercury-wasm'); +const transaction = require('./transaction'); +const axios = require('axios').default; +const { SocksProxyAgent } = require('socks-proxy-agent'); +const { CoinStatus } = require('./coin_enum'); +const utils = require('./utils'); + +const execute = async (clientConfig, electrumClient, db, walletName, statechainId, toAddress, forceSend, batchId) => { + + let wallet = await sqlite_manager.getWallet(db, walletName); + + const backupTxs = await sqlite_manager.getBackupTxs(db, statechainId); + + if (backupTxs.length === 0) { + throw new Error(`There is no backup transaction for the statechain id ${statechainId}`); + } + + const new_tx_n = backupTxs.length + 1; + + const isCoinDuplicated = wallet.coins.some(c => + c.statechain_id === statechainId && + c.status === CoinStatus.DUPLICATED + ); + + const areThereDuplicateCoinsWithdrawn = wallet.coins.some(c => + c.statechain_id === statechainId && + (c.status === CoinStatus.WITHDRAWING || c.status === CoinStatus.WITHDRAWN) && + c.duplicate_index > 0 + ); + + if (areThereDuplicateCoinsWithdrawn) { + throw new Error("There have been withdrawals of other coins with this same statechain_id (possibly duplicates). " + + "This transfer cannot be performed because the recipient would reject it due to the difference in signature count. " + + "This coin can be withdrawn, however." + ); + } + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId && c.status != CoinStatus.DUPLICATED + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + if (isCoinDuplicated && !forceSend) { + throw new Error("Coin is duplicated. If you want to proceed, use the command '--force, -f' option. " + + "You will no longer be able to move other duplicate coins with the same statechain_id and this will cause PERMANENT LOSS of these duplicate coin funds."); + } + + if (coin.locktime == null) { + throw new Error("Coin.locktime is null"); + } + + const blockHeader = await electrumClient.request('blockchain.headers.subscribe'); // request(promise) + const currentBlockheight = blockHeader.height; + + const serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + if (currentBlockheight + serverInfo.interval >= coin.locktime) { + throw new Error(`The coin is expired. Coin locktime is ${coin.locktime} and current blockheight is ${currentBlockheight}`); + } + + const statechain_id = coin.statechain_id; + const signed_statechain_id = coin.signed_statechain_id; + + const isWithdrawal = false; + const qtBackupTx = backupTxs.length; + + backupTxs.sort((a, b) => a.tx_n - b.tx_n); + + const bkp_tx1 = backupTxs[0]; + + const block_height = mercury_wasm.getBlockheight(bkp_tx1); + + const decodedTransferAddress = mercury_wasm.decodeTransferAddress(toAddress); + const new_auth_pubkey = decodedTransferAddress.auth_pubkey; + + const new_x1 = await get_new_x1(clientConfig, statechain_id, signed_statechain_id, new_auth_pubkey, batchId); + + let feeRateSatsPerByte = (serverInfo.fee_rate_sats_per_byte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.fee_rate_sats_per_byte; + + const signed_tx = await transaction.new_transaction( + clientConfig, + electrumClient, + coin, + toAddress, + isWithdrawal, + qtBackupTx, + block_height, + wallet.network, + feeRateSatsPerByte, + serverInfo.initlock, + serverInfo.interval + ); + + const backup_tx = { + tx_n: new_tx_n, + tx: signed_tx, + client_public_nonce: coin.public_nonce, + server_public_nonce: coin.server_public_nonce, + client_public_key: coin.user_pubkey, + server_public_key: coin.server_pubkey, + blinding_factor: coin.blinding_factor + }; + + backupTxs.push(backup_tx); + + const input_txid = coin.utxo_txid; + const input_vout = coin.utxo_vout; + const client_seckey = coin.user_privkey; + const recipient_address = toAddress; + + const transfer_signature = mercury_wasm.createTransferSignature(recipient_address, input_txid, input_vout, client_seckey); + + const transferUpdateMsgRequestPayload = mercury_wasm.createTransferUpdateMsg(new_x1, recipient_address, coin, transfer_signature, backupTxs); + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/update_msg"; + const url = statechain_entity_url + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + const response = await axios.post(url, transferUpdateMsgRequestPayload, socksAgent); + + if (!response.data.updated) { + throw new Error(`Transfer update failed`); + } + + await sqlite_manager.updateTransaction(db, coin.statechain_id, backupTxs); + + let utxo = `${coin.utxo_txid}:${coin.input_vout}`; + + let activity = { + utxo: utxo, + amount: coin.amount, + action: "Transfer", + date: new Date().toISOString() + }; + + wallet.activities.push(activity); + coin.status = CoinStatus.IN_TRANSFER; + + await sqlite_manager.updateWallet(db, wallet); + + return coin; +} + +const get_new_x1 = async (clientConfig, statechain_id, signed_statechain_id, new_auth_pubkey, batchId) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/sender"; + const url = statechain_entity_url + '/' + path; + + let transferSenderRequestPayload = { + statechain_id: statechain_id, + auth_sig: signed_statechain_id, + new_user_auth_key: new_auth_pubkey, + batch_id: batchId, + }; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response; + try { + response = await axios.post(url, transferSenderRequestPayload, socksAgent); + } catch (error) { + if (error.code === 'ECONNREFUSED') { + console.error('Error: Connection refused. The server at 0.0.0.0:8000 is not available.'); + } else { + console.error('An error occurred:', error.message); + } + } + return response?.data?.x1; +} + +module.exports = { execute }; \ No newline at end of file diff --git a/clients/libs/nodejs/utils.js b/clients/libs/nodejs/utils.js new file mode 100644 index 00000000..e503c49b --- /dev/null +++ b/clients/libs/nodejs/utils.js @@ -0,0 +1,114 @@ +const axios = require('axios').default; +const { SocksProxyAgent } = require('socks-proxy-agent'); +const bitcoinjs_lib = require("bitcoinjs-lib"); + +const infoConfig = async (clientConfig, ecl) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "info/config"; + + let fee_rate_btc_per_kb; + try { + fee_rate_btc_per_kb = await ecl.request('blockchain.estimatefee', [3]); // request(promise) + } catch (error) { + throw new Error("Error getting fee rate from electrs server"); + } + + // Why does it happen? + if (fee_rate_btc_per_kb <= 0) { + fee_rate_btc_per_kb = 0.00001; + } + const fee_rate_sats_per_byte = (fee_rate_btc_per_kb * 100000.0); + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let response = await axios.get(statechain_entity_url + '/' + path, socksAgent); + return { + initlock: response.data.initlock, + interval: response.data.interval, + fee_rate_sats_per_byte, + }; +} + +const getNetwork = (wallet_network) => { + switch(wallet_network) { + case "signet": + return bitcoinjs_lib.networks.testnet; + case "testnet": + return bitcoinjs_lib.networks.testnet; + case "regtest": + return bitcoinjs_lib.networks.regtest; + case "bitcoin": + return bitcoinjs_lib.networks.bitcoin; + default: + throw new Error("Unknown network"); + } +} + +const createActivity = (utxo, amount, action) => { + + const activity = { + utxo, + amount, + action, + date: new Date().toISOString() + }; + + return activity; + +} + +const getStatechainInfo = async (clientConfig, statechainId) => { + + const statechainEntityUrl = clientConfig.statechainEntity; + const path = `info/statechain/${statechainId}`; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + try { + let response = await axios.get(statechainEntityUrl + '/' + path, socksAgent); + return response.data; + } catch (error) { + if (error.response.status == 404) { + return null; + } else { + throw error; + } + } +} + +const completeWithdraw = async (clientConfig, statechainId, signedStatechainId) => { + + const statechainEntityUrl = clientConfig.statechainEntity; + const path = "withdraw/complete"; + const url = statechainEntityUrl + '/' + path; + + const torProxy = clientConfig.torProxy; + + let socksAgent = undefined; + + if (torProxy) { + socksAgent = { httpAgent: new SocksProxyAgent(torProxy) }; + } + + let deleteStatechainPayload = { + statechain_id: statechainId, + signed_statechain_id: signedStatechainId + }; + + await axios.post(url, deleteStatechainPayload, socksAgent); +} + +module.exports = { infoConfig, getNetwork, createActivity, getStatechainInfo, completeWithdraw }; diff --git a/clients/libs/nodejs/wallet.js b/clients/libs/nodejs/wallet.js new file mode 100644 index 00000000..9362e326 --- /dev/null +++ b/clients/libs/nodejs/wallet.js @@ -0,0 +1,62 @@ +const utils = require('./utils'); + +const mercury_wasm = require('mercury-wasm'); + +const createWallet = async (name, clientConfig, electrumClient) => { + + const urlElectrum = clientConfig.electrumServer; + const urlElectrumObject = new URL(urlElectrum); + + const electrumPort = urlElectrumObject.port; + const electrumHost = urlElectrumObject.hostname; + const electrumProtocol = urlElectrumObject.protocol.slice(0, -1); + + const electrumEndpoint = urlElectrum; + const statechainEntityEndpoint = clientConfig.statechainEntity; + const network = clientConfig.network; + const electrumType = clientConfig.electrumType; + + let block_header = await electrumClient.request('blockchain.headers.subscribe'); + let blockheight = block_header.height; + + let serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + let mnemonic = mercury_wasm.generateMnemonic(); + + let settings = { + network, + block_explorerURL: null, + torProxyHost: null, + torProxyPort: null, + torProxyControlPassword: null, + torProxyControlPort: null, + statechainEntityApi: statechainEntityEndpoint, + torStatechainEntityApi: null, + electrumProtocol, + electrumHost, + electrumPort, + electrumType, + notifications: false, + tutorials: false + } + + let wallet = { + name, + mnemonic, + version: "0.1.0", + state_entity_endpoint: statechainEntityEndpoint, + electrum_endpoint: electrumEndpoint, + network: network, + blockheight, + initlock: serverInfo.initlock, + interval: serverInfo.interval, + tokens: [], + activities: [], + coins: [], + settings + }; + + return wallet; +} + +module.exports = { createWallet }; diff --git a/clients/libs/nodejs/withdraw.js b/clients/libs/nodejs/withdraw.js new file mode 100644 index 00000000..0d3a476f --- /dev/null +++ b/clients/libs/nodejs/withdraw.js @@ -0,0 +1,93 @@ +const sqlite_manager = require('./sqlite_manager'); +const utils = require('./utils'); +const transaction = require('./transaction'); +const { CoinStatus } = require('./coin_enum'); + +const execute = async (clientConfig, electrumClient, db, walletName, statechainId, toAddress, feeRate, duplicatedIndex) => { + let wallet = await sqlite_manager.getWallet(db, walletName); + + const backupTxs = await sqlite_manager.getBackupTxs(db, statechainId); + + if (backupTxs.length === 0) { + throw new Error(`There is no backup transaction for the statechain id ${statechainId}`); + } + + const serverInfo = await utils.infoConfig(clientConfig, electrumClient); + + if (!feeRate) { + feeRate = (serverInfo.fee_rate_sats_per_byte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.fee_rate_sats_per_byte; + } else { + feeRate = parseFloat(feeRate); + } + + let coin; + + if (!!duplicatedIndex) { + coin = wallet.coins + .filter(c => c.statechain_id === statechainId && c.status === CoinStatus.DUPLICATED && c.duplicate_index === duplicatedIndex) + .reduce((min, current) => (current.locktime < min.locktime) ? current : min, { locktime: Infinity }); + } else { + coin = wallet.coins + .filter(c => c.statechain_id === statechainId && c.status !== CoinStatus.DUPLICATED) + .reduce((min, current) => (current.locktime < min.locktime) ? current : min, { locktime: Infinity }); + } + + if (!coin || coin.locktime === Infinity) { + if (duplicatedIndex !== undefined) { + throw new Error(`No duplicated coins associated with this statechain ID and index ${duplicatedIndex} were found`); + } else { + throw new Error("No coins associated with this statechain ID were found"); + } + } + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER && coin.status != CoinStatus.DUPLICATED) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER or DUPLICATED to withdraw it. The current status is ${coin.status}`); + } + + const isWithdrawal = true; + const qtBackupTx = backupTxs.length; + + let signed_tx = await transaction.new_transaction( + clientConfig, + electrumClient, + coin, + toAddress, + isWithdrawal, + qtBackupTx, + null, + wallet.network, + feeRate, + serverInfo.initlock, + serverInfo.interval + ); + + const txid = await electrumClient.request('blockchain.transaction.broadcast', [signed_tx]); + + coin.tx_withdraw = txid; + coin.withdrawal_address = toAddress; + coin.status = CoinStatus.WITHDRAWING; + + let activity = { + utxo: txid, + amount: coin.amount, + action: "Withdraw", + date: new Date().toISOString() + }; + + wallet.activities.push(activity); + + await sqlite_manager.updateWallet(db, wallet); + + const isThereMoreDuplicatedCoins = wallet.coins.some(coin => + (coin.status === CoinStatus.DUPLICATED || coin.status === CoinStatus.CONFIRMED) && + (duplicatedIndex === undefined || coin.duplicate_index !== duplicatedIndex) + ); + + if (!isThereMoreDuplicatedCoins) { + await utils.completeWithdraw(clientConfig, coin.statechain_id, coin.signed_statechain_id); + } + + return txid; +} + +module.exports = { execute }; \ No newline at end of file diff --git a/clients/libs/rust/Cargo.toml b/clients/libs/rust/Cargo.toml new file mode 100644 index 00000000..89b57b43 --- /dev/null +++ b/clients/libs/rust/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "mercuryrustlib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +bech32 = { version = "0.9.1", default-features = false } +bitcoin = { version = "0.30.1", features = ["serde", "base64", "rand-std", "std", "bitcoinconsensus"], default-features = false } +bip39 = "1.2.0" +clap = { version = "4.2.5", features = ["derive"]} +chrono = "0.4.31" +config = "0.13.1" +electrum-client = "0.18.0" +hex = "0.4.3" +rand = "0.8.5" +reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } +schemars = { version = "0.8.12", features = ["chrono", "uuid"] } +secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } +tokio = { version = "1.27.0", features = ["full"] } +uuid = { version = "1.3.1", features = ["v4", "serde"] } +mercurylib = { path = "../../../lib" } diff --git a/clients/libs/rust/migrations/0001_signer_data_table.sql b/clients/libs/rust/migrations/0001_signer_data_table.sql new file mode 100644 index 00000000..93511757 --- /dev/null +++ b/clients/libs/rust/migrations/0001_signer_data_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS wallet ( + wallet_name TEXT UNIQUE, + wallet_json BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS backup_txs ( + statechain_id TEXT NOT NULL, + txs TEXT NOT NULL +); diff --git a/clients/libs/rust/rust-toolchain.toml b/clients/libs/rust/rust-toolchain.toml new file mode 100644 index 00000000..624eb0ea --- /dev/null +++ b/clients/libs/rust/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.76.0" diff --git a/clients/libs/rust/src/broadcast_backup_tx.rs b/clients/libs/rust/src/broadcast_backup_tx.rs new file mode 100644 index 00000000..93f07a31 --- /dev/null +++ b/clients/libs/rust/src/broadcast_backup_tx.rs @@ -0,0 +1,78 @@ +use crate::{client_config::ClientConfig, sqlite_manager::{get_backup_txs, get_wallet, update_wallet}}; +use anyhow::{anyhow, Result}; +use electrum_client::ElectrumApi; +use mercurylib::wallet::{cpfp_tx, CoinStatus}; + +pub async fn execute(client_config: &ClientConfig, wallet_name: &str, statechain_id: &str, to_address: &str, fee_rate: Option) -> Result<()> { + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let is_address_valid = mercurylib::validate_address(to_address, &wallet.network)?; + + if !is_address_valid { + return Err(anyhow!("Invalid address")); + } + + let backup_txs = get_backup_txs(&client_config.pool, &statechain_id).await?; + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + let coin = wallet.coins + .iter_mut() + .filter(|tx| tx.statechain_id == Some(statechain_id.to_string())) // Filter coins with the specified statechain_id + .min_by_key(|tx| tx.locktime); // Find the one with the lowest locktime + + if coin.is_none() { + return Err(anyhow!("No coins associated with this statechain ID were found")); + } + + let coin = coin.unwrap(); + + if coin.status != CoinStatus::CONFIRMED && coin.status != CoinStatus::IN_TRANSFER { + return Err(anyhow::anyhow!("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is {}", coin.status)); + } + + let backup_tx = cpfp_tx::latest_backup_tx_pays_to_user_pubkey(&backup_txs, &coin, &wallet.network)?; + + let fee_rate = match fee_rate { + Some(fee_rate) => fee_rate, + None => { + let mut fee_rate_btc_per_kb = client_config.electrum_client.estimate_fee(1)?; + + // Why does it happen? + if fee_rate_btc_per_kb <= 0.0 { + fee_rate_btc_per_kb = 0.00001; + } + + let fee_rate_sats_per_byte = fee_rate_btc_per_kb * 100000.0; + + if fee_rate_sats_per_byte > client_config.max_fee_rate { + client_config.max_fee_rate + } else { + fee_rate_sats_per_byte + } + }, + }; + + let cpfp_tx = cpfp_tx::create_cpfp_tx(&backup_tx, &coin, to_address, fee_rate, &wallet.network)?; + + let tx_bytes = hex::decode(&backup_tx.tx)?; + let txid = client_config.electrum_client.transaction_broadcast_raw(&tx_bytes)?; + println!("Broadcasting backup transaction: {}", txid); + + let tx_bytes = hex::decode(&cpfp_tx)?; + let txid = client_config.electrum_client.transaction_broadcast_raw(&tx_bytes)?; + println!("Broadcasting CPFP transaction: {}", txid); + + coin.tx_cpfp = Some(txid.to_string()); + coin.withdrawal_address = Some(to_address.to_string()); + coin.status = CoinStatus::WITHDRAWING; + + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap().to_string(); + + update_wallet(&client_config.pool, &wallet).await?; + + crate::utils::complete_withdraw(statechain_id, &signed_statechain_id, &client_config).await?; + + Ok(()) +} \ No newline at end of file diff --git a/clients/libs/rust/src/client_config.rs b/clients/libs/rust/src/client_config.rs new file mode 100644 index 00000000..7975a086 --- /dev/null +++ b/clients/libs/rust/src/client_config.rs @@ -0,0 +1,126 @@ +use std::env; + +use bitcoin::Network; +use config::Config; +use sqlx::{Sqlite, migrate::MigrateDatabase, SqlitePool}; +use anyhow::Result; + +/// Config struct storing all StataChain Entity config +pub struct ClientConfig { + /// Active lockbox server addresses + pub statechain_entity: String, + /// Electrum client + pub electrum_client: electrum_client::Client, + /// Electrum server url + pub electrum_server_url: String, + /// Electrum server type (e.g. electrs, electrumx, etc.) + pub electrum_type: String, + /// Bitcoin network name (testnet, regtest, mainnet) + pub network: Network, + /// Fee rate tolerance + pub fee_rate_tolerance: f64, + /// Confirmation target + pub confirmation_target: u32, + /// Database connection pool + pub pool: sqlx::Pool, + /// Tor SOCKS5 proxy address + pub tor_proxy: Option, + /// Confirmation target + pub max_fee_rate: f64, +} + +fn check_and_set_settings() -> String { + if let Ok(value) = env::var("ML_NETWORK") { + if value == "regtest" { + return String::from("regtest.Settings"); + } + } + "Settings".to_string() +} + +impl ClientConfig { + pub async fn load() -> Self { + + let settings_filename = check_and_set_settings(); + + let settings = Config::builder() + .add_source(config::File::with_name(&settings_filename)) + .build() + .unwrap(); + + let statechain_entity = settings.get_string("statechain_entity").unwrap(); + let electrum_server = settings.get_string("electrum_server").unwrap(); + let electrum_type = settings.get_string("electrum_type").unwrap(); + let network = settings.get_string("network").unwrap(); + let fee_rate_tolerance = settings.get_int("fee_rate_tolerance").unwrap() as f64; + let database_file = settings.get_string("database_file").unwrap(); + let confirmation_target = settings.get_int("confirmation_target").unwrap() as u32; + let max_fee_rate = settings.get_int("max_fee_rate").unwrap() as f64; + + let tor_proxy = match settings.get_string("tor_proxy") { + Ok(proxy) => Some(proxy.to_string()), + Err(_) => None, + }; + // Open database connection pool + + if !Sqlite::database_exists(&database_file).await.unwrap_or(false) { + match Sqlite::create_database(&database_file).await { + Ok(_) => {}, + Err(error) => panic!("error: {}", error), + } + } + + let pool: sqlx::Pool = SqlitePool::connect(&database_file).await.unwrap(); + + sqlx::migrate!("./migrations") + .run(&pool) + .await + .unwrap(); + + // Convert network string to Network enum + + let network = match network.as_str() { + "signet" => Network::Signet, + "testnet" => Network::Testnet, + "regtest" => Network::Regtest, + "bitcoin" => Network::Bitcoin, + _ => panic!("Invalid network name"), + }; + + // Create Electrum client + + let electrum_client = electrum_client::Client::new(electrum_server.as_str()).unwrap(); + + ClientConfig { + statechain_entity, + electrum_client, + electrum_server_url: electrum_server, + electrum_type, + network, + fee_rate_tolerance, + confirmation_target, + pool, + tor_proxy, + max_fee_rate + } + } + + pub fn get_reqwest_client(&self) -> Result { + + match self.tor_proxy { + Some(ref proxy) => { + let proxy = reqwest::Proxy::all(proxy)?; + Ok(reqwest::Client::builder() + .proxy(proxy) + .build()?) + }, + None => Ok(reqwest::Client::new()), + + } + } +} + +pub async fn load() -> ClientConfig { + let client_config = ClientConfig::load().await; + client_config +} \ No newline at end of file diff --git a/clients/libs/rust/src/coin_status.rs b/clients/libs/rust/src/coin_status.rs new file mode 100644 index 00000000..664138a7 --- /dev/null +++ b/clients/libs/rust/src/coin_status.rs @@ -0,0 +1,263 @@ + +use crate::utils::create_activity; +use std::str::FromStr; + +use bitcoin::Address; +use electrum_client::{ElectrumApi, ListUnspentRes}; +use mercurylib::{utils::is_enclave_pubkey_part_of_coin, wallet::{Activity, BackupTx, Coin, CoinStatus}}; +use anyhow::{anyhow, Result, Ok}; + +use crate::{client_config::ClientConfig, sqlite_manager::{get_wallet, update_wallet, insert_backup_txs}, deposit::create_tx1}; + +struct DepositResult { + activity: Activity, + backup_tx: BackupTx, +} + +async fn check_deposit(client_config: &ClientConfig, coin: &mut Coin, wallet_netwotk: &str) -> Result> { + + if coin.statechain_id.is_none() && coin.utxo_txid.is_none() && coin.utxo_vout.is_none() { + if coin.status != CoinStatus::INITIALISED { + return Err(anyhow!("Coin does not have a statechain ID, a UTXO and the status is not INITIALISED")); + } else { + return Ok(None); + } + } + + let mut utxo: Option = None; + + let address = Address::from_str(&coin.aggregated_address.as_ref().unwrap())?.require_network(client_config.network)?; + + let utxo_list = client_config.electrum_client.script_list_unspent(&address.script_pubkey())?; + + for unspent in utxo_list { + if unspent.value == coin.amount.unwrap() as u64 { + utxo = Some(unspent); + break; + } + } + + // No deposit found. No change in the coin status + if utxo.is_none() { + return Ok(None); + // return Err(anyhow!("There is no UTXO with the address {} and the amount {}", coin.aggregated_address.as_ref().unwrap(), coin.amount.unwrap())); + } + + let utxo = utxo.unwrap(); + + // IN_MEMPOOL. there is nothing to do + if utxo.height == 0 && coin.status == CoinStatus::IN_MEMPOOL { + return Ok(None); + } + + let block_header = client_config.electrum_client.block_headers_subscribe_raw()?; + let blockheight = block_header.height; + + let mut deposit_result: Option = None; + + if coin.status == CoinStatus::INITIALISED { + let utxo_txid = utxo.tx_hash.to_string(); + let utxo_vout = utxo.tx_pos as u32; + + let backup_tx = create_tx1(client_config, coin, wallet_netwotk, &utxo_txid, utxo_vout).await?; + + let activity_utxo = format!("{}:{}", utxo.tx_hash.to_string(), utxo.tx_pos); + + let activity = Some(create_activity(&activity_utxo, utxo.value as u32, "deposit")); + // return Ok(Some(activity)); + + deposit_result = Some(DepositResult { + activity: activity.unwrap(), + backup_tx + }); + } + + if utxo.height > 0 { + + let confirmations = blockheight - utxo.height + 1; + + coin.status = CoinStatus::UNCONFIRMED; + + if confirmations as u32 >= client_config.confirmation_target { + coin.status = CoinStatus::CONFIRMED; + } + } + + Ok(deposit_result) +} + +async fn check_transfer(client_config: &ClientConfig, coin: &Coin) -> Result { + + if coin.statechain_id.is_none() { + return Err(anyhow!("Coin does not have a statechain ID")); + } + + let statechain_id = coin.statechain_id.as_ref().unwrap(); + + let statechain_info = crate::utils::get_statechain_info(statechain_id, &client_config).await?; + + // if the statechain info is not found, we assume the coin has been transferred + if statechain_info.is_none() { + return Ok(true); + } + + let statechain_info = statechain_info.unwrap(); + + let enclave_public_key = statechain_info.enclave_public_key; + + // if the enclave's public key is no longer part of the coin, the coin has been transferred + let is_transferred = !is_enclave_pubkey_part_of_coin(&coin, &enclave_public_key)?; + + return Ok(is_transferred); +} + +async fn check_withdrawal(client_config: &ClientConfig, coin: &mut Coin) -> Result<()> { + + let mut txid: Option = None; + + if coin.tx_withdraw.is_some() { + txid = Some(coin.tx_withdraw.as_ref().unwrap().to_string()); + } + + if coin.tx_cpfp.is_some() { + if txid.is_some() { + return Err(anyhow!("Coin has both tx_withdraw and tx_cpfp")); + } + txid = Some(coin.tx_cpfp.as_ref().unwrap().to_string()); + } + + if txid.is_none() { + return Err(anyhow!("Coin does not have tx_withdraw or tx_cpfp")); + } + + let txid = txid.unwrap(); + + if coin.withdrawal_address.is_none() { + return Err(anyhow!("Coin does not have withdrawal_address")); + } + + let address = Address::from_str(&coin.withdrawal_address.as_ref().unwrap())?.require_network(client_config.network)?; + + let utxo_list = client_config.electrum_client.script_list_unspent(&address.script_pubkey())?; + + let mut utxo: Option = None; + + for unspent in utxo_list { + if unspent.tx_hash.to_string() == txid { + utxo = Some(unspent); + break; + } + } + + if utxo.is_none() { + // sometimes the transaction has not yet been transmitted to the specified Electrum server + // return Err(anyhow!("There is no UTXO with the address {} and the txid {}", coin.withdrawal_address.as_ref().unwrap(), txid)); + return Ok(()); + } + + let utxo = utxo.unwrap(); + + if utxo.height > 0 { + + let block_header = client_config.electrum_client.block_headers_subscribe_raw()?; + let blockheight = block_header.height; + + let confirmations = blockheight - utxo.height + 1; + + if confirmations as u32 >= client_config.confirmation_target { + coin.status = CoinStatus::WITHDRAWN; + } + } + + + Ok(()) +} + +async fn check_for_duplicated(client_config: &ClientConfig, existing_coins: &Vec) -> Result>{ + + let mut duplicated_coin_list : Vec = Vec::new(); + + for coin in existing_coins.iter() { + + if coin.status != CoinStatus::IN_MEMPOOL && coin.status != CoinStatus::UNCONFIRMED && coin.status != CoinStatus::CONFIRMED { + continue; + } + + let address = Address::from_str(&coin.aggregated_address.as_ref().unwrap())?.require_network(client_config.network)?; + + let utxo_list = client_config.electrum_client.script_list_unspent(&address.script_pubkey())?; + + let mut max_duplicated_index = existing_coins.iter() + .filter(|c| c.statechain_id == coin.statechain_id ) + .map(|coin| coin.duplicate_index) + .max() + .unwrap(); + + for unspent in utxo_list { + + let utxo_exists = existing_coins.iter().any(|coin| { + coin.utxo_txid == Some(unspent.tx_hash.to_string()) && + coin.utxo_vout == Some(unspent.tx_pos as u32) + }); + + if utxo_exists { + continue; + } + + max_duplicated_index = max_duplicated_index + 1; + + let mut duplicated_coin = coin.clone(); + duplicated_coin.status = CoinStatus::DUPLICATED; + duplicated_coin.utxo_txid = Some(unspent.tx_hash.to_string()); + duplicated_coin.utxo_vout = Some(unspent.tx_pos as u32); + duplicated_coin.amount = Some(unspent.value as u32); + duplicated_coin.duplicate_index = max_duplicated_index; + duplicated_coin_list.push(duplicated_coin); + } + } + + Ok(duplicated_coin_list) + +} + +pub async fn update_coins(client_config: &ClientConfig, wallet_name: &str) -> Result<()> { + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let network = wallet.network.clone(); + + for coin in wallet.coins.iter_mut() { + + if coin.status == CoinStatus::INITIALISED || coin.status == CoinStatus::IN_MEMPOOL || coin.status == CoinStatus::UNCONFIRMED { + + let deposit_result = check_deposit(client_config, coin, &network).await?; + + if deposit_result.is_some() { + let deposit_result = deposit_result.unwrap(); + let activity = deposit_result.activity; + let backup_tx = deposit_result.backup_tx; + + wallet.activities.push(activity); + insert_backup_txs(&client_config.pool, &coin.statechain_id.as_ref().unwrap(), &[backup_tx].to_vec()).await?; + } + } else if coin.status == CoinStatus::IN_TRANSFER { + + let is_transferred = check_transfer(client_config, coin).await?; + + if is_transferred { + coin.status = CoinStatus::TRANSFERRED; + } + + } else if coin.status == CoinStatus::WITHDRAWING { + check_withdrawal(client_config, coin).await?; + } + } + + let duplicated_coins = check_for_duplicated(client_config, &wallet.coins).await?; + + wallet.coins.extend(duplicated_coins); + + update_wallet(&client_config.pool, &wallet).await?; + + Ok(()) +} \ No newline at end of file diff --git a/clients/libs/rust/src/deposit.rs b/clients/libs/rust/src/deposit.rs new file mode 100644 index 00000000..3ed62f31 --- /dev/null +++ b/clients/libs/rust/src/deposit.rs @@ -0,0 +1,155 @@ +use anyhow::{anyhow, Result, Ok}; +use mercurylib::{deposit::{create_deposit_msg1, create_aggregated_address}, wallet::{Wallet, BackupTx, CoinStatus, Coin}, transaction:: get_user_backup_address, utils::get_blockheight}; + +use crate::{client_config::ClientConfig, sqlite_manager::{get_wallet, update_wallet}, transaction::new_transaction, utils::info_config}; + +pub async fn get_deposit_bitcoin_address(client_config: &ClientConfig, wallet_name: &str, token_id: &str, amount: u32) -> Result { + + let token_id = uuid::Uuid::parse_str(&token_id)?; + // println!("Deposit: {} {} {}", wallet_name, token_id, amount); + let wallet = get_wallet(&client_config.pool, &wallet_name).await?; + let mut wallet = init(&client_config, &wallet, token_id).await?; + + let coin = wallet.coins.last_mut().unwrap(); + + let aggregated_public_key = create_aggregated_address(&coin, wallet.network.clone())?; + + coin.amount = Some(amount); + coin.aggregated_address = Some(aggregated_public_key.aggregate_address.clone()); + coin.aggregated_pubkey = Some(aggregated_public_key.aggregate_pubkey); + + update_wallet(&client_config.pool, &wallet).await?; + + Ok(aggregated_public_key.aggregate_address) +} + +pub async fn create_tx1(client_config: &ClientConfig, coin: &mut Coin, wallet_netwotk: &str, tx0_hash: &str, tx0_vout: u32) -> Result { + + if coin.status != CoinStatus::INITIALISED { + return Err(anyhow!("The coin with the public key {} is not in the INITIALISED state", coin.user_pubkey.to_string())); + } + + if coin.utxo_txid.is_some() && coin.utxo_vout.is_some() { + return Err(anyhow!("The coin with the public key {} has already been deposited", coin.user_pubkey.to_string())); + } + coin.utxo_txid = Some(tx0_hash.to_string()); + coin.utxo_vout = Some(tx0_vout); + + coin.status = CoinStatus::IN_MEMPOOL; + + let to_address = get_user_backup_address(&coin, wallet_netwotk.to_string())?; + + let server_info = info_config(&client_config).await?; + + let fee_rate_sats_per_byte = if server_info.fee_rate_sats_per_byte > client_config.max_fee_rate { + client_config.max_fee_rate + } else { + server_info.fee_rate_sats_per_byte + }; + + let signed_tx = new_transaction( + &client_config, + coin, + &to_address, + 0, + false, + None, + wallet_netwotk, + fee_rate_sats_per_byte, + server_info.initlock, + server_info.interval + ).await?; + + if coin.public_nonce.is_none() { + return Err(anyhow::anyhow!("coin.public_nonce is None")); + } + + if coin.blinding_factor.is_none() { + return Err(anyhow::anyhow!("coin.blinding_factor is None")); + } + + if coin.statechain_id.is_none() { + return Err(anyhow::anyhow!("coin.statechain_id is None")); + } + + let backup_tx = BackupTx { + tx_n: 1, + tx: signed_tx, + client_public_nonce: coin.public_nonce.as_ref().unwrap().to_string(), + server_public_nonce: coin.server_public_nonce.as_ref().unwrap().to_string(), + client_public_key: coin.user_pubkey.clone(), + server_public_key: coin.server_pubkey.as_ref().unwrap().to_string(), + blinding_factor: coin.blinding_factor.as_ref().unwrap().to_string(), + }; + + let block_height = Some(get_blockheight(&backup_tx)?); + coin.locktime = block_height; + + Ok(backup_tx) +} + +pub async fn init(client_config: &ClientConfig, wallet: &Wallet, token_id: uuid::Uuid) -> Result { + + let mut wallet = wallet.clone(); + + let coin = wallet.get_new_coin()?; + + wallet.coins.push(coin.clone()); + + update_wallet(&client_config.pool, &wallet).await?; + + let deposit_msg_1 = create_deposit_msg1(&coin, &token_id.to_string())?; + + // println!("deposit_msg_1: {:?}", deposit_msg_1); + + let endpoint = client_config.statechain_entity.clone(); + let path = "deposit/init/pod"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let response = request.json(&deposit_msg_1).send().await?; + + if response.status() != 200 { + let response_body = response.text().await?; + return Err(anyhow!(response_body)); + } + + let value = response.text().await?; + + let deposit_msg_1_response: mercurylib::deposit::DepositMsg1Response = serde_json::from_str(value.as_str())?; + + let deposit_init_result = mercurylib::deposit::handle_deposit_msg_1_response(&coin, &deposit_msg_1_response)?; + + let coin = wallet.coins.last_mut().unwrap(); + + coin.statechain_id = Some(deposit_init_result.statechain_id); + coin.signed_statechain_id = Some(deposit_init_result.signed_statechain_id); + coin.server_pubkey = Some(deposit_init_result.server_pubkey); + + update_wallet(&client_config.pool, &wallet).await?; + + Ok(wallet) +} + +pub async fn get_token(client_config: &ClientConfig) -> Result { + + let endpoint = client_config.statechain_entity.clone(); + let path = "deposit/get_token"; + + let client = client_config.get_reqwest_client()?; + let request = client.get(&format!("{}/{}", endpoint, path)); + + let response = request.send().await?; + + if response.status() != 200 { + let response_body = response.text().await?; + return Err(anyhow!(response_body)); + } + + let value = response.text().await?; + + let token: mercurylib::deposit::TokenID = serde_json::from_str(value.as_str())?; + + return Ok(token.token_id); +} diff --git a/clients/libs/rust/src/lib.rs b/clients/libs/rust/src/lib.rs new file mode 100644 index 00000000..17004821 --- /dev/null +++ b/clients/libs/rust/src/lib.rs @@ -0,0 +1,38 @@ +pub mod broadcast_backup_tx; +pub mod client_config; +pub mod coin_status; +pub mod deposit; +pub mod lightning_latch; +pub mod sqlite_manager; +pub mod transaction; +pub mod transfer_receiver; +pub mod transfer_sender; +pub mod utils; +pub mod wallet; +pub mod withdraw; + +pub use mercurylib::wallet::Wallet; +pub use mercurylib::wallet::CoinStatus; +pub use mercurylib::wallet::Coin; +pub use mercurylib::wallet::BackupTx; +pub use mercurylib::wallet::Activity; + +pub use mercurylib::transfer::sender::{TransferSenderRequestPayload, TransferSenderResponsePayload, create_transfer_signature, create_transfer_update_msg}; +pub use mercurylib::transaction::{SignFirstRequestPayload, SignFirstResponsePayload, create_and_commit_nonces}; +pub use mercurylib::utils::get_blockheight; +pub use mercurylib::{validate_address, decode_transfer_address}; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/clients/libs/rust/src/lightning_latch.rs b/clients/libs/rust/src/lightning_latch.rs new file mode 100644 index 00000000..b1f98f0c --- /dev/null +++ b/clients/libs/rust/src/lightning_latch.rs @@ -0,0 +1,147 @@ + +use crate::{client_config::ClientConfig, sqlite_manager::get_wallet}; +use anyhow::{anyhow, Result}; +use mercurylib::{transfer::sender::{PaymentHashRequestPayload, PaymentHashResponsePayload, TransferPreimageRequestPayload, TransferPreimageResponsePayload}, wallet::CoinStatus}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct CreatePreImageResponse { + pub hash: String, + pub batch_id: String, +} + +pub async fn create_pre_image( + client_config: &ClientConfig, + wallet_name: &str, + statechain_id: &str) -> Result +{ + let batch_id = Some(uuid::Uuid::new_v4().to_string()).unwrap(); + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let coin = wallet.coins + .iter_mut() + .filter(|tx| tx.statechain_id == Some(statechain_id.to_string())) // Filter coins with the specified statechain_id + .min_by_key(|tx| tx.locktime.unwrap_or(u32::MAX)); // Find the one with the lowest locktime + + if coin.is_none() { + return Err(anyhow!("No coins associated with this statechain ID were found")); + } + + let coin = coin.unwrap(); + + if coin.amount.is_none() { + return Err(anyhow::anyhow!("coin.amount is None")); + } + + if coin.status != CoinStatus::CONFIRMED && coin.status != CoinStatus::IN_TRANSFER { + return Err(anyhow::anyhow!("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is {}", coin.status)); + } + + if coin.locktime.is_none() { + return Err(anyhow::anyhow!("coin.locktime is None")); + } + + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap(); + + let payment_hash_payload = PaymentHashRequestPayload { + statechain_id: statechain_id.to_string(), + auth_sig: signed_statechain_id.to_string(), + batch_id: batch_id.clone(), + }; + + let endpoint = client_config.statechain_entity.clone(); + let path = "transfer/paymenthash"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let response = request.json(&payment_hash_payload).send().await?; + + if response.status() != 200 { + let response_body = response.text().await?; + return Err(anyhow!(response_body)); + } + + let value = response.text().await?; + + let payment_hash_response_payload: PaymentHashResponsePayload = serde_json::from_str(value.as_str())?; + + Ok(CreatePreImageResponse { + hash: payment_hash_response_payload.hash, + batch_id, + }) +} + +pub async fn confirm_pending_invoice(client_config: &ClientConfig, wallet_name: &str, statechain_id: &str) -> Result<()> { + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let coin = wallet.coins + .iter_mut() + .filter(|tx| tx.statechain_id == Some(statechain_id.to_string())) // Filter coins with the specified statechain_id + .min_by_key(|tx| tx.locktime.unwrap_or(u32::MAX)); // Find the one with the lowest locktime + + if coin.is_none() { + return Err(anyhow!("No coins associated with this statechain ID were found")); + } + + let coin = coin.unwrap(); + + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap(); + + let path = "transfer/unlock"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", client_config.statechain_entity, path)); + + let transfer_unlock_request_payload = mercurylib::transfer::receiver::TransferUnlockRequestPayload { + statechain_id: statechain_id.to_string(), + auth_sig: signed_statechain_id.to_string(), + auth_pub_key: None, + }; + + let status = request.json(&transfer_unlock_request_payload).send().await?.status(); + + if !status.is_success() { + return Err(anyhow::anyhow!("Failed to update transfer message".to_string())); + } + + Ok(()) +} + +pub async fn retrieve_pre_image(client_config: &ClientConfig, wallet_name: &str, statechain_id: &str, batch_id: &str) -> Result { + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let coin = wallet.coins + .iter_mut() + .filter(|tx| tx.statechain_id == Some(statechain_id.to_string())) // Filter coins with the specified statechain_id + .min_by_key(|tx| tx.locktime.unwrap_or(u32::MAX)); // Find the one with the lowest locktime + + if coin.is_none() { + return Err(anyhow!("No coins associated with this statechain ID were found")); + } + + let coin = coin.unwrap(); + + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap(); + + let path = "transfer/transfer_preimage"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", client_config.statechain_entity, path)); + + let transfer_preimage_request_payload = TransferPreimageRequestPayload { + statechain_id: statechain_id.to_string(), + auth_sig: signed_statechain_id.to_string(), + previous_user_auth_key: coin.auth_pubkey.to_string(), + batch_id: batch_id.to_string(), + }; + + let value = request.json(&transfer_preimage_request_payload).send().await?.text().await?; + + let transfer_preimage_response_payload: TransferPreimageResponsePayload = serde_json::from_str(value.as_str())?; + + Ok(transfer_preimage_response_payload.preimage) +} diff --git a/clients/libs/rust/src/sqlite_manager.rs b/clients/libs/rust/src/sqlite_manager.rs new file mode 100644 index 00000000..088fb753 --- /dev/null +++ b/clients/libs/rust/src/sqlite_manager.rs @@ -0,0 +1,130 @@ +use mercurylib::wallet::{Wallet, BackupTx}; +use serde_json::json; +use sqlx::{Pool, Sqlite, Row}; +use anyhow::{anyhow, Result}; + +pub async fn insert_wallet(pool: &Pool, wallet: &Wallet) -> Result<()> { + + let wallet_json = json!(wallet).to_string(); + + let query = "INSERT INTO wallet (wallet_name, wallet_json) VALUES ($1, $2)"; + + let _ = sqlx::query(query) + .bind(wallet.name.clone()) + .bind(wallet_json) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn get_wallet(pool: &Pool, wallet_name: &str) -> Result { + + let query = "SELECT wallet_json FROM wallet WHERE wallet_name = $1"; + + let row = sqlx::query(query) + .bind(wallet_name) + .fetch_one(pool) + .await?; + + if row.is_empty() { + return Err(anyhow!("Wallet not found")); + } + + let wallet_json: String = row.get(0); + + let wallet: Wallet = serde_json::from_str(&wallet_json)?; + + Ok(wallet) +} + +pub async fn update_wallet(pool: &Pool, wallet: &Wallet) -> Result<()> { + + let wallet_json = json!(wallet).to_string(); + + let query = "UPDATE wallet SET wallet_json = $1 WHERE wallet_name = $2"; + + let _ = sqlx::query(query) + .bind(wallet_json) + .bind(wallet.name.clone()) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn insert_backup_txs(pool: &Pool, statechain_id: &str, backup_txs: &Vec) -> Result<()> { + + let backup_txs_json = json!(backup_txs).to_string(); + + let query = "INSERT INTO backup_txs (statechain_id, txs) VALUES ($1, $2)"; + + let _ = sqlx::query(query) + .bind(statechain_id) + .bind(backup_txs_json) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn update_backup_txs(pool: &Pool, statechain_id: &str, backup_txs: &Vec) -> Result<()> { + + let backup_txs_json = json!(backup_txs).to_string(); + + let query = "UPDATE backup_txs SET txs = $1 WHERE statechain_id = $2"; + + let _ = sqlx::query(query) + .bind(backup_txs_json) + .bind(statechain_id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn get_backup_txs(pool: &Pool, statechain_id: &str,) -> Result> { + + let query = "SELECT txs FROM backup_txs WHERE statechain_id = $1"; + + let row = sqlx::query(query) + .bind(statechain_id) + .fetch_one(pool) + .await?; + + if row.is_empty() { + return Err(anyhow!("Statechain id not found")); + } + + let backup_txs_json: String = row.get(0); + + let backup_txs: Vec = serde_json::from_str(&backup_txs_json)?; + + Ok(backup_txs) +} + +pub async fn insert_or_update_backup_txs(pool: &Pool, statechain_id: &str, backup_txs: &Vec) -> Result<()> { + + let mut transaction = pool.begin().await?; + + let backup_txs_json = json!(backup_txs).to_string(); + + let query = "DELETE FROM backup_txs WHERE statechain_id = $1"; + + let _ = sqlx::query(query) + .bind(statechain_id) + .execute(&mut *transaction) + .await?; + + let query = "INSERT INTO backup_txs (statechain_id, txs) VALUES ($1, $2)"; + + let _ = sqlx::query(query) + .bind(statechain_id) + .bind(backup_txs_json) + .execute(&mut *transaction) + .await?; + + transaction.commit().await?; + + Ok(()) +} \ No newline at end of file diff --git a/clients/libs/rust/src/transaction.rs b/clients/libs/rust/src/transaction.rs new file mode 100644 index 00000000..bbe5403a --- /dev/null +++ b/clients/libs/rust/src/transaction.rs @@ -0,0 +1,127 @@ +use electrum_client::ElectrumApi; +use mercurylib::{transaction::{SignFirstRequestPayload, PartialSignatureRequestPayload, PartialSignatureResponsePayload, get_partial_sig_request, create_signature, new_backup_transaction}, wallet::Coin}; +use anyhow::Result; +use reqwest::StatusCode; +use secp256k1_zkp::musig::MusigPartialSignature; +use serde_json::Value; +use crate::client_config::ClientConfig; + +pub async fn new_transaction( + client_config: &ClientConfig, + coin: &mut Coin, + to_address: &str, + qt_backup_tx: u32, + is_withdrawal: bool, + block_height: Option, + network: &str, + fee_rate_sats_per_byte: f64, + initlock: u32, + interval: u32) -> Result { + + // TODO: validate address first + + let coin_nonce = mercurylib::transaction::create_and_commit_nonces(&coin)?; + coin.secret_nonce = Some(coin_nonce.secret_nonce); + coin.public_nonce = Some(coin_nonce.public_nonce); + coin.blinding_factor = Some(coin_nonce.blinding_factor); + + let server_public_nonce = sign_first(&client_config, &coin_nonce.sign_first_request_payload).await?; + + coin.server_public_nonce = Some(server_public_nonce); + + let block_height = match block_height { + Some(block_height) => block_height, + None => { + let block_header = client_config.electrum_client.block_headers_subscribe_raw()?; + block_header.height as u32 + }, + }; + + let partial_sig_request = get_partial_sig_request( + &coin, + block_height, + initlock, + interval, + fee_rate_sats_per_byte, + qt_backup_tx, + to_address.to_string(), + network.to_string(), + is_withdrawal)?; + + let server_partial_sig_request = partial_sig_request.partial_signature_request_payload; + + let server_partial_sig = sign_second(&client_config, &server_partial_sig_request).await?; + + let client_partial_sig_hex = partial_sig_request.client_partial_sig; + let server_partial_sig_hex = hex::encode(server_partial_sig.serialize()); + let msg = partial_sig_request.msg; + let session_hex = partial_sig_request.encoded_session; + let output_pubkey_hex = partial_sig_request.output_pubkey; + + let encoded_unsigned_tx = partial_sig_request.encoded_unsigned_tx; + + let signature = create_signature(msg, client_partial_sig_hex, server_partial_sig_hex, session_hex, output_pubkey_hex)?; + + let signed_tx = new_backup_transaction(encoded_unsigned_tx, signature)?; + + Ok(signed_tx) +} + +/// This function gets the server public nonce from the statechain entity. +pub async fn sign_first(client_config: &ClientConfig, sign_first_request_payload: &SignFirstRequestPayload) -> Result { + + let endpoint = client_config.statechain_entity.clone(); + let path = "sign/first"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + // let value = request.json(&sign_first_request_payload).send().await?.text().await?; + + let response = request.json(&sign_first_request_payload).send().await?; + + let status = response.status(); + + let value = response.text().await?; + + if status != StatusCode::OK{ + + let error_message: Value = serde_json::from_str(value.as_str())?; + + return Err(anyhow::anyhow!("{}", error_message["message"].as_str().unwrap())); + } + + let sign_first_response_payload: mercurylib::transaction::SignFirstResponsePayload = serde_json::from_str(value.as_str())?; + + let mut server_pubnonce_hex = sign_first_response_payload.server_pubnonce.to_string(); + + if server_pubnonce_hex.starts_with("0x") { + server_pubnonce_hex = server_pubnonce_hex[2..].to_string(); + } + + Ok(server_pubnonce_hex) +} + +pub async fn sign_second(client_config: &ClientConfig, partial_sig_request: &PartialSignatureRequestPayload) -> Result { + let endpoint = client_config.statechain_entity.clone(); + let path = "sign/second"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let value = request.json(&partial_sig_request).send().await?.text().await?; + + let response: PartialSignatureResponsePayload = serde_json::from_str(value.as_str())?; + + let mut server_partial_sig_hex = response.partial_sig.to_string(); + + if server_partial_sig_hex.starts_with("0x") { + server_partial_sig_hex = server_partial_sig_hex[2..].to_string(); + } + + let server_partial_sig_bytes = hex::decode(server_partial_sig_hex)?; + + let server_partial_sig = MusigPartialSignature::from_slice(server_partial_sig_bytes.as_slice())?; + + Ok(server_partial_sig) +} \ No newline at end of file diff --git a/clients/libs/rust/src/transfer_receiver.rs b/clients/libs/rust/src/transfer_receiver.rs new file mode 100644 index 00000000..2de94b5d --- /dev/null +++ b/clients/libs/rust/src/transfer_receiver.rs @@ -0,0 +1,400 @@ +use std::{collections::{HashMap, HashSet}, str::FromStr}; + +use crate::{sqlite_manager::{get_wallet, update_wallet, insert_or_update_backup_txs}, client_config::ClientConfig, utils}; +use anyhow::{anyhow, Ok, Result}; +use bitcoin::{Txid, Address}; +use chrono::Utc; +use electrum_client::ElectrumApi; +use mercurylib::{utils::{get_network, InfoConfig}, wallet::{Activity, Coin, CoinStatus}}; +use reqwest::StatusCode; + +pub async fn new_transfer_address(client_config: &ClientConfig, wallet_name: &str) -> Result{ + + let wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let mut wallet = wallet.clone(); + + let coin = wallet.get_new_coin()?; + + wallet.coins.push(coin.clone()); + + update_wallet(&client_config.pool, &wallet).await?; + + Ok(coin.address) +} + +pub struct TransferReceiveResult { + pub is_there_batch_locked: bool, + pub received_statechain_ids: Vec, +} + +pub async fn execute(client_config: &ClientConfig, wallet_name: &str) -> Result{ + + let mut wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let info_config = utils::info_config(&client_config).await.unwrap(); + + let mut unique_auth_pubkeys: HashSet = HashSet::new(); + + for coin in wallet.coins.iter() { + unique_auth_pubkeys.insert(coin.auth_pubkey.clone()); + } + + let mut enc_msgs_per_auth_pubkey: HashMap> = HashMap::new(); + + for auth_pubkey in unique_auth_pubkeys { + + let enc_messages = get_msg_addr(&auth_pubkey, &client_config).await?; + if enc_messages.len() == 0 { + continue; + } + + enc_msgs_per_auth_pubkey.insert(auth_pubkey.clone(), enc_messages); + } + + let mut is_there_batch_locked = false; + + let mut received_statechain_ids = Vec::::new(); + + let mut temp_coins = wallet.coins.clone(); + let mut temp_activities = wallet.activities.clone(); + + for (key, values) in &enc_msgs_per_auth_pubkey { + + let auth_pubkey = key.clone(); + + for enc_message in values { + + let coin: Option<&mut Coin> = temp_coins.iter_mut().find(|coin| coin.auth_pubkey == auth_pubkey && coin.status == CoinStatus::INITIALISED); + + if coin.is_some() { + + let mut coin = coin.unwrap(); + + let message_result = process_encrypted_message(client_config, &mut coin, enc_message, &wallet.network, &info_config, &mut temp_activities).await; + + if message_result.is_err() { + println!("Error: {}", message_result.err().unwrap().to_string()); + continue; + } + + let message_result = message_result.unwrap(); + + if message_result.is_batch_locked { + is_there_batch_locked = true; + } + + if message_result.statechain_id.is_some() { + received_statechain_ids.push(message_result.statechain_id.unwrap()); + } + + } else { + + let new_coin = mercurylib::transfer::receiver::duplicate_coin_to_initialized_state(&wallet, &auth_pubkey); + + if new_coin.is_err() { + println!("Error: {}", new_coin.err().unwrap().to_string()); + continue; + } + + let mut new_coin = new_coin.unwrap(); + + let message_result = process_encrypted_message(client_config, &mut new_coin, enc_message, &wallet.network, &info_config, &mut temp_activities).await; + + if message_result.is_err() { + println!("Error: {}", message_result.err().unwrap().to_string()); + continue; + } + + temp_coins.push(new_coin); + + let message_result = message_result.unwrap(); + + if message_result.is_batch_locked { + is_there_batch_locked = true; + } + + if message_result.statechain_id.is_some() { + received_statechain_ids.push(message_result.statechain_id.unwrap()); + } + } + } + } + + wallet.coins = temp_coins.clone(); + wallet.activities = temp_activities.clone(); + + update_wallet(&client_config.pool, &wallet).await?; + + Ok(TransferReceiveResult{ + is_there_batch_locked, + received_statechain_ids + }) +} + +async fn get_msg_addr(auth_pubkey: &str, client_config: &ClientConfig) -> Result> { + + let path = format!("transfer/get_msg_addr/{}", auth_pubkey.to_string()); + + let client = client_config.get_reqwest_client()?; + let request = client.get(&format!("{}/{}", client_config.statechain_entity, path)); + + let value = request.send().await?.text().await?; + + let response: mercurylib::transfer::receiver::GetMsgAddrResponsePayload = serde_json::from_str(value.as_str())?; + + Ok(response.list_enc_transfer_msg) +} + +pub struct MessageResult { + pub is_batch_locked: bool, + pub statechain_id: Option, +} + +async fn process_encrypted_message(client_config: &ClientConfig, coin: &mut Coin, enc_message: &str, network: &str, info_config: &InfoConfig, activities: &mut Vec) -> Result { + + let client_auth_key = coin.auth_privkey.clone(); + let new_user_pubkey = coin.user_pubkey.clone(); + + let transfer_msg = mercurylib::transfer::receiver::decrypt_transfer_msg(enc_message, &client_auth_key)?; + + let tx0_outpoint = mercurylib::transfer::receiver::get_tx0_outpoint(&transfer_msg.backup_transactions)?; + + let tx0_hex = get_tx0(&client_config.electrum_client, &tx0_outpoint.txid).await?; + + let is_transfer_signature_valid = mercurylib::transfer::receiver::verify_transfer_signature(&new_user_pubkey, &tx0_outpoint, &transfer_msg)?; + + if !is_transfer_signature_valid { + return Err(anyhow::anyhow!("Invalid transfer signature".to_string())); + } + + let statechain_info = utils::get_statechain_info(&transfer_msg.statechain_id, &client_config).await?; + + if statechain_info.is_none() { + return Err(anyhow::anyhow!("Statechain info not found".to_string())); + } + + let statechain_info = statechain_info.unwrap(); + + let is_tx0_output_pubkey_valid = mercurylib::transfer::receiver::validate_tx0_output_pubkey(&statechain_info.enclave_public_key, &transfer_msg, &tx0_outpoint, &tx0_hex, network)?; + + if !is_tx0_output_pubkey_valid { + return Err(anyhow::anyhow!("Invalid tx0 output pubkey".to_string())); + } + + let latest_backup_tx_pays_to_user_pubkey = mercurylib::transfer::receiver::verify_latest_backup_tx_pays_to_user_pubkey(&transfer_msg, &new_user_pubkey, network)?; + + if !latest_backup_tx_pays_to_user_pubkey { + return Err(anyhow::anyhow!("Latest Backup Tx does not pay to the expected public key".to_string())); + } + + if statechain_info.num_sigs != transfer_msg.backup_transactions.len() as u32 { + return Err(anyhow::anyhow!("num_sigs is not correct".to_string())); + } + + let (is_tx0_output_unspent, tx0_status) = verify_tx0_output_is_unspent_and_confirmed(&client_config.electrum_client, &tx0_outpoint, &tx0_hex, &network, client_config.confirmation_target).await?; + + if !is_tx0_output_unspent { + return Err(anyhow::anyhow!("tx0 output is spent or not confirmed".to_string())); + } + + let current_fee_rate_sats_per_byte = if info_config.fee_rate_sats_per_byte > client_config.max_fee_rate { + client_config.max_fee_rate + } else { + info_config.fee_rate_sats_per_byte + }; + + let previous_lock_time = mercurylib::transfer::receiver::validate_signature_scheme( + &transfer_msg, + &statechain_info, + &tx0_hex, + client_config.fee_rate_tolerance, + current_fee_rate_sats_per_byte, + info_config.interval); + + if previous_lock_time.is_err() { + let error = previous_lock_time.err().unwrap(); + return Err(anyhow!("Signature scheme validation failed. Error {}", error.to_string())); + } + + let previous_lock_time = previous_lock_time.unwrap(); + + let transfer_receiver_request_payload = mercurylib::transfer::receiver::create_transfer_receiver_request_payload(&statechain_info, &transfer_msg, &coin)?; + + // unlock the statecoin - it might be part of a batch + + // the pub_auth_key has not been updated yet in the server (it will be updated after the transfer/receive call) + // So we need to manually sign the statechain_id with the client_auth_key + let signed_statechain_id_for_unlock = mercurylib::transfer::receiver::sign_message(&transfer_msg.statechain_id, &coin)?; + + unlock_statecoin(&client_config, &transfer_msg.statechain_id, &signed_statechain_id_for_unlock, &coin.auth_pubkey).await?; + + let transfer_receiver_result = send_transfer_receiver_request_payload(&client_config, &transfer_receiver_request_payload).await; + + let server_public_key_hex = match transfer_receiver_result { + std::result::Result::Ok(server_public_key_hex) => { + + if server_public_key_hex.is_batch_locked { + return Ok(MessageResult { + is_batch_locked: true, + statechain_id: None, + }); + } + + server_public_key_hex.server_pubkey.unwrap() + }, + Err(err) => { + return Err(anyhow::anyhow!("Error: {}", err.to_string())); + } + }; + + let new_key_info = mercurylib::transfer::receiver::get_new_key_info(&server_public_key_hex, &coin, &transfer_msg.statechain_id, &tx0_outpoint, &tx0_hex, network)?; + + coin.server_pubkey = Some(server_public_key_hex); + coin.aggregated_pubkey = Some(new_key_info.aggregate_pubkey); + coin.aggregated_address = Some(new_key_info.aggregate_address); + coin.statechain_id = Some(transfer_msg.statechain_id.clone()); + coin.signed_statechain_id = Some(new_key_info.signed_statechain_id.clone()); + coin.amount = Some(new_key_info.amount); + coin.utxo_txid = Some(tx0_outpoint.txid.clone()); + coin.utxo_vout = Some(tx0_outpoint.vout); + coin.locktime = Some(previous_lock_time); + coin.status = tx0_status; + + let date = Utc::now(); // This will get the current date and time in UTC + let iso_string = date.to_rfc3339(); // Converts the date to an ISO 8601 string + + let activity = Activity { + utxo: tx0_outpoint.txid.clone(), + amount: new_key_info.amount, + action: "Receive".to_string(), + date: iso_string + }; + + activities.push(activity); + + insert_or_update_backup_txs(&client_config.pool, &transfer_msg.statechain_id, &transfer_msg.backup_transactions).await?; + + Ok(MessageResult { + is_batch_locked: false, + statechain_id: Some(transfer_msg.statechain_id.clone()), + }) + + // Ok(transfer_msg.statechain_id.clone()) +} + +async fn get_tx0(electrum_client: &electrum_client::Client, tx0_txid: &str) -> Result { + + let tx0_txid = Txid::from_str(tx0_txid)?; + let tx_bytes = electrum_client.batch_transaction_get_raw(&[tx0_txid])?; + + if tx_bytes.len() == 0 { + return Err(anyhow!("tx0 not found")); + } + + // let tx0 = bitcoin::consensus::encode::deserialize(&tx_bytes[0])?; + + let tx0_hex = hex::encode(&tx_bytes[0]); + + Ok(tx0_hex) +} + +async fn verify_tx0_output_is_unspent_and_confirmed(electrum_client: &electrum_client::Client, tx0_outpoint: &mercurylib::transfer::receiver::TxOutpoint, tx0_hex: &str, network: &str, confirmation_target: u32) -> Result<(bool, CoinStatus)> { + let output_address = mercurylib::transfer::receiver::get_output_address_from_tx0(&tx0_outpoint, &tx0_hex, &network)?; + + let network = get_network(&network)?; + let address = Address::from_str(&output_address)?.require_network(network)?; + let script = address.script_pubkey(); + let script = script.as_script(); + + let res = electrum_client.script_list_unspent(script)?; + + let block_header = electrum_client.block_headers_subscribe_raw()?; + let blockheight = block_header.height; + + let mut status = CoinStatus::UNCONFIRMED; + + for unspent in res { + if (unspent.tx_hash.to_string() == tx0_outpoint.txid) && (unspent.tx_pos as u32 == tx0_outpoint.vout) { + let confirmations = blockheight - unspent.height + 1; + + if confirmations as u32 >= confirmation_target { + status = CoinStatus::CONFIRMED; + } + + return Ok((true, status)); + } + } + + Ok((false, status)) +} + +async fn unlock_statecoin(client_config: &ClientConfig, statechain_id: &str, signed_statechain_id: &str, auth_pubkey: &str) -> Result<()> { + + let path = "transfer/unlock"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", client_config.statechain_entity, path)); + + let transfer_unlock_request_payload = mercurylib::transfer::receiver::TransferUnlockRequestPayload { + statechain_id: statechain_id.to_string(), + auth_sig: signed_statechain_id.to_string(), + auth_pub_key: Some(auth_pubkey.to_string()), + }; + + let status = request.json(&transfer_unlock_request_payload).send().await?.status(); + + if !status.is_success() { + return Err(anyhow::anyhow!("Failed to update transfer message".to_string())); + } + + Ok(()) +} + +pub struct TransferReceiveRequestResult { + pub is_batch_locked: bool, + pub server_pubkey: Option, +} + +async fn send_transfer_receiver_request_payload(client_config: &ClientConfig, transfer_receiver_request_payload: &mercurylib::transfer::receiver::TransferReceiverRequestPayload) -> Result{ + + let path = "transfer/receiver"; + + let client = client_config.get_reqwest_client()?; + + let request: reqwest::RequestBuilder = client.post(&format!("{}/{}", client_config.statechain_entity, path)); + + let response = request.json(&transfer_receiver_request_payload).send().await?; + + let status = response.status(); + + let value = response.text().await?; + + if status == StatusCode::BAD_REQUEST{ + + let error: mercurylib::transfer::receiver::TransferReceiverErrorResponsePayload = serde_json::from_str(value.as_str())?; + + match error.code { + mercurylib::transfer::receiver::TransferReceiverError::ExpiredBatchTimeError => { + return Err(anyhow::anyhow!(error.message)); + }, + mercurylib::transfer::receiver::TransferReceiverError::StatecoinBatchLockedError => { + return Ok(TransferReceiveRequestResult { + is_batch_locked: true, + server_pubkey: None, + }); + }, + } + } + + if status == StatusCode::OK { + let response: mercurylib::transfer::receiver::TransferReceiverPostResponsePayload = serde_json::from_str(value.as_str())?; + return Ok(TransferReceiveRequestResult { + is_batch_locked: false, + server_pubkey: Some(response.server_pubkey) + }); + } else { + return Err(anyhow::anyhow!("{}: {}", "Failed to update transfer message".to_string(), value)); + } + +} \ No newline at end of file diff --git a/clients/libs/rust/src/transfer_sender.rs b/clients/libs/rust/src/transfer_sender.rs new file mode 100644 index 00000000..6904465a --- /dev/null +++ b/clients/libs/rust/src/transfer_sender.rs @@ -0,0 +1,219 @@ +use crate::{client_config::ClientConfig, sqlite_manager::{get_backup_txs, get_wallet, update_backup_txs, update_wallet}, transaction::new_transaction, utils::info_config}; +use anyhow::{anyhow, Result}; +use chrono::Utc; +use mercurylib::{wallet::{Coin, BackupTx, Activity, CoinStatus}, utils::get_blockheight, decode_transfer_address, transfer::sender::{TransferSenderRequestPayload, TransferSenderResponsePayload, create_transfer_signature, create_transfer_update_msg}}; +use electrum_client::ElectrumApi; + +pub async fn execute( + client_config: &ClientConfig, + recipient_address: &str, + wallet_name: &str, + statechain_id: &str, + force_send: bool, + batch_id: Option) -> Result<()> +{ + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let is_address_valid = mercurylib::validate_address(recipient_address, &wallet.network)?; + + if !is_address_valid { + return Err(anyhow!("Invalid address")); + } + + let mut backup_transactions = get_backup_txs(&client_config.pool, &statechain_id).await?; + + if backup_transactions.len() == 0 { + return Err(anyhow!("No backup transaction associated with this statechain ID were found")); + } + + let qt_backup_tx = backup_transactions.len() as u32; + + backup_transactions.sort_by(|a, b| a.tx_n.cmp(&b.tx_n)); + + let new_tx_n = backup_transactions.len() as u32 + 1; + + let is_coin_duplicated = wallet.coins.iter().any(|c| { + c.statechain_id == Some(statechain_id.to_string()) && + c.status == CoinStatus::DUPLICATED + }); + + let are_there_duplicate_coins_withdrawn = wallet.coins.iter().any(|c| { + c.statechain_id == Some(statechain_id.to_string()) && + (c.status == CoinStatus::WITHDRAWING || c.status == CoinStatus::WITHDRAWING) && + c.duplicate_index > 0 + }); + + if are_there_duplicate_coins_withdrawn { + return Err(anyhow::anyhow!("There have been withdrawals of other coins with this same statechain_id (possibly duplicates).\ + This transfer cannot be performed because the recipient would reject it due to the difference in signature count.\ + This coin can be withdrawn, however.")); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + let coin = wallet.coins + .iter_mut() + .filter(|c| c.statechain_id == Some(statechain_id.to_string()) && c.status != CoinStatus::DUPLICATED) // Filter coins with the specified statechain_id + .min_by_key(|c| c.locktime.unwrap_or(u32::MAX)); // Find the one with the lowest locktime + + if coin.is_none() { + return Err(anyhow!("No coins associated with this statechain ID were found")); + } + + let coin = coin.unwrap(); + + if is_coin_duplicated && !force_send { + return Err(anyhow::anyhow!("Coin is duplicated. If you want to proceed, use the command '--force, -f' option. \ + You will no longer be able to move other duplicate coins with the same statechain_id and this will cause PERMANENT LOSS of these duplicate coin funds.")); + } + + if coin.amount.is_none() { + return Err(anyhow::anyhow!("coin.amount is None")); + } + + if coin.status != CoinStatus::CONFIRMED && coin.status != CoinStatus::IN_TRANSFER { + return Err(anyhow::anyhow!("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is {}", coin.status)); + } + + if coin.locktime.is_none() { + return Err(anyhow::anyhow!("coin.locktime is None")); + } + + let block_header = client_config.electrum_client.block_headers_subscribe_raw()?; + let current_blockheight = block_header.height as u32; + + if current_blockheight > coin.locktime.unwrap() { + return Err(anyhow::anyhow!("The coin is expired. Coin locktime is {} and current blockheight is {}", coin.locktime.unwrap(), current_blockheight)); + } + + let statechain_id = coin.statechain_id.as_ref().unwrap(); + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap(); + + let (_, _, recipient_auth_pubkey) = decode_transfer_address(recipient_address)?; + let x1 = get_new_x1(&client_config, statechain_id, signed_statechain_id, &recipient_auth_pubkey.to_string(), batch_id).await?; + + let bkp_tx1 = &backup_transactions[0]; + + let signed_tx = create_backup_tx_to_receiver(client_config, coin, bkp_tx1, recipient_address, qt_backup_tx, &wallet.network).await?; + + let backup_tx = BackupTx { + tx_n: new_tx_n, + tx: signed_tx.clone(), + client_public_nonce: coin.public_nonce.as_ref().unwrap().to_string(), + server_public_nonce: coin.server_public_nonce.as_ref().unwrap().to_string(), + client_public_key: coin.user_pubkey.clone(), + server_public_key: coin.server_pubkey.as_ref().unwrap().to_string(), + blinding_factor: coin.blinding_factor.as_ref().unwrap().to_string(), + }; + + backup_transactions.push(backup_tx); + + let input_txid = coin.utxo_txid.as_ref().unwrap(); + let input_vout = coin.utxo_vout.unwrap(); + let client_seckey = coin.user_privkey.as_ref(); + + let transfer_signature = create_transfer_signature(recipient_address, input_txid, input_vout, client_seckey)?; + + let transfer_update_msg_request_payload = create_transfer_update_msg(&x1, recipient_address, &coin, &transfer_signature, &backup_transactions)?; + + let endpoint = client_config.statechain_entity.clone(); + let path = "transfer/update_msg"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let status = request.json(&transfer_update_msg_request_payload).send().await?.status(); + + if !status.is_success() { + return Err(anyhow::anyhow!("Failed to update transfer message".to_string())); + } + + update_backup_txs(&client_config.pool, &coin.statechain_id.as_ref().unwrap(), &backup_transactions).await?; + + let date = Utc::now(); // This will get the current date and time in UTC + let iso_string = date.to_rfc3339(); // Converts the date to an ISO 8601 string + + let utxo = format!("{}:{}", input_txid, input_vout); + + let activity = Activity { + utxo, + amount: coin.amount.unwrap(), + action: "Transfer".to_string(), + date: iso_string + }; + + wallet.activities.push(activity); + coin.status = CoinStatus::IN_TRANSFER; + + update_wallet(&client_config.pool, &wallet).await?; + + Ok(()) +} + +async fn create_backup_tx_to_receiver(client_config: &ClientConfig, coin: &mut Coin, bkp_tx1: &BackupTx, recipient_address: &str, qt_backup_tx: u32, network: &str) -> Result { + + let block_height = Some(get_blockheight(bkp_tx1)?); + + let server_info = info_config(&client_config).await?; + + let fee_rate_sats_per_byte = if server_info.fee_rate_sats_per_byte > client_config.max_fee_rate { + client_config.max_fee_rate + } else { + server_info.fee_rate_sats_per_byte + }; + + let is_withdrawal = false; + let signed_tx = new_transaction( + client_config, + coin, + recipient_address, + qt_backup_tx, + is_withdrawal, + block_height, + network, + fee_rate_sats_per_byte, + server_info.initlock, + server_info.interval).await?; + + Ok(signed_tx) +} + +async fn get_new_x1(client_config: &ClientConfig, statechain_id: &str, signed_statechain_id: &str, recipient_auth_pubkey: &str, batch_id: Option) -> Result { + + let endpoint = client_config.statechain_entity.clone(); + let path = "transfer/sender"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let transfer_sender_request_payload = TransferSenderRequestPayload { + statechain_id: statechain_id.to_string(), + auth_sig: signed_statechain_id.to_string(), + new_user_auth_key: recipient_auth_pubkey.to_string(), + batch_id, + }; + + let value = match request.json(&transfer_sender_request_payload).send().await { + Ok(response) => { + + let status = response.status(); + let text = response.text().await.unwrap_or("Unexpected error".to_string()); + + if status.is_success() { + text + } else { + return Err(anyhow::anyhow!(format!("status: {}, error: {}", status, text))); + } + }, + Err(err) => { + return Err(anyhow::anyhow!(format!("status: {}, error: {}", err.status().unwrap(),err.to_string()))); + }, + }; + + let response: TransferSenderResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); + + Ok(response.x1) +} + + diff --git a/clients/libs/rust/src/utils.rs b/clients/libs/rust/src/utils.rs new file mode 100644 index 00000000..6eeca1ce --- /dev/null +++ b/clients/libs/rust/src/utils.rs @@ -0,0 +1,97 @@ + +use chrono::Utc; +use electrum_client::ElectrumApi; +use mercurylib::{transfer::receiver::StatechainInfoResponsePayload, utils::{InfoConfig, ServerConfig}, wallet::Activity, withdraw::WithdrawCompletePayload}; +use anyhow::{anyhow, Result, Ok}; +use reqwest::StatusCode; +use crate::client_config::ClientConfig; + +pub async fn info_config(client_config: &ClientConfig) -> Result{ + + let path = "info/config"; + + let client = client_config.get_reqwest_client()?; + let request = client.get(&format!("{}/{}", client_config.statechain_entity, path)); + + let value = request.send().await?.text().await?; + + let server_config: ServerConfig = serde_json::from_str(value.as_str())?; + + let initlock = server_config.initlock; + let interval = server_config.interval; + + let number_blocks = 3; + let mut fee_rate_btc_per_kb = client_config.electrum_client.estimate_fee(number_blocks)?; + + // Why does it happen? + if fee_rate_btc_per_kb <= 0.0 { + fee_rate_btc_per_kb = 0.00001; + } + + let fee_rate_sats_per_byte = fee_rate_btc_per_kb * 100000.0; + + Ok(InfoConfig { + initlock, + interval, + fee_rate_sats_per_byte, + }) +} + +pub fn create_activity(utxo: &str, amount: u32, action: &str) -> Activity { + + let date = Utc::now(); // This will get the current date and time in UTC + let iso_string = date.to_rfc3339(); // Converts the date to an ISO 8601 string + + let activity = Activity { + utxo: utxo.to_string(), + amount, + action: action.to_string(), + date: iso_string + }; + + activity +} + +pub async fn get_statechain_info(statechain_id: &str, client_config: &ClientConfig) -> Result> { + + let path = format!("info/statechain/{}", statechain_id.to_string()); + + let client = client_config.get_reqwest_client()?; + let request = client.get(&format!("{}/{}", client_config.statechain_entity, path)); + + let response = request.send().await?; + + if response.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + + let value = response.text().await?; + + let response: StatechainInfoResponsePayload = serde_json::from_str(value.as_str())?; + + Ok(Some(response)) +} + +pub async fn complete_withdraw(statechain_id: &str, signed_statechain_id: &str, client_config: &ClientConfig) -> Result<()> { + + let endpoint = client_config.statechain_entity.clone(); + let path = "withdraw/complete"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let delete_statechain_payload = WithdrawCompletePayload { + statechain_id: statechain_id.to_string(), + signed_statechain_id: signed_statechain_id.to_string(), + }; + + let response = request.json(&delete_statechain_payload).send().await?; + + if response.status() != 200 { + let response_body = response.text().await?; + return Err(anyhow!(response_body)); + } + + Ok(()) + +} \ No newline at end of file diff --git a/clients/libs/rust/src/wallet.rs b/clients/libs/rust/src/wallet.rs new file mode 100644 index 00000000..6323a2d0 --- /dev/null +++ b/clients/libs/rust/src/wallet.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use electrum_client::ElectrumApi; +use mercurylib::wallet::{generate_mnemonic, Settings, Wallet}; + +use crate::{utils::info_config, client_config::ClientConfig}; + +pub async fn create_wallet( + name: &str, + client_config: &ClientConfig +) -> Result { + let mnemonic = generate_mnemonic()?; + + let server_info = info_config(&client_config).await?; + + let block_header = client_config.electrum_client.block_headers_subscribe_raw()?; + let blockheight = block_header.height as u32; + + let electrum_endpoint = client_config.electrum_server_url.to_string(); + let (electrum_protocol, rest) = electrum_endpoint.split_once("://").expect("Could not find protocol separator"); + + let (electrum_host, electrum_port) = rest.rsplit_once(':').expect("Could not find port separator"); + + let notifications = false; + let tutorials = false; + + let settings = Settings { + network: client_config.network.to_string(), + block_explorerURL: None, + torProxyHost: None, + torProxyPort: None, + torProxyControlPassword: None, + torProxyControlPort: None, + statechainEntityApi: client_config.statechain_entity.to_string(), + torStatechainEntityApi: None, + electrumProtocol: electrum_protocol.to_string(), + electrumHost: electrum_host.to_string(), + electrumPort: electrum_port.to_string(), + electrumType: client_config.electrum_type.to_string(), + notifications, + tutorials, + }; + + let wallet = Wallet { + name: name.to_string(), + mnemonic, + version: String::from("0.1.0"), + state_entity_endpoint: client_config.statechain_entity.to_string(), + electrum_endpoint, + network: client_config.network.to_string(), + blockheight, + initlock: server_info.initlock, + interval: server_info.interval, + tokens: Vec::new(), + activities: Vec::new(), + coins: Vec::new(), + settings, + }; + + // save wallet to database + + Ok(wallet) +} \ No newline at end of file diff --git a/clients/libs/rust/src/withdraw.rs b/clients/libs/rust/src/withdraw.rs new file mode 100644 index 00000000..27fb2b67 --- /dev/null +++ b/clients/libs/rust/src/withdraw.rs @@ -0,0 +1,147 @@ +use crate::{client_config::ClientConfig, sqlite_manager::{get_backup_txs, get_wallet, update_wallet}, transaction::new_transaction, utils::info_config}; +use anyhow::{anyhow, Result}; +use chrono::Utc; +use electrum_client::ElectrumApi; +use mercurylib::wallet::{Activity, CoinStatus}; + + +pub async fn execute(client_config: &ClientConfig, wallet_name: &str, statechain_id: &str, to_address: &str, fee_rate: Option, duplicated_index: Option) -> Result<()>{ + + let mut wallet: mercurylib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let is_address_valid = mercurylib::validate_address(to_address, &wallet.network)?; + + if !is_address_valid { + return Err(anyhow!("Invalid address")); + } + + let backup_txs = get_backup_txs(&client_config.pool, &statechain_id).await?; + + if backup_txs.len() == 0 { + return Err(anyhow!("No backup transaction associated with this statechain ID were found")); + } + + let qt_backup_tx = backup_txs.len() as u32; + + // let new_tx_n = qt_backup_tx + 1; + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + + let coin: Option<&mut mercurylib::wallet::Coin> = match duplicated_index { + Some(index) => wallet.coins + .iter_mut() + .filter(|c| c.statechain_id == Some(statechain_id.to_string()) + && c.status == CoinStatus::DUPLICATED + && c.duplicate_index == index) + .min_by_key(|c| c.locktime), + None => wallet.coins + .iter_mut() + .filter(|c| c.statechain_id == Some(statechain_id.to_string()) + && c.status != CoinStatus::DUPLICATED) + .min_by_key(|c| c.locktime) // Find the one with the lowest locktime + }; + + if coin.is_none() { + + match duplicated_index { + Some(index) => { return Err(anyhow!("No duplicated coins associated with this statechain ID and index {} were found", index)); }, + None => { return Err(anyhow!("No coins associated with this statechain ID were found")) }, + } + } + + let coin = coin.unwrap(); + + if coin.amount.is_none() { + return Err(anyhow::anyhow!("coin.amount is None")); + } + + if coin.status != CoinStatus::CONFIRMED && coin.status != CoinStatus::IN_TRANSFER && coin.status != CoinStatus::DUPLICATED { + return Err(anyhow::anyhow!("Coin status must be CONFIRMED or IN_TRANSFER or DUPLICATED to withdraw it. The current status is {}", coin.status)); + } + + let server_info = info_config(&client_config).await?; + + let fee_rate_sats_per_byte = match fee_rate { + Some(fee_rate) => fee_rate, + None => if server_info.fee_rate_sats_per_byte > client_config.max_fee_rate { + client_config.max_fee_rate + } else { + server_info.fee_rate_sats_per_byte + }, + }; + + let signed_tx = new_transaction( + client_config, + coin, + &to_address, + qt_backup_tx, + true, + None, + &wallet.network, + fee_rate_sats_per_byte, + server_info.initlock, + server_info.interval + ).await?; + + if coin.public_nonce.is_none() { + return Err(anyhow::anyhow!("coin.public_nonce is None")); + } + + if coin.blinding_factor.is_none() { + return Err(anyhow::anyhow!("coin.blinding_factor is None")); + } + + if coin.statechain_id.is_none() { + return Err(anyhow::anyhow!("coin.statechain_id is None")); + } + + /*let backup_tx = BackupTx { + tx_n: new_tx_n, + tx: signed_tx.clone(), + client_public_nonce: coin.public_nonce.as_ref().unwrap().to_string(), + server_public_nonce: coin.server_public_nonce.as_ref().unwrap().to_string(), + client_public_key: coin.user_pubkey.clone(), + server_public_key: coin.server_pubkey.as_ref().unwrap().to_string(), + blinding_factor: coin.blinding_factor.as_ref().unwrap().to_string(), + }; + + backup_txs.push(backup_tx); + + update_backup_txs(&client_config.pool, &coin.statechain_id.as_ref().unwrap(), &backup_txs).await?;*/ + + let tx_bytes = hex::decode(&signed_tx)?; + let txid = client_config.electrum_client.transaction_broadcast_raw(&tx_bytes)?; + + coin.tx_withdraw = Some(txid.to_string()); + coin.withdrawal_address = Some(to_address.to_string()); + coin.status = CoinStatus::WITHDRAWING; + + let date = Utc::now(); // This will get the current date and time in UTC + let iso_string = date.to_rfc3339(); // Converts the date to an ISO 8601 string + + let activity = Activity { + utxo: txid.to_string(), + amount: coin.amount.unwrap(), + action: "Withdraw".to_string(), + date: iso_string + }; + + wallet.activities.push(activity); + + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap().to_string(); + + update_wallet(&client_config.pool, &wallet).await?; + + let is_there_more_duplicated_coins = wallet.coins.iter().any(|coin| { + (coin.status == CoinStatus::DUPLICATED || coin.status == CoinStatus::CONFIRMED) && + duplicated_index.map_or(true, |index| coin.duplicate_index != index) + }); + + if !is_there_more_duplicated_coins { + crate::utils::complete_withdraw(statechain_id, &signed_statechain_id, &client_config).await?; + } + + Ok(()) + +} \ No newline at end of file diff --git a/clients/libs/web/.gitignore b/clients/libs/web/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/clients/libs/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/clients/libs/web/broadcast_backup_tx.js b/clients/libs/web/broadcast_backup_tx.js new file mode 100644 index 00000000..657c4e14 --- /dev/null +++ b/clients/libs/web/broadcast_backup_tx.js @@ -0,0 +1,82 @@ +import axios from 'axios'; +import initWasm from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; +import storageManager from './storage_manager.js'; +import utils from './utils.js'; +import CoinStatus from './coin_enum.js'; + +const execute = async (clientConfig, walletName, statechainId, toAddress, feeRate) => { + + await initWasm(wasmUrl); + + let wallet = storageManager.getItem(walletName); + + let backupTxs = storageManager.getItem(statechainId); + + if (!feeRate) { + const response = await axios.get(`${clientConfig.esploraServer}/api/fee-estimates`); + const feeRateSatsPerByte = response.data[3]; + feeRate = (feeRateSatsPerByte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: feeRateSatsPerByte; + } else { + feeRate = parseFloat(feeRate); + } + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + const backupTx = mercury_wasm.latestBackuptxPaysToUserpubkey(backupTxs, coin, wallet.network); + + if (!backupTx) { + throw new Error(`There is no backup transaction for the statechain id ${statechainId}`); + } + + const CpfpTx = mercury_wasm.createCpfpTx(backupTx, coin, toAddress, feeRate, wallet.network); + + const url = `${clientConfig.esploraServer}/api/tx`; + + let response = await axios.post(url, backupTx.tx, { + headers: { + 'Content-Type': 'text/plain' + } + }); + + let backupTxTxid = response.data; + + response = await axios.post(url, CpfpTx, { + headers: { + 'Content-Type': 'text/plain' + } + }); + + let cpfpTxTxid = response.data; + + coin.tx_cpfp = cpfpTxTxid; + coin.withdrawal_address = toAddress; + coin.status = CoinStatus.WITHDRAWING; + + storageManager.setItem(walletName, wallet, true); + + utils.completeWithdraw(clientConfig, coin.statechain_id, coin.signed_statechain_id); + + return { + backupTx: backupTxTxid, + cpfpTx: cpfpTxTxid + }; +} + +export default { execute }; \ No newline at end of file diff --git a/clients/libs/web/coin_enum.js b/clients/libs/web/coin_enum.js new file mode 100644 index 00000000..f4187a4e --- /dev/null +++ b/clients/libs/web/coin_enum.js @@ -0,0 +1,14 @@ + +const CoinStatus = { + INITIALISED: "INITIALISED", // address generated but no Tx0 yet + IN_MEMPOOL: "IN_MEMPOOL", // Tx0 in mempool + UNCONFIRMED: "UNCONFIRMED", // Tx0 confirmed but coin not available to be sent + CONFIRMED: "CONFIRMED", // Tx0 confirmed and coin available to be sent + IN_TRANSFER: "IN_TRANSFER", // transfer-sender performed, but receiver hasn't completed transfer-receiver + WITHDRAWING: "WITHDRAWING", // withdrawal tx signed and broadcast but not yet confirmed + TRANSFERRED: "TRANSFERRED", // the coin was transferred + WITHDRAWN: "WITHDRAWN", // the coin was withdrawn + DUPLICATED: "DUPLICATED", // the coin was duplicated +}; + +export default CoinStatus; \ No newline at end of file diff --git a/clients/libs/web/coin_status.js b/clients/libs/web/coin_status.js new file mode 100644 index 00000000..521080a3 --- /dev/null +++ b/clients/libs/web/coin_status.js @@ -0,0 +1,260 @@ +import axios from 'axios'; +import initWasm from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; +import storageManager from './storage_manager.js'; +import CoinStatus from './coin_enum.js'; +import deposit from './deposit.js'; +import utils from './utils.js'; + +const checkDeposit = async (clientConfig, coin, walletNetwork) => { + + if (!coin.statechain_id && !coin.utxo_txid && !coin.utxo_vout) { + if (coin.status != CoinStatus.INITIALISED) { + throw new Error(`Coin does not have a statechain ID, a UTXO and the status is not INITIALISED`); + } else { + return null; + } + } + + let response = await axios.get(`${clientConfig.esploraServer}/api/address/${coin.aggregated_address}/utxo`); + + let utxo_list = response.data; + + let utxo = null; + + for (let unspent of utxo_list) { + if (unspent.value === coin.amount) { + utxo = unspent; + break; + } + } + + // No deposit found. No change in the coin status + if (!utxo) { + return null; + } + + // IN_MEMPOOL. there is nothing to do + if (utxo.status.confirmed == false && coin.status == CoinStatus.IN_MEMPOOL) { + return null; + } + + let depositResult = null; + + if (coin.status == CoinStatus.INITIALISED) { + const utxo_txid = utxo.txid; + const utxo_vout = utxo.vout; + + const backup_tx = await deposit.createTx1(clientConfig, coin, walletNetwork, utxo_txid, utxo_vout); + + const activity_utxo = `${utxo_txid}:${utxo_vout}`; + + const activity = utils.createActivity(activity_utxo, coin.amount, "Deposit"); + + depositResult = { + activity, + backup_tx + }; + } + + if (utxo.status.confirmed) { + + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + const block_header = response.data; + const blockheight = parseInt(block_header, 10); + + if (isNaN(blockheight)) { + throw new Error(`Invalid block height: ${block_header}`); + } + + const confirmations = blockheight - parseInt(utxo.status.block_height, 10) + 1; + + const confirmationTarget = clientConfig.confirmationTarget; + + coin.status = CoinStatus.UNCONFIRMED; + + if (confirmations >= confirmationTarget) { + coin.status = CoinStatus.CONFIRMED; + } + + } + + return depositResult; + +} + +const checkTransfer = async (clientConfig, coin) => { + + if (!coin.statechain_id) { + throw new Error(`The coin with the aggregated address ${coin.aggregated_address} does not have a statechain ID`); + } + + let statechainInfo = await utils.getStatechainInfo(clientConfig, coin.statechain_id); + + // if the statechain info is not found, we assume the coin has been transferred + if (!statechainInfo) { + return true; + } + + let enclavePublicKey = statechainInfo.enclave_public_key; + + let isTransferred = !mercury_wasm.isEnclavePubkeyPartOfCoin(coin, enclavePublicKey); + + return isTransferred; +} + +const checkWithdrawal = async (clientConfig, coin) => { + + let txid = undefined; + + if (coin.tx_withdraw) { + txid = coin.tx_withdraw; + } + + if (coin.tx_cpfp) { + if (txid) { + throw new Error(`Coin ${coin.aggregated_address} has both tx_withdraw and tx_cpfp`); + } + txid = coin.tx_cpfp; + } + + if (!txid) { + throw new Error(`Coin ${coin.aggregated_address} has neither tx_withdraw nor tx_cpfp`); + } + + if (!coin.withdrawal_address) { + throw new Error(`Coin ${coin.aggregated_address} has no withdrawal_address`); + } + + console.log(`${clientConfig.esploraServer}/api/address/${coin.withdrawal_address}/utxo`); + + let response = await axios.get(`${clientConfig.esploraServer}/api/address/${coin.withdrawal_address}/utxo`); + + let utxo_list = response.data; + + let utxo = null; + + for (let unspent of utxo_list) { + if (unspent.txid === txid) { + utxo = unspent; + break; + } + } + + if (!utxo) { + // sometimes the transaction has not yet been transmitted to the specified Electrum server + // throw new Error(`There is no UTXO with the address ${coin.withdrawal_address} and the txid ${txid}`); + return false; + } + + if (utxo.status.confirmed) { + + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + const block_header = response.data; + const blockheight = parseInt(block_header, 10); + + if (isNaN(blockheight)) { + throw new Error(`Invalid block height: ${block_header}`); + } + + const confirmations = blockheight - parseInt(utxo.status.block_height, 10) + 1; + + const confirmationTarget = clientConfig.confirmationTarget; + + return confirmations >= confirmationTarget; + } + + return false; +} + +const checkForDuplicated = async (clientConfig, existingCoins) => { + + const duplicatedCoinList = []; + + for (const coin of existingCoins) { + + if (![CoinStatus.IN_MEMPOOL, CoinStatus.UNCONFIRMED, CoinStatus.CONFIRMED].includes(coin.status)) { + continue; + } + + let response = await axios.get(`${clientConfig.esploraServer}/api/address/${coin.aggregated_address}/utxo`); + + let utxoList = response.data; + + let maxDuplicatedIndex = Math.max( + ...existingCoins + .filter(c => c.statechain_id === coin.statechain_id) + .map(coin => coin.duplicate_index) + ); + + for (const unspent of utxoList) { + + const utxoExists = existingCoins.some(coin => + coin.utxo_txid === unspent.txid && + coin.utxo_vout === unspent.vout + ); + + if (utxoExists) { + continue; + } + + maxDuplicatedIndex++; + + const duplicatedCoin = { + ...coin, + status: CoinStatus.DUPLICATED, + utxo_txid: unspent.txid, + utxo_vout: unspent.vout, + amount: unspent.value, + duplicate_index: maxDuplicatedIndex + }; + + duplicatedCoinList.push(duplicatedCoin); + } + } + + return duplicatedCoinList; +} + +const updateCoins = async (clientConfig, walletName) => { + + await initWasm(wasmUrl); + + let wallet = storageManager.getItem(walletName); + + const network = wallet.network; + + for (let i = 0; i < wallet.coins.length; i++) { + let coin = wallet.coins[i]; + + if (coin.status == CoinStatus.INITIALISED || coin.status == CoinStatus.IN_MEMPOOL || coin.status == CoinStatus.UNCONFIRMED) { + + let depositResult = await checkDeposit(clientConfig, coin, network); + + if (depositResult) { + wallet.activities.push(depositResult.activity); + storageManager.setItem(coin.statechain_id, [depositResult.backup_tx], false); + } + } else if (coin.status === CoinStatus.IN_TRANSFER) { + let is_transferred = await checkTransfer(clientConfig, coin); + + if (is_transferred) { + coin.status = CoinStatus.TRANSFERRED; + } + } else if (coin.status == CoinStatus.WITHDRAWING) { + let is_withdrawn = await checkWithdrawal(clientConfig, coin, network); + + if (is_withdrawn) { + coin.status = CoinStatus.WITHDRAWN; + } + } + } + + const duplicatedCoins = await checkForDuplicated(clientConfig, wallet.coins); + wallet.coins = [...wallet.coins, ...duplicatedCoins]; + + storageManager.setItem(wallet.name, wallet, true); +} + +export default { updateCoins } \ No newline at end of file diff --git a/clients/libs/web/deposit.js b/clients/libs/web/deposit.js new file mode 100644 index 00000000..6a836008 --- /dev/null +++ b/clients/libs/web/deposit.js @@ -0,0 +1,154 @@ + +import axios from 'axios'; +import initWasm from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; +import storageManager from './storage_manager.js'; +import transaction from './transaction.js'; +import CoinStatus from './coin_enum.js'; +import utils from './utils.js'; + +const getTokenFromServer = async (clientConfig) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "tokens/token_init"; + const url = statechain_entity_url + '/' + path; + + const response = await axios.get(url); + + if (response.status != 200) { + throw new Error(`Token error: ${response.data}`); + } + + let token = response.data; + + return token; +} + +const getToken = async (clientConfig, walletName) => { + + let wallet = storageManager.getItem(walletName); + + let token = await getTokenFromServer(clientConfig); + + // for dev purposes + token.confirmed = true; + + wallet.tokens.push(token); + + storageManager.setItem(walletName, wallet, true); + + return token; +} + +const init = async (clientConfig, wallet, token_id) => { + + let coin = mercury_wasm.getNewCoin(wallet); + + wallet.coins.push(coin); + + storageManager.setItem(wallet.name, wallet, true); + + let depositMsg1 = mercury_wasm.createDepositMsg1(coin, token_id); + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "deposit/init/pod"; + const url = statechain_entity_url + '/' + path; + + const response = await axios.post(url, depositMsg1); + + if (response.status != 200) { + throw new Error(`Deposit error: ${response.data}`); + } + + let depositMsg1Response = response.data; + + let depositInitResult = mercury_wasm.handleDepositMsg1Response(coin, depositMsg1Response); + + coin.statechain_id = depositInitResult.statechain_id; + coin.signed_statechain_id = depositInitResult.signed_statechain_id; + coin.server_pubkey = depositInitResult.server_pubkey; + + storageManager.setItem(wallet.name, wallet, true); +} + +const getDepositBitcoinAddress = async (clientConfig, walletName, amount) => { + + await initWasm(wasmUrl); + + let wallet = storageManager.getItem(walletName); + + let foundToken = wallet.tokens.find(token => token.confirmed === true && token.spent === false); + + if (!foundToken) { + throw new Error(`There is no token available`); + } + + await init(clientConfig, wallet, foundToken.token_id); + + let coin = wallet.coins[wallet.coins.length - 1]; + + let aggregatedPublicKey = mercury_wasm.createAggregatedAddress(coin, wallet.network); + + coin.amount = parseInt(amount, 10); + coin.aggregated_address = aggregatedPublicKey.aggregate_address; + coin.aggregated_pubkey = aggregatedPublicKey.aggregate_pubkey; + + foundToken.spent = true; + + storageManager.setItem(wallet.name, wallet, true); + + return { "deposit_address": coin.aggregated_address, "statechain_id": coin.statechain_id }; +} + +const createTx1 = async (clientConfig, coin, walletNetwork, tx0Hash, tx0Vout) => { + + if (coin.status !== CoinStatus.INITIALISED) { + throw new Error(`The coin with the aggregated address ${aggregated_address} is not in the INITIALISED state`); + } + + if ('utxo_txid' in coin && 'input_vout' in coin) { + throw new Error(`The coin with the aggregated address ${aggregated_address} has already been deposited`); + } + + coin.utxo_txid = tx0Hash; + coin.utxo_vout = tx0Vout; + coin.status = CoinStatus.IN_MEMPOOL; + + const toAddress = mercury_wasm.getUserBackupAddress(coin, walletNetwork); + const isWithdrawal = false; + const qtBackupTx = 0; + + const serverInfo = await utils.infoConfig(clientConfig); + + let feeRateSatsPerByte = (serverInfo.feeRateSatsPerByte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.feeRateSatsPerByte; + + let signedTx = await transaction.newTransaction( + clientConfig, + coin, + toAddress, + isWithdrawal, + qtBackupTx, + null, + walletNetwork, + feeRateSatsPerByte, + serverInfo.initlock, + serverInfo.interval + ); + + let backupTx = { + tx_n: 1, + tx: signedTx, + client_public_nonce: coin.public_nonce, + server_public_nonce: coin.server_public_nonce, + client_public_key: coin.user_pubkey, + server_public_key: coin.server_pubkey, + blinding_factor: coin.blinding_factor + }; + + coin.locktime = mercury_wasm.getBlockheight(backupTx); + + return backupTx; +} + +export default { getToken, getDepositBitcoinAddress, createTx1 } diff --git a/clients/libs/web/lightning-latch.js b/clients/libs/web/lightning-latch.js new file mode 100644 index 00000000..f032f731 --- /dev/null +++ b/clients/libs/web/lightning-latch.js @@ -0,0 +1,116 @@ +import storageManager from './storage_manager.js'; +import { v4 as uuidv4 } from 'uuid'; +import axios from 'axios'; +import CoinStatus from './coin_enum.js'; + +const createPreImage = async (clientConfig, walletName, statechainId) => { + + const batchId = uuidv4(); + + const wallet = storageManager.getItem(walletName); + + const coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + const coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + if (coin.locktime == null) { + throw new Error("Coin.locktime is null"); + } + + const paymentHashPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + batch_id: batchId + }; + + const paymentHash = await sendPaymentHash(clientConfig, paymentHashPayload); + + return { + hash: paymentHash, + batchId: batchId + }; +} + +const sendPaymentHash = async (clientConfig, paymentHashPayload) => { + + const url = `${clientConfig.statechainEntity}/transfer/paymenthash`; + + let response = await axios.post(url, paymentHashPayload); + + return response?.data?.hash; +} + +const confirmPendingInvoice = async (clientConfig, walletName, statechainId) => { + + const wallet = storageManager.getItem(walletName); + + const coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + const coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + const transferUnlockRequestPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + auth_pub_key: null + }; + + const url = `${clientConfig.statechainEntity}/transfer/unlock`; + + // If there is an http error an exception will be thrown + await axios.post(url, transferUnlockRequestPayload); +} + +const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) => { + + const wallet = storageManager.getItem(walletName); + + const coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + const coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + const transferPreimageRequestPayload = { + statechain_id: statechainId, + auth_sig: coin.signed_statechain_id, + previous_user_auth_key: coin.auth_pubkey, + batch_id: batchId, + }; + + const url = `${clientConfig.statechainEntity}/transfer/transfer_preimage`; + + let response = await axios.post(url, transferPreimageRequestPayload); + + return { preimage: response?.data?.preimage }; +} + +export default { createPreImage, confirmPendingInvoice, retrievePreImage }; diff --git a/clients/libs/web/main.js b/clients/libs/web/main.js new file mode 100644 index 00000000..6d4930f9 --- /dev/null +++ b/clients/libs/web/main.js @@ -0,0 +1,139 @@ +import axios from 'axios'; +import walletManager from './wallet.js'; +import storageManager from './storage_manager.js'; +import deposit from './deposit.js'; +import coin_status from './coin_status.js'; +import withdraw from './withdraw.js'; +import broadcast_backup_tx from './broadcast_backup_tx.js'; +import transfer_send from './transfer_send.js'; +import transfer_receive from './transfer_receive.js'; +import lightningLatch from './lightning-latch.js'; +import { v4 as uuidv4 } from 'uuid'; + +const greet = async () => { + + var esploraServer = "https://mempool.space/signet"; + + const response = await axios.get(`${esploraServer}/api/blocks/tip/height`); + + console.log(response.data) + + console.log('Hello from the web lib!') +} + +const createWallet = async (clientConfig, name) => { + + const wallet = await walletManager.createWallet(clientConfig, name); + + storageManager.setItem(name, wallet, false); + + return wallet; +} + +const newToken = async (clientConfig, walletName) => { + const token = await deposit.getToken(clientConfig, walletName); + return token; +} + +const getDepositBitcoinAddress = async (clientConfig, walletName, amount) => { + const address_info = await deposit.getDepositBitcoinAddress(clientConfig, walletName, amount); + return address_info; +} + +const listStatecoins = async (clientConfig, walletName) => { + + await coin_status.updateCoins(clientConfig, walletName); + + let wallet = storageManager.getItem(walletName); + + let coins = wallet.coins.map(coin => ({ + statechain_id: coin.statechain_id, + amount: coin.amount, + status: coin.status, + adress: coin.address, + locktime: coin.locktime, + duplicate_index: coin.duplicate_index + })); + + return coins; +} + +const withdrawCoin = async (clientConfig, walletName, statechainId, toAddress, feeRate, duplicatedIndex) => { + + await coin_status.updateCoins(clientConfig, walletName); + + return await withdraw.execute(clientConfig, walletName, statechainId, toAddress, feeRate, duplicatedIndex); +} + +const broadcastBackupTransaction = async (clientConfig, walletName, statechainId, toAddress, feeRate) => { + + await coin_status.updateCoins(clientConfig, walletName); + + let txIds = await broadcast_backup_tx.execute(clientConfig, walletName, statechainId, toAddress, feeRate); + + return txIds; +} + +const newTransferAddress = async (walletName, generateBatchId) => { + + const addr = await transfer_receive.newTransferAddress(walletName) + let res = {transfer_receive: addr}; + + if (generateBatchId) { + const batchId = uuidv4(); + res.batch_id = batchId; + } + + return res; +} + +const transferSend = async (clientConfig, walletName, statechainId, toAddress, forceSend, batchId) => { + + await coin_status.updateCoins(clientConfig, walletName); + + return await transfer_send.execute(clientConfig, walletName, statechainId, toAddress, forceSend, batchId); +} + +const transferReceive = async (clientConfig, walletName) => { + + await coin_status.updateCoins(clientConfig, walletName); + + return await transfer_receive.execute(clientConfig, walletName); +} + +const paymentHash = async (clientConfig, walletName, statechainId) => { + + await coin_status.updateCoins(clientConfig, walletName); + + return await lightningLatch.createPreImage(clientConfig, walletName, statechainId); +} + +const confirmPendingInvoice = async (clientConfig, walletName, statechainId) => { + + await coin_status.updateCoins(clientConfig, walletName); + + await lightningLatch.confirmPendingInvoice(clientConfig, walletName, statechainId); +} + +const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) => { + + await coin_status.updateCoins(clientConfig, walletName); + + return await lightningLatch.retrievePreImage(clientConfig, walletName, statechainId, batchId); +} + +export default { + greet, + createWallet, + newToken, + getDepositBitcoinAddress, + listStatecoins, + withdrawCoin, + broadcastBackupTransaction, + newTransferAddress, + transferSend, + transferReceive, + paymentHash, + confirmPendingInvoice, + retrievePreImage +} diff --git a/clients/libs/web/package-lock.json b/clients/libs/web/package-lock.json new file mode 100644 index 00000000..6015c3d6 --- /dev/null +++ b/clients/libs/web/package-lock.json @@ -0,0 +1,934 @@ +{ + "name": "mercuryweblib", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mercuryweblib", + "version": "0.0.0", + "dependencies": { + "axios": "^1.7.2", + "mercury-wasm": "file:../../../wasm/web_pkg/debug", + "uuid": "^10.0.0" + }, + "devDependencies": { + "vite": "^5.2.0" + } + }, + "../../../wasm/web_pkg/debug": { + "name": "mercury-wasm", + "version": "0.1.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/mercury-wasm": { + "resolved": "../../../wasm/web_pkg/debug", + "link": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/clients/libs/web/package.json b/clients/libs/web/package.json new file mode 100644 index 00000000..66d889a9 --- /dev/null +++ b/clients/libs/web/package.json @@ -0,0 +1,20 @@ +{ + "name": "mercuryweblib", + "private": true, + "version": "0.0.0", + "main": "main.js", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.2.0" + }, + "dependencies": { + "axios": "^1.7.2", + "mercury-wasm": "file:../../../wasm/web_pkg/debug", + "uuid": "^10.0.0" + } +} diff --git a/clients/libs/web/storage_manager.js b/clients/libs/web/storage_manager.js new file mode 100644 index 00000000..21502f48 --- /dev/null +++ b/clients/libs/web/storage_manager.js @@ -0,0 +1,24 @@ +const namespace = 'mercury-layer' + +const setItem = (key, jsonData, isUpdate = false) => { + const namespacedKey = `${namespace}:${key}`; + + if (!isUpdate && localStorage.getItem(namespacedKey) !== null) { + throw new Error(`Key "${namespacedKey}" already exists.`); + } + + localStorage.setItem(namespacedKey, JSON.stringify(jsonData)); +} + +const getItem = (key) => { + const namespacedKey = `${namespace}:${key}`; + const jsonData = localStorage.getItem(namespacedKey); + + if (jsonData === null) { + throw new Error(`Key "${namespacedKey}" does not exist.`); + } + + return JSON.parse(jsonData); +} + +export default { setItem, getItem } diff --git a/clients/libs/web/transaction.js b/clients/libs/web/transaction.js new file mode 100644 index 00000000..0a668f20 --- /dev/null +++ b/clients/libs/web/transaction.js @@ -0,0 +1,92 @@ + +import axios from 'axios'; +import initWasm from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; + +const signFirst = async (clientConfig, signFirstRequestPayload) => { + + const url = `${clientConfig.statechainEntity}/sign/first`; + + let response = await axios.post(url, signFirstRequestPayload); + + let serverPubnonceHex = response.data.server_pubnonce; + + if (serverPubnonceHex.startsWith("0x")) { + serverPubnonceHex = serverPubnonceHex.substring(2); + } + + return serverPubnonceHex; +} + +const signSecond = async (clientConfig, partialSigRequest) => { + + const url = `${clientConfig.statechainEntity}/sign/second`; + + let response = await axios.post(url, partialSigRequest); + + let serverPartialSigHex = response.data.partial_sig; + + if (serverPartialSigHex.startsWith("0x")) { + serverPartialSigHex = serverPartialSigHex.substring(2); + } + + return serverPartialSigHex; +} + +const newTransaction = async(clientConfig, coin, toAddress, isWithdrawal, qtBackupTx, block_height, network, feeRateSatsPerByte, initlock, interval) => { + + await initWasm(wasmUrl); + + let coin_nonce = mercury_wasm.createAndCommitNonces(coin); + + let server_pubnonce = await signFirst(clientConfig, coin_nonce.sign_first_request_payload); + + coin.secret_nonce = coin_nonce.secret_nonce; + coin.public_nonce = coin_nonce.public_nonce; + coin.server_public_nonce = server_pubnonce; + coin.blinding_factor = coin_nonce.blinding_factor; + + let new_block_height = 0; + if (block_height == null) { + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + const block_header = response.data; + new_block_height = parseInt(block_header, 10); + + if (isNaN(new_block_height)) { + throw new Error(`Invalid block height: ${block_header}`); + } + } else { + new_block_height = block_height; + } + + let partialSigRequest = mercury_wasm.getPartialSigRequest( + coin, + new_block_height, + initlock, + interval, + feeRateSatsPerByte, + qtBackupTx, + toAddress, + network, + isWithdrawal); + + const serverPartialSigRequest = partialSigRequest.partial_signature_request_payload; + + const serverPartialSig = await signSecond(clientConfig, serverPartialSigRequest); + + const clientPartialSig = partialSigRequest.client_partial_sig; + const msg = partialSigRequest.msg; + const session = partialSigRequest.encoded_session; + const outputPubkey = partialSigRequest.output_pubkey; + + const signature = mercury_wasm.createSignature(msg, clientPartialSig, serverPartialSig, session, outputPubkey); + + const encodedUnsignedTx = partialSigRequest.encoded_unsigned_tx; + + const signed_tx = mercury_wasm.newBackupTransaction(encodedUnsignedTx, signature); + + return signed_tx; +} + +export default { newTransaction } \ No newline at end of file diff --git a/clients/libs/web/transfer_receive.js b/clients/libs/web/transfer_receive.js new file mode 100644 index 00000000..61460d87 --- /dev/null +++ b/clients/libs/web/transfer_receive.js @@ -0,0 +1,343 @@ +import axios from 'axios'; +import initWasm from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; +import storageManager from './storage_manager.js'; +import utils from './utils.js'; +import CoinStatus from './coin_enum.js'; + +const newTransferAddress = async (walletName) => { + + await initWasm(wasmUrl); + + let wallet = storageManager.getItem(walletName); + + let coin = mercury_wasm.getNewCoin(wallet); + + wallet.coins.push(coin); + + storageManager.setItem(walletName, wallet, true); + + return coin.address; +} + +const getMsgAddr = async (clientConfig, auth_pubkey) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/get_msg_addr/"; + const url = statechain_entity_url + '/' + path + auth_pubkey; + + const response = await axios.get(url); + + return response.data.list_enc_transfer_msg; +} + +const getTx0 = async (clientConfig, tx0_txid) => { + + let response = await axios.get(`${clientConfig.esploraServer}/api/tx/${tx0_txid}/hex`); + + return response.data; +} + +const verifyTx0OutputIsUnspentAndConfirmed = async (clientConfig, tx0Outpoint, tx0Hex, wallet_network) => { + + let tx0outputAddress = mercury_wasm.getOutputAddressFromTx0(tx0Outpoint, tx0Hex, wallet_network); + + let response = await axios.get(`${clientConfig.esploraServer}/api/address/${tx0outputAddress}/utxo`); + + let utxo_list = response.data; + + let status = CoinStatus.UNCONFIRMED; + + for (let unspent of utxo_list) { + if (unspent.txid === tx0Outpoint.txid && unspent.vout === tx0Outpoint.vout) { + + if (!unspent.status.confirmed) { + status = CoinStatus.IN_MEMPOOL; + break; + } + + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + const block_header = response.data; + const blockheight = parseInt(block_header, 10); + + if (isNaN(blockheight)) { + throw new Error(`Invalid block height: ${block_header}`); + } + + const confirmations = blockheight - parseInt(unspent.status.block_height, 10) + 1; + + const confirmationTarget = clientConfig.confirmationTarget; + + if (confirmations >= confirmationTarget) { + status = CoinStatus.CONFIRMED; + } + + return { result: true, status }; + } + } + + return { result: false, status }; +} + +const unlockStatecoin = async (clientConfig, statechainId, signedStatechainId, authPubkey) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/unlock"; + const url = statechain_entity_url + '/' + path; + + let transferUnlockRequestPayload = { + statechain_id: statechainId, + auth_sig: signedStatechainId, + auth_pub_key: authPubkey, + }; + + const response = await axios.post(url, transferUnlockRequestPayload); + + if (response.status != 200) { + throw new Error(`Failed to unlock transfer message`); + } +} + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const sendTransferReceiverRequestPayload = async (clientConfig, transferReceiverRequestPayload) => { + + try { + const url = `${clientConfig.statechainEntity}/transfer/receiver`; + const response = await axios.post(url, transferReceiverRequestPayload); + return { + isBatchLocked: false, + serverPubkey: response.data.server_pubkey, + }; + } + catch (error) { + + if (error.response.status == 400) { + if (error.response.data.code == 'ExpiredBatchTimeError') { + throw new Error(`Failed to update transfer message ${error.response.data.message}`); + } else if (error.response.data.code == 'StatecoinBatchLockedError') { + return { + isBatchLocked: true, + serverPubkey: null, + }; + } + } else { + throw new Error(`Failed to update transfer message ${JSON.stringify(error.response.data)}`); + } + } + +} + +const processEncryptedMessage = async (clientConfig, coin, encMessage, network, serverInfo, activities) => { + let clientAuthKey = coin.auth_privkey; + let newUserPubkey = coin.user_pubkey; + + let transferMsg = mercury_wasm.decryptTransferMsg(encMessage, clientAuthKey); + + let tx0Outpoint = mercury_wasm.getTx0Outpoint(transferMsg.backup_transactions); + + const tx0Hex = await getTx0(clientConfig, tx0Outpoint.txid); + + const isTransferSignatureValid = mercury_wasm.verifyTransferSignature(newUserPubkey, tx0Outpoint, transferMsg); + + if (!isTransferSignatureValid) { + throw new Error("Invalid transfer signature"); + } + + const statechainInfo = await utils.getStatechainInfo(clientConfig, transferMsg.statechain_id); + + if (statechainInfo == null) { + throw new Error("Statechain info not found"); + } + + const isTx0OutputPubkeyValid = mercury_wasm.validateTx0OutputPubkey(statechainInfo.enclave_public_key, transferMsg, tx0Outpoint, tx0Hex, network); + + if (!isTx0OutputPubkeyValid) { + throw new Error("Invalid tx0 output pubkey"); + } + + let latestBackupTxPaysToUserPubkey = mercury_wasm.verifyLatestBackupTxPaysToUserPubkey(transferMsg, newUserPubkey, network); + + if (!latestBackupTxPaysToUserPubkey) { + throw new Error("Latest Backup Tx does not pay to the expected public key"); + } + + if (statechainInfo.num_sigs != transferMsg.backup_transactions.length) { + throw new Error("num_sigs is not correct"); + } + + let isTx0OutputUnspent = await verifyTx0OutputIsUnspentAndConfirmed(clientConfig, tx0Outpoint, tx0Hex, network); + if (!isTx0OutputUnspent.result) { + throw new Error("tx0 output is spent or not confirmed"); + } + + const currentFeeRateSatsPerByte = (serverInfo.feeRateSatsPerByte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.feeRateSatsPerByte; + + const feeRateTolerance = clientConfig.feeRateTolerance; + + let isSignatureValid = mercury_wasm.validateSignatureScheme( + transferMsg, + statechainInfo, + tx0Hex, + feeRateTolerance, + currentFeeRateSatsPerByte, + serverInfo.interval + ) + + if (!isSignatureValid.result) { + throw new Error(`Invalid signature scheme, ${isSignatureValid.msg}`); + } + + let previousLockTime = isSignatureValid.previousLockTime; + + const transferReceiverRequestPayload = mercury_wasm.createTransferReceiverRequestPayload(statechainInfo, transferMsg, coin); + + let signedStatechainIdForUnlock = mercury_wasm.signMessage(transferMsg.statechain_id, coin); + + await unlockStatecoin(clientConfig, transferMsg.statechain_id, signedStatechainIdForUnlock, coin.auth_pubkey); + + let serverPublicKeyHex = ""; + + try { + const transferReceiverResult = await sendTransferReceiverRequestPayload(clientConfig, transferReceiverRequestPayload); + + if (transferReceiverResult.isBatchLocked) { + return { + isBatchLocked: true, + statechainId: null, + }; + } + + serverPublicKeyHex = transferReceiverResult.serverPubkey; + } catch (error) { + throw new Error(error); + } + + let newKeyInfo = mercury_wasm.getNewKeyInfo(serverPublicKeyHex, coin, transferMsg.statechain_id, tx0Outpoint, tx0Hex, network); + + coin.server_pubkey = serverPublicKeyHex; + coin.aggregated_pubkey = newKeyInfo.aggregate_pubkey; + coin.aggregated_address = newKeyInfo.aggregate_address; + coin.statechain_id = transferMsg.statechain_id; + coin.signed_statechain_id = newKeyInfo.signed_statechain_id; + coin.amount = newKeyInfo.amount; + coin.utxo_txid = tx0Outpoint.txid; + coin.utxo_vout = tx0Outpoint.vout; + coin.locktime = previousLockTime; + coin.status = isTx0OutputUnspent.status; + + let utxo = `${tx0Outpoint.txid}:${tx0Outpoint.vout}`; + + let activity = { + utxo: utxo, + amount: newKeyInfo.amount, + action: "Receive", + date: new Date().toISOString() + }; + + activities.push(activity); + + storageManager.setItem(transferMsg.statechain_id, transferMsg.backup_transactions, true); + + return { + isBatchLocked: false, + statechainId: transferMsg.statechain_id, + }; +} + +const execute = async (clientConfig, walletName) => { + await initWasm(wasmUrl); + + let wallet = storageManager.getItem(walletName); + + const serverInfo = await utils.infoConfig(clientConfig); + + let uniqueAuthPubkeys = new Set(); + + wallet.coins.forEach(coin => { + uniqueAuthPubkeys.add(coin.auth_pubkey); + }); + + let encMsgsPerAuthPubkey = new Map(); + + for (let authPubkey of uniqueAuthPubkeys) { + try { + let encMessages = await getMsgAddr(clientConfig, authPubkey); + if (encMessages.length === 0) { + console.log("No messages"); + continue; + } + + encMsgsPerAuthPubkey.set(authPubkey, encMessages); + } catch (err) { + console.error(err); + } + } + + let isThereBatchLocked = false; + let receivedStatechainIds = []; + + let tempCoins = [...wallet.coins]; + let tempActivities = [...wallet.activities]; + + for (let [authPubkey, encMessages] of encMsgsPerAuthPubkey.entries()) { + + for (let encMessage of encMessages) { + + let coin = tempCoins.find(coin => coin.auth_pubkey === authPubkey && coin.status === 'INITIALISED'); + + if (coin) { + try { + let messageResult = await processEncryptedMessage(clientConfig, coin, encMessage, wallet.network, serverInfo, tempActivities); + + if (messageResult.isBatchLocked) { + isThereBatchLocked = true; + } + + if (messageResult.statechainId) { + receivedStatechainIds.push(messageResult.statechainId); + } + } catch (error) { + console.error(`Error: ${error}`); + continue; + } + + } else { + try { + let newCoin = await mercury_wasm.duplicateCoinToInitializedState(wallet, authPubkey); + + if (newCoin) { + let messageResult = await processEncryptedMessage(clientConfig, newCoin, encMessage, wallet.network, serverInfo, tempActivities); + + if (messageResult.isBatchLocked) { + isThereBatchLocked = true; + } + + if (messageResult.statechainId) { + tempCoins.push(newCoin); + receivedStatechainIds.push(messageResult.statechainId); + } + } + } catch (error) { + console.error(`Error: ${error}`); + continue; + } + } + } + } + + wallet.coins = [...tempCoins]; + wallet.activities = [...tempActivities]; + + storageManager.setItem(walletName, wallet, true); + + return { + isThereBatchLocked, + receivedStatechainIds + }; +} + +export default { newTransferAddress, execute } \ No newline at end of file diff --git a/clients/libs/web/transfer_send.js b/clients/libs/web/transfer_send.js new file mode 100644 index 00000000..15b91bb4 --- /dev/null +++ b/clients/libs/web/transfer_send.js @@ -0,0 +1,182 @@ +import axios from 'axios'; +import initWasm from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; +import storageManager from './storage_manager.js'; +import utils from './utils.js'; +import CoinStatus from './coin_enum.js'; +import transaction from './transaction.js'; + +const execute = async (clientConfig, walletName, statechainId, toAddress, forceSend, batchId) => { + + await initWasm(wasmUrl); + + let wallet = storageManager.getItem(walletName); + + let backupTxs = storageManager.getItem(statechainId); + + if (backupTxs.length === 0) { + throw new Error(`There is no backup transaction for the statechain id ${statechainId}`); + } + + const new_tx_n = backupTxs.length + 1; + + const isCoinDuplicated = wallet.coins.some(c => + c.statechain_id === statechainId && + c.status === CoinStatus.DUPLICATED + ); + + const areThereDuplicateCoinsWithdrawn = wallet.coins.some(c => + c.statechain_id === statechainId && + (c.status === CoinStatus.WITHDRAWING || c.status === CoinStatus.WITHDRAWN) && + c.duplicate_index > 0 + ); + + if (areThereDuplicateCoinsWithdrawn) { + throw new Error("There have been withdrawals of other coins with this same statechain_id (possibly duplicates). " + + "This transfer cannot be performed because the recipient would reject it due to the difference in signature count. " + + "This coin can be withdrawn, however." + ); + } + + let coinsWithStatechainId = wallet.coins.filter(c => { + return c.statechain_id === statechainId && c.status != CoinStatus.DUPLICATED + }); + + if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) { + throw new Error(`There is no coin for the statechain id ${statechainId}`); + } + + // If the user sends to himself, he will have two coins with same statechain_id + // In this case, we need to find the one with the lowest locktime + // Sort the coins by locktime in ascending order and pick the first one + let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0]; + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`); + } + + if (isCoinDuplicated && !forceSend) { + throw new Error("Coin is duplicated. If you want to proceed, use the command '--force, -f' option. " + + "You will no longer be able to move other duplicate coins with the same statechain_id and this will cause PERMANENT LOSS of these duplicate coin funds."); + } + + if (coin.locktime == null) { + throw new Error("Coin.locktime is null"); + } + + let response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + const block_header = response.data; + const currentBlockheight = parseInt(block_header, 10); + + if (isNaN(currentBlockheight)) { + throw new Error(`Invalid block height: ${block_header}`); + } + + if (currentBlockheight > coin.locktime) { + throw new Error(`The coin is expired. Coin locktime is ${coin.locktime} and current blockheight is ${currentBlockheight}`); + } + + const statechain_id = coin.statechain_id; + const signed_statechain_id = coin.signed_statechain_id; + + const isWithdrawal = false; + const qtBackupTx = backupTxs.length; + + backupTxs.sort((a, b) => a.tx_n - b.tx_n); + + const bkp_tx1 = backupTxs[0]; + + const block_height = mercury_wasm.getBlockheight(bkp_tx1); + + const decodedTransferAddress = mercury_wasm.decodeTransferAddress(toAddress); + const new_auth_pubkey = decodedTransferAddress.auth_pubkey; + + const new_x1 = await get_new_x1(clientConfig, statechain_id, signed_statechain_id, new_auth_pubkey, batchId); + + const serverInfo = await utils.infoConfig(clientConfig); + + let feeRateSatsPerByte = (serverInfo.feeRateSatsPerByte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.feeRateSatsPerByte; + + const signed_tx = await transaction.newTransaction( + clientConfig, + coin, + toAddress, + isWithdrawal, + qtBackupTx, + block_height, + wallet.network, + feeRateSatsPerByte, + serverInfo.initlock, + serverInfo.interval + ); + + const backup_tx = { + tx_n: new_tx_n, + tx: signed_tx, + client_public_nonce: coin.public_nonce, + server_public_nonce: coin.server_public_nonce, + client_public_key: coin.user_pubkey, + server_public_key: coin.server_pubkey, + blinding_factor: coin.blinding_factor + }; + + backupTxs.push(backup_tx); + + const input_txid = coin.utxo_txid; + const input_vout = coin.utxo_vout; + const client_seckey = coin.user_privkey; + const recipient_address = toAddress; + + const transfer_signature = mercury_wasm.createTransferSignature(recipient_address, input_txid, input_vout, client_seckey); + + const transferUpdateMsgRequestPayload = mercury_wasm.createTransferUpdateMsg(new_x1, recipient_address, coin, transfer_signature, backupTxs); + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/update_msg"; + const url = statechain_entity_url + '/' + path; + + response = await axios.post(url, transferUpdateMsgRequestPayload); + + if (!response.data.updated) { + throw new Error(`Transfer update failed`); + } + + storageManager.setItem(coin.statechain_id, backupTxs, true); + + let utxo = `${coin.utxo_txid}:${coin.input_vout}`; + + let activity = { + utxo: utxo, + amount: coin.amount, + action: "Transfer", + date: new Date().toISOString() + }; + + wallet.activities.push(activity); + coin.status = CoinStatus.IN_TRANSFER; + + storageManager.setItem(wallet.name, wallet, true); + + return coin; +}; + +const get_new_x1 = async (clientConfig, statechain_id, signed_statechain_id, new_auth_pubkey, batchId) => { + + const statechain_entity_url = clientConfig.statechainEntity; + const path = "transfer/sender"; + const url = statechain_entity_url + '/' + path; + + let transferSenderRequestPayload = { + statechain_id: statechain_id, + auth_sig: signed_statechain_id, + new_user_auth_key: new_auth_pubkey, + batch_id: batchId, + }; + + const response = await axios.post(url, transferSenderRequestPayload); + + return response.data.x1; +} + +export default { execute }; \ No newline at end of file diff --git a/clients/libs/web/utils.js b/clients/libs/web/utils.js new file mode 100644 index 00000000..01e704ed --- /dev/null +++ b/clients/libs/web/utils.js @@ -0,0 +1,70 @@ +import axios from 'axios'; + +const infoConfig = async (clientConfig) => { + + let response = await axios.get(`${clientConfig.esploraServer}/api/fee-estimates`); + + let feeRateSatsPerByte = response.data[3]; + + if (!feeRateSatsPerByte) { + feeRateSatsPerByte = 1; + } + + // console.log(`feeRateSatsPerByte: ${feeRateSatsPerByte}`) + + /* let response = await axios.get(`${clientConfig.esploraServer}/api/v1/fees/recommended`); + + const feeRateSatsPerByte = parseInt(response.data.fastestFee, 10);*/ + + response = await axios.get(`${clientConfig.statechainEntity}/info/config`); + + return { + initlock: response.data.initlock, + interval: response.data.interval, + feeRateSatsPerByte, + }; + +} + +const createActivity = (utxo, amount, action) => { + + const activity = { + utxo, + amount, + action, + date: new Date().toISOString() + }; + + return activity; +} + +const completeWithdraw = async (clientConfig, statechainId, signedStatechainId) => { + + const url = `${clientConfig.statechainEntity}/withdraw/complete`; + + let deleteStatechainPayload = { + statechain_id: statechainId, + signed_statechain_id: signedStatechainId + }; + + await axios.post(url, deleteStatechainPayload); +} + +const getStatechainInfo = async (clientConfig, statechainId) => { + + const statechainEntityUrl = clientConfig.statechainEntity; + const path = `info/statechain/${statechainId}`; + + try { + let response = await axios.get(statechainEntityUrl + '/' + path); + return response.data; + } catch (error) { + if (error.response.status == 404) { + return null; + } else { + throw error; + } + } +} + +export default { infoConfig, createActivity, completeWithdraw, getStatechainInfo } \ No newline at end of file diff --git a/clients/libs/web/vite.config.js b/clients/libs/web/vite.config.js new file mode 100644 index 00000000..a1a47266 --- /dev/null +++ b/clients/libs/web/vite.config.js @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import { nodePolyfills } from 'vite-plugin-node-polyfills' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + // Could also be a dictionary or array of multiple entry points + entry: resolve(__dirname, 'main.js'), + name: 'mercuryweblib', + // the proper extensions will be added + fileName: 'mercuryweblib', + }, + }, +}) \ No newline at end of file diff --git a/clients/libs/web/wallet.js b/clients/libs/web/wallet.js new file mode 100644 index 00000000..25b443b4 --- /dev/null +++ b/clients/libs/web/wallet.js @@ -0,0 +1,62 @@ +import axios from 'axios'; +import init from 'mercury-wasm'; +import wasmUrl from 'mercury-wasm/mercury_wasm_bg.wasm?url' +import * as mercury_wasm from 'mercury-wasm'; +import utils from './utils.js'; + +const createWallet = async (clientConfig, name) => { + + await init(wasmUrl); + + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + + const blockheight = response.data; + + let serverInfo = await utils.infoConfig(clientConfig); + + const urlElectrumObject = new URL(clientConfig.esploraServer); + + const electrumPort = "80"; + const electrumHost = urlElectrumObject.hostname; + const electrumProtocol = urlElectrumObject.protocol + const electrumType = "esplora"; + + let mnemonic = mercury_wasm.generateMnemonic(); + + let settings = { + network: clientConfig.network, + block_explorerURL: null, + torProxyHost: null, + torProxyPort: null, + torProxyControlPassword: null, + torProxyControlPort: null, + statechainEntityApi: clientConfig.statechainEntity, + torStatechainEntityApi: null, + electrumProtocol, + electrumHost, + electrumPort, + electrumType, + notifications: false, + tutorials: false + } + + let wallet = { + name, + mnemonic, + version: "0.1.0", + state_entity_endpoint: clientConfig.statechainEntity, + electrum_endpoint: clientConfig.esploraServer, + network: clientConfig.network, + blockheight, + initlock: serverInfo.initlock, + interval: serverInfo.interval, + tokens: [], + activities: [], + coins: [], + settings + }; + + return wallet; +} + +export default { createWallet } diff --git a/clients/libs/web/withdraw.js b/clients/libs/web/withdraw.js new file mode 100644 index 00000000..b85c884b --- /dev/null +++ b/clients/libs/web/withdraw.js @@ -0,0 +1,96 @@ +import axios from 'axios'; +import storageManager from './storage_manager.js'; +import utils from './utils.js'; +import CoinStatus from './coin_enum.js'; +import transaction from './transaction.js'; + +const execute = async (clientConfig, walletName, statechainId, toAddress, feeRate, duplicatedIndex) => { + + let wallet = storageManager.getItem(walletName); + + let backupTxs = storageManager.getItem(statechainId); + + if (backupTxs.length === 0) { + throw new Error(`There is no backup transaction for the statechain id ${statechainId}`); + } + + const serverInfo = await utils.infoConfig(clientConfig); + + if (!feeRate) { + feeRate = (serverInfo.feeRateSatsPerByte > clientConfig.maxFeeRate) ? clientConfig.maxFeeRate: serverInfo.feeRateSatsPerByte; + } + + feeRate = parseFloat(feeRate); + + let coin; + + if (!!duplicatedIndex) { + coin = wallet.coins + .filter(c => c.statechain_id === statechainId && c.status === CoinStatus.DUPLICATED && c.duplicate_index === duplicatedIndex) + .reduce((min, current) => (current.locktime < min.locktime) ? current : min, { locktime: Infinity }); + } else { + coin = wallet.coins + .filter(c => c.statechain_id === statechainId && c.status !== CoinStatus.DUPLICATED) + .reduce((min, current) => (current.locktime < min.locktime) ? current : min, { locktime: Infinity }); + } + + if (!coin || coin.locktime === Infinity) { + if (duplicatedIndex !== undefined) { + throw new Error(`No duplicated coins associated with this statechain ID and index ${duplicatedIndex} were found`); + } else { + throw new Error("No coins associated with this statechain ID were found"); + } + } + + if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER && coin.status != CoinStatus.DUPLICATED) { + throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER or DUPLICATED to withdraw it. The current status is ${coin.status}`); + } + + const isWithdrawal = true; + const qtBackupTx = backupTxs.length; + + let signed_tx = await transaction.newTransaction( + clientConfig, + coin, + toAddress, + isWithdrawal, + qtBackupTx, + null, + wallet.network, + feeRate, + serverInfo.initlock, + serverInfo.interval); + + const url = `${clientConfig.esploraServer}/api/tx`; + + let response = await axios.post(url, signed_tx, { + headers: { + 'Content-Type': 'text/plain' + } + }); + + let txid = response.data; + + coin.tx_withdraw = txid; + coin.withdrawal_address = toAddress; + coin.status = CoinStatus.WITHDRAWING; + + const activity = utils.createActivity(txid, coin.amount, "Withdraw"); + + wallet.activities.push(activity); + + storageManager.setItem(wallet.name, wallet, true); + + const isThereMoreDuplicatedCoins = wallet.coins.some(coin => + (coin.status === CoinStatus.DUPLICATED || coin.status === CoinStatus.CONFIRMED) && + (duplicatedIndex === undefined || coin.duplicate_index !== duplicatedIndex) + ); + + if (!isThereMoreDuplicatedCoins) { + utils.completeWithdraw(clientConfig, coin.statechain_id, coin.signed_statechain_id); + } + + return txid; +} + +export default { execute }; \ No newline at end of file diff --git a/clients/tests/rust/.gitignore b/clients/tests/rust/.gitignore new file mode 100644 index 00000000..a0c4a4b8 --- /dev/null +++ b/clients/tests/rust/.gitignore @@ -0,0 +1,3 @@ +/target +wallet.db* +wallet.json diff --git a/clients/tests/rust/Cargo.toml b/clients/tests/rust/Cargo.toml new file mode 100644 index 00000000..227acc87 --- /dev/null +++ b/clients/tests/rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +mercuryrustlib = { path = "../../libs/rust" } +tokio = { version = "1.27.0", features = ["full"] } +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +electrum-client = "0.18.0" +uuid = { version = "1.3.1", features = ["v4", "serde"] } +sha2 = "0.10.8" +hex = "0.4.3" diff --git a/clients/tests/rust/regtest.Settings.toml b/clients/tests/rust/regtest.Settings.toml new file mode 100644 index 00000000..f342e9ed --- /dev/null +++ b/clients/tests/rust/regtest.Settings.toml @@ -0,0 +1,11 @@ +statechain_entity = "http://127.0.0.1:8000" +electrum_server = "tcp://localhost:50001" +electrum_type = "electrs" +network = "regtest" +fee_rate_tolerance = 5 +database_file="wallet.db" +confirmation_target = 2 +#tor_proxy = "socks5h://localhost:9050" +# max fee rate in sat/vbyte +# if the fee rate is higher than this, the transaction will use this max fee rate +max_fee_rate = 1 diff --git a/clients/tests/rust/rust-toolchain.toml b/clients/tests/rust/rust-toolchain.toml new file mode 100644 index 00000000..624eb0ea --- /dev/null +++ b/clients/tests/rust/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.76.0" diff --git a/clients/tests/rust/src/bitcoin_core.rs b/clients/tests/rust/src/bitcoin_core.rs new file mode 100644 index 00000000..eff64c8c --- /dev/null +++ b/clients/tests/rust/src/bitcoin_core.rs @@ -0,0 +1,74 @@ +use std::{process::Command, thread}; +use anyhow::{anyhow, Result, Ok}; + +pub fn get_container_id() -> Result { + // First, get the container ID by running the docker ps command + let output = Command::new("docker") + .arg("ps") + .arg("-qf") + .arg("name=lnd_docker-bitcoind-1") + .output() + .expect("Failed to execute docker ps command"); + + // Convert the output to a string and trim whitespace + let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if container_id.is_empty() { + return Err(anyhow!("No container found with the name lnd_docker-bitcoind-1")); + } + + Ok(container_id) +} + +pub fn execute_bitcoin_command(bitcoin_command: &str) -> Result { + let container_id = get_container_id()?; + + let output = Command::new("docker") + .arg("exec") + .arg(&container_id) + .arg("sh") + .arg("-c") + .arg(bitcoin_command) + .output() + .expect("Failed to execute docker exec command"); + + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).to_string().trim().to_string()); + } else { + return Err(anyhow!("Command execution failed:\n{}", String::from_utf8_lossy(&output.stderr))); + } +} + +pub fn sendtoaddress(amount_in_sats: u32, address: &str) -> Result { + + let amount = amount_in_sats as f64 / 100_000_000.0; + + let bitcoin_command = format!( + "bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress {} {}", address, amount + ); + + execute_bitcoin_command(&bitcoin_command) +} + +pub fn generatetoaddress(num_blocks: u32, address: &str) -> Result { + + let bitcoin_command = format!( + "bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress {} {}", num_blocks, address + ); + + let res = execute_bitcoin_command(&bitcoin_command); + + // The command may take some time to execute + thread::sleep(std::time::Duration::from_secs(1)); + + res +} + +pub fn getnewaddress() -> Result { + + let bitcoin_command = format!( + "bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getnewaddress" + ); + + execute_bitcoin_command(&bitcoin_command) +} \ No newline at end of file diff --git a/clients/tests/rust/src/electrs.rs b/clients/tests/rust/src/electrs.rs new file mode 100644 index 00000000..2969aef6 --- /dev/null +++ b/clients/tests/rust/src/electrs.rs @@ -0,0 +1,27 @@ +use std::str::FromStr; + +use electrum_client::{bitcoin::Address, ElectrumApi, ListUnspentRes}; +use mercuryrustlib::client_config::ClientConfig; +use anyhow::{Result, Ok}; + +pub async fn check_address(client_config: &ClientConfig, address: &str, amount: u32) -> Result { + + let mut utxo: Option = None; + + let address = Address::from_str(address)?.require_network(client_config.network)?; + + let utxo_list = client_config.electrum_client.script_list_unspent(&address.script_pubkey())?; + + for unspent in utxo_list { + if unspent.value == amount as u64 { + utxo = Some(unspent); + break; + } + } + + if utxo.is_none() { + return Ok(false); + } + + Ok(true) +} \ No newline at end of file diff --git a/clients/tests/rust/src/main.rs b/clients/tests/rust/src/main.rs new file mode 100644 index 00000000..e9e4df20 --- /dev/null +++ b/clients/tests/rust/src/main.rs @@ -0,0 +1,25 @@ + +pub mod electrs; +pub mod bitcoin_core; +pub mod ta01_sign_second_not_called; +pub mod ta02_duplicate_deposits; +pub mod tb01_simple_transfer; +pub mod tb02_transfer_address_reuse; +pub mod tb03_simple_atomic_transfer; +pub mod tb04_simple_lightning_latch; +pub mod tm01_sender_double_spends; +use anyhow::{Result, Ok}; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + + tb01_simple_transfer::execute().await?; + tb02_transfer_address_reuse::execute().await?; + tb03_simple_atomic_transfer::execute().await?; + tb04_simple_lightning_latch::execute().await?; + tm01_sender_double_spends::execute().await?; + ta01_sign_second_not_called::execute().await?; + ta02_duplicate_deposits::execute().await?; + + Ok(()) +} diff --git a/clients/tests/rust/src/ta01_sign_second_not_called.rs b/clients/tests/rust/src/ta01_sign_second_not_called.rs new file mode 100644 index 00000000..40e5112d --- /dev/null +++ b/clients/tests/rust/src/ta01_sign_second_not_called.rs @@ -0,0 +1,212 @@ +use std::{env, process::Command, thread, time::Duration}; +use anyhow::{anyhow, Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, create_and_commit_nonces, decode_transfer_address, sqlite_manager::get_wallet, Coin, CoinStatus, SignFirstRequestPayload, SignFirstResponsePayload, TransferSenderRequestPayload, TransferSenderResponsePayload, Wallet}; + +use crate::{bitcoin_core, electrs}; + +/// This function gets the server public nonce from the statechain entity. +pub async fn sign_first(client_config: &ClientConfig, sign_first_request_payload: &SignFirstRequestPayload) -> Result { + + let endpoint = client_config.statechain_entity.clone(); + let path = "sign/first"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let value = request.json(&sign_first_request_payload).send().await?.text().await?; + + let sign_first_response_payload: SignFirstResponsePayload = serde_json::from_str(value.as_str())?; + + let mut server_pubnonce_hex = sign_first_response_payload.server_pubnonce.to_string(); + + if server_pubnonce_hex.starts_with("0x") { + server_pubnonce_hex = server_pubnonce_hex[2..].to_string(); + } + + Ok(server_pubnonce_hex) +} + +pub async fn new_transaction_only_sign_first( + client_config: &ClientConfig, + coin: &mut Coin) -> Result<()> { + + let coin_nonce = create_and_commit_nonces(&coin)?; + coin.secret_nonce = Some(coin_nonce.secret_nonce); + coin.public_nonce = Some(coin_nonce.public_nonce); + coin.blinding_factor = Some(coin_nonce.blinding_factor); + + let _ = sign_first(&client_config, &coin_nonce.sign_first_request_payload).await?; + + Ok(()) +} + +pub async fn execute_only_sign_first( + client_config: &ClientConfig, + recipient_address: &str, + wallet_name: &str, + statechain_id: &str, + batch_id: Option) -> Result<()> +{ + + let mut wallet = get_wallet(&client_config.pool, &wallet_name).await?; + + let coin = wallet.coins + .iter_mut() + .filter(|tx| tx.statechain_id == Some(statechain_id.to_string())) // Filter coins with the specified statechain_id + .min_by_key(|tx| tx.locktime.unwrap_or(u32::MAX)); // Find the one with the lowest locktime + + if coin.is_none() { + return Err(anyhow!("No coins associated with this statechain ID were found")); + } + + let coin = coin.unwrap(); + + let statechain_id = coin.statechain_id.as_ref().unwrap(); + let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap(); + + let (_, _, recipient_auth_pubkey) = decode_transfer_address(recipient_address)?; + let _ = get_new_x1(&client_config, statechain_id, signed_statechain_id, &recipient_auth_pubkey.to_string(), batch_id).await?; + + let _ = new_transaction_only_sign_first(client_config, coin).await?; + + Ok(()) + +} + +async fn get_new_x1(client_config: &ClientConfig, statechain_id: &str, signed_statechain_id: &str, recipient_auth_pubkey: &str, batch_id: Option) -> Result { + + let endpoint = client_config.statechain_entity.clone(); + let path = "transfer/sender"; + + let client = client_config.get_reqwest_client()?; + let request = client.post(&format!("{}/{}", endpoint, path)); + + let transfer_sender_request_payload = TransferSenderRequestPayload { + statechain_id: statechain_id.to_string(), + auth_sig: signed_statechain_id.to_string(), + new_user_auth_key: recipient_auth_pubkey.to_string(), + batch_id, + }; + + let value = match request.json(&transfer_sender_request_payload).send().await { + std::result::Result::Ok(response) => { + + let status = response.status(); + let text = response.text().await.unwrap_or("Unexpected error".to_string()); + + if status.is_success() { + text + } else { + return Err(anyhow::anyhow!(format!("status: {}, error: {}", status, text))); + } + }, + Err(err) => { + return Err(anyhow::anyhow!(format!("status: {}, error: {}", err.status().unwrap(),err.to_string()))); + }, + }; + + let response: TransferSenderResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); + + Ok(response.x1) +} + +async fn ta01(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let amount = 1000; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1 = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + let batch_id = None; + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + execute_only_sign_first( + &client_config, + &wallet2_transfer_adress, + &wallet1.name, + &statechain_id, + batch_id).await?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1 = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let batch_id = None; + + let force_send = false; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_ok()); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; + let received_statechain_ids = transfer_receive_result.received_statechain_ids; + + assert!(received_statechain_ids.contains(&statechain_id.to_string())); + assert!(received_statechain_ids.len() == 1); + + let wallet2 = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + + let new_coin = wallet2.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id.to_string())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1 = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet1.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id.to_string())).unwrap(); + + assert!(new_coin.status == CoinStatus::TRANSFERRED); + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + ta01(&client_config, &wallet1, &wallet2).await?; + + println!("TA01 - 'SignSecond not called' tested successfully"); + + Ok(()) +} diff --git a/clients/tests/rust/src/ta02_duplicate_deposits.rs b/clients/tests/rust/src/ta02_duplicate_deposits.rs new file mode 100644 index 00000000..c179d747 --- /dev/null +++ b/clients/tests/rust/src/ta02_duplicate_deposits.rs @@ -0,0 +1,236 @@ +use std::{env, process::Command, thread, time::Duration}; + +use anyhow::{Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + +async fn withdraw_flow(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let amount = 1000; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let deposit_address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &deposit_address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &deposit_address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let amount = 2000; + + let _ = bitcoin_core::sendtoaddress(amount, &deposit_address)?; + + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &deposit_address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::CONFIRMED); + let duplicated_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::DUPLICATED); + + assert!(new_coin.is_some()); + assert!(duplicated_coin.is_some()); + + let new_coin = new_coin.unwrap(); + let duplicated_coin = duplicated_coin.unwrap(); + + assert!(new_coin.duplicate_index == 0); + assert!(duplicated_coin.duplicate_index == 1); + + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + let batch_id = None; + + let force_send = false; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, statechain_id, force_send, batch_id.clone()).await; + + assert!(result.is_err()); + + let error_msg = result.err().unwrap().to_string(); + + assert!(error_msg == "Coin is duplicated. If you want to proceed, use the command '--force, -f' option. \ + You will no longer be able to move other duplicate coins with the same statechain_id and this will cause PERMANENT LOSS of these duplicate coin funds."); + + let fee_rate = None; + + let result = mercuryrustlib::withdraw::execute(&client_config, &wallet1.name, statechain_id, &core_wallet_address, fee_rate, Some(1)).await; + + assert!(result.is_ok()); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, statechain_id, force_send, batch_id).await; + + assert!(result.is_err()); + + let error_msg = result.err().unwrap().to_string(); + + assert!(error_msg == "There have been withdrawals of other coins with this same statechain_id (possibly duplicates).\ + This transfer cannot be performed because the recipient would reject it due to the difference in signature count.\ + This coin can be withdrawn, however."); + + let result = mercuryrustlib::withdraw::execute(&client_config, &wallet1.name, statechain_id, &core_wallet_address, fee_rate, None).await; + + assert!(result.is_ok()); + + Ok(()) +} + +async fn transfer_flow(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let amount = 1000; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let deposit_address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &deposit_address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &deposit_address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let amount = 2000; + + let _ = bitcoin_core::sendtoaddress(amount, &deposit_address)?; + + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &deposit_address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::CONFIRMED); + let duplicated_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::DUPLICATED); + + assert!(new_coin.is_some()); + assert!(duplicated_coin.is_some()); + + let new_coin = new_coin.unwrap(); + let duplicated_coin = duplicated_coin.unwrap(); + + assert!(new_coin.duplicate_index == 0); + assert!(duplicated_coin.duplicate_index == 1); + + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + let batch_id = None; + + let force_send = true; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, statechain_id, force_send, batch_id.clone()).await; + + assert!(result.is_ok()); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; + let received_statechain_ids = transfer_receive_result.received_statechain_ids; + + assert!(received_statechain_ids.contains(&statechain_id.to_string())); + assert!(received_statechain_ids.len() == 1); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let transferred_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::TRANSFERRED); + let duplicated_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::DUPLICATED); + + assert!(transferred_coin.is_some()); + assert!(duplicated_coin.is_some()); + + let transferred_coin = transferred_coin.unwrap(); + let duplicated_coin = duplicated_coin.unwrap(); + + assert!(transferred_coin.duplicate_index == 0); + assert!(duplicated_coin.duplicate_index == 1); + + let fee_rate = None; + + let result = mercuryrustlib::withdraw::execute(&client_config, &wallet1.name, statechain_id, &core_wallet_address, fee_rate, Some(1)).await; + + assert!(result.is_err()); + + let error_msg = result.err().unwrap().to_string(); + + assert!(error_msg == "Signature does not match authentication key."); + + + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + withdraw_flow(&client_config, &wallet1, &wallet2).await?; + transfer_flow(&client_config, &wallet1, &wallet2).await?; + + println!("TA02 - Test \"Multiple Deposits in the Same Adress\" completed successfully"); + + Ok(()) +} \ No newline at end of file diff --git a/clients/tests/rust/src/tb01_simple_transfer.rs b/clients/tests/rust/src/tb01_simple_transfer.rs new file mode 100644 index 00000000..e3c7d98d --- /dev/null +++ b/clients/tests/rust/src/tb01_simple_transfer.rs @@ -0,0 +1,201 @@ + +use std::{env, process::Command, thread, time::Duration}; + +use anyhow::{anyhow, Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + +async fn try_to_send_unconfirmed_coin(client_config: &ClientConfig, to_address: &str, wallet: &Wallet, statechain_id: &str, current_status: &str) -> Result<()> { + + let batch_id = None; + + let force_send = false; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, to_address, &wallet.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_err()); + + let error = result.err().unwrap(); + + let error_message = format!("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is {}", current_status); + + assert!(error.to_string() == error_message); + + Ok(()) +} + +async fn sucessfully_transfer(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let amount = 1000; + + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + // let mut coins_json = Vec::new(); + + // for coin in wallet.coins.iter() { + // let obj = json!({ + // "coin.user_pubkey": coin.user_pubkey, + // "coin.aggregated_address": coin.aggregated_address.as_ref().unwrap_or(&"".to_string()), + // "coin.address": coin.address, + // "coin.statechain_id": coin.statechain_id.as_ref().unwrap_or(&"".to_string()), + // "coin.amount": coin.amount.unwrap_or(0), + // "coin.status": coin.status, + // "coin.locktime": coin.locktime.unwrap_or(0), + // }); + + // coins_json.push(obj); + // } + + // let coins_json_string = serde_json::to_string_pretty(&coins_json).unwrap(); + // println!("{}", coins_json_string); + + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())); + + if new_coin.is_none() { + return Err(anyhow!("Coin not found in wallet")); + } + + let new_coin = new_coin.unwrap(); + + assert!(new_coin.status == CoinStatus::INITIALISED); + assert!(new_coin.amount == Some(amount)); + assert!(new_coin.statechain_id.is_some()); + + let _ = bitcoin_core::sendtoaddress(amount, &address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::IN_MEMPOOL); + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + try_to_send_unconfirmed_coin(&client_config, &wallet2_transfer_adress, &wallet1, statechain_id, &CoinStatus::IN_MEMPOOL.to_string()).await?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let _ = bitcoin_core::generatetoaddress(1, &core_wallet_address)?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::UNCONFIRMED); + + try_to_send_unconfirmed_coin(&client_config, &wallet2_transfer_adress, &wallet1, statechain_id, &CoinStatus::UNCONFIRMED.to_string()).await?; + + let remaining_blocks = client_config.confirmation_target - 1; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let batch_id = None; + + let force_send = false; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_ok()); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::IN_TRANSFER); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; + let received_statechain_ids = transfer_receive_result.received_statechain_ids; + + assert!(received_statechain_ids.contains(&statechain_id.to_string())); + assert!(received_statechain_ids.len() == 1); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::TRANSFERRED); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet2.name).await?; + let local_wallet_2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + let new_coin = local_wallet_2.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let fee_rate = None; + + let result = mercuryrustlib::withdraw::execute(&client_config, &wallet2.name, &statechain_id, &core_wallet_address, fee_rate, None).await; + + assert!(result.is_ok()); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet2.name).await?; + let local_wallet_2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + let new_coin = local_wallet_2.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::WITHDRAWING); + + let _ = bitcoin_core::generatetoaddress(client_config.confirmation_target, &core_wallet_address)?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet2.name).await?; + let local_wallet_2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + let new_coin = local_wallet_2.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::WITHDRAWN); + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + sucessfully_transfer(&client_config, &wallet1, &wallet2).await?; + + println!("TB01 - Transfer completed successfully"); + + Ok(()) +} diff --git a/clients/tests/rust/src/tb02_transfer_address_reuse.rs b/clients/tests/rust/src/tb02_transfer_address_reuse.rs new file mode 100644 index 00000000..e02ed726 --- /dev/null +++ b/clients/tests/rust/src/tb02_transfer_address_reuse.rs @@ -0,0 +1,174 @@ +use std::{env, process::Command, thread, time::Duration}; +use anyhow::{Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + + +async fn tb02(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let amount = 1000; + + // Create first deposit address + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &address)?; + + // Create second deposit address + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + assert!(wallet1.coins.len() == 2); + + for coin in wallet1.coins.iter() { + assert!(coin.status == CoinStatus::CONFIRMED); + } + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + for coin in wallet1.coins.iter() { + let batch_id = None; + + let force_send = false; + + let statechain_id = coin.statechain_id.as_ref().unwrap(); + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_ok()); + } + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; + let received_statechain_ids = transfer_receive_result.received_statechain_ids; + + let wallet2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + + assert!(wallet2.coins.len() == 2); + + for coin in wallet2.coins.iter() { + assert!(coin.status == CoinStatus::CONFIRMED); + assert!(received_statechain_ids.contains(&coin.statechain_id.as_ref().unwrap().clone())); + } + + for i in 0..wallet2.coins.len() { + for j in (i + 1)..wallet2.coins.len() { + assert_eq!(wallet2.coins[i].user_privkey, wallet2.coins[j].user_privkey, "user_privkey mismatch"); + assert_eq!(wallet2.coins[i].auth_privkey, wallet2.coins[j].auth_privkey, "auth_privkey mismatch"); + assert_eq!(wallet2.coins[i].address, wallet2.coins[j].address, "address mismatch"); + + assert_ne!(wallet2.coins[i].server_pubkey, wallet2.coins[j].server_pubkey, "server_pubkey should differ"); + assert_ne!(wallet2.coins[i].statechain_id, wallet2.coins[j].statechain_id, "statechain_id should differ"); + assert_ne!(wallet2.coins[i].aggregated_address, wallet2.coins[j].aggregated_address, "aggregated_address should differ"); + } + } + + let statechain_id = wallet2.coins[0].statechain_id.as_ref().unwrap().clone(); + + let fee_rate = None; + + let result = mercuryrustlib::withdraw::execute(&client_config, &wallet2.name, &statechain_id, &core_wallet_address, fee_rate, None).await; + + assert!(result.is_ok()); + + let wallet2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + + assert!(wallet2.coins[0].status == CoinStatus::WITHDRAWING); + + let _ = bitcoin_core::generatetoaddress(client_config.confirmation_target, &core_wallet_address)?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet2.name).await?; + let wallet2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + + assert!(wallet2.coins[0].status == CoinStatus::WITHDRAWN); + + let wallet1_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet1.name).await?; + + let batch_id = None; + + let statechain_id = wallet2.coins[1].statechain_id.as_ref().unwrap().clone(); + + let force_send = false; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet1_transfer_adress, &wallet2.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_ok()); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet1.name).await?; + let received_statechain_ids = transfer_receive_result.received_statechain_ids; + + assert!(received_statechain_ids.contains(&statechain_id.to_string())); + assert!(received_statechain_ids.len() == 1); + + let result = mercuryrustlib::withdraw::execute(&client_config, &wallet1.name, &statechain_id, &core_wallet_address, fee_rate, None).await; + + assert!(result.is_ok()); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let withdrawn_coin = wallet1.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id.to_string()) && coin.status == CoinStatus::WITHDRAWING); + let transferred_coin = wallet1.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id.to_string()) && coin.status == CoinStatus::TRANSFERRED); + + assert!(withdrawn_coin.is_some()); + assert!(transferred_coin.is_some()); + + let _ = bitcoin_core::generatetoaddress(client_config.confirmation_target, &core_wallet_address)?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let withdrawn_coin = wallet1.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id.to_string()) && coin.status == CoinStatus::WITHDRAWN); + + assert!(withdrawn_coin.is_some()); + + Ok(()) +} + + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + tb02(&client_config, &wallet1, &wallet2).await?; + + println!("TB02 - Transfer Address Reuse completed successfully"); + + Ok(()) +} diff --git a/clients/tests/rust/src/tb03_simple_atomic_transfer.rs b/clients/tests/rust/src/tb03_simple_atomic_transfer.rs new file mode 100644 index 00000000..4bbfb346 --- /dev/null +++ b/clients/tests/rust/src/tb03_simple_atomic_transfer.rs @@ -0,0 +1,139 @@ +use std::{env, process::Command, thread, time::Duration}; +use anyhow::{Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + +pub async fn tb03(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet, wallet3: &Wallet, wallet4: &Wallet) -> Result<()> { + + let amount = 1000; + + // Create first deposit address + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let wallet1_address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &wallet1_address)?; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let wallet2_address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet2.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &wallet2_address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + let addr1_ok = electrs::check_address(client_config, &wallet1_address, amount).await?; + let addr2_ok = electrs::check_address(client_config, &wallet2_address, amount).await?; + is_tx_indexed = addr1_ok && addr2_ok; + thread::sleep(Duration::from_secs(1)); + } + + let batch_id = Some(uuid::Uuid::new_v4().to_string()); + + let wallet3_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet3.name).await?; + let wallet4_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet4.name).await?; + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(wallet1_address.clone()) && coin.status == CoinStatus::CONFIRMED).unwrap(); + let statechain_id_1 = new_coin.statechain_id.as_ref().unwrap(); + + let force_send = false; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet3_transfer_adress, &wallet1.name, &statechain_id_1, force_send, batch_id.clone()).await; + + assert!(result.is_ok()); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet2.name).await?; + let wallet2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + let new_coin = wallet2.coins.iter().find(|&coin| coin.aggregated_address == Some(wallet2_address.clone()) && coin.status == CoinStatus::CONFIRMED).unwrap(); + let statechain_id_2 = new_coin.statechain_id.as_ref().unwrap(); + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet4_transfer_adress, &wallet2.name, &statechain_id_2, force_send, batch_id).await; + + assert!(result.is_ok()); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet3.name).await?; + + assert!(transfer_receive_result.is_there_batch_locked); + assert!(transfer_receive_result.received_statechain_ids.len() == 0); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet4.name).await?; + + assert!(!transfer_receive_result.is_there_batch_locked); + assert!(transfer_receive_result.received_statechain_ids.len() == 1); + assert!(transfer_receive_result.received_statechain_ids[0] == statechain_id_2.to_string()); + + let wallet4: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet4.name).await?; + let new_coin = wallet4.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id_2.clone())).unwrap(); + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet3.name).await?; + + assert!(!transfer_receive_result.is_there_batch_locked); + assert!(transfer_receive_result.received_statechain_ids.len() == 1); + assert!(transfer_receive_result.received_statechain_ids[0] == statechain_id_1.to_string()); + + let wallet3: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet3.name).await?; + let new_coin = wallet3.coins.iter().find(|&coin| coin.statechain_id == Some(statechain_id_1.clone())).unwrap(); + assert!(new_coin.status == CoinStatus::CONFIRMED); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(wallet1_address.clone())).unwrap(); + assert!(new_coin.status == CoinStatus::TRANSFERRED); + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet2.name).await?; + let wallet2: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet2.name).await?; + let new_coin = wallet2.coins.iter().find(|&coin| coin.aggregated_address == Some(wallet2_address.clone())).unwrap(); + assert!(new_coin.status == CoinStatus::TRANSFERRED); + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + let wallet3 = mercuryrustlib::wallet::create_wallet( + "wallet3", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet3).await?; + + let wallet4 = mercuryrustlib::wallet::create_wallet( + "wallet4", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet4).await?; + + tb03(&client_config, &wallet1, &wallet2, &wallet3, &wallet4).await?; + + println!("TB03 - Simple Atomic Transfer Test completed successfully"); + + Ok(()) +} \ No newline at end of file diff --git a/clients/tests/rust/src/tb04_simple_lightning_latch.rs b/clients/tests/rust/src/tb04_simple_lightning_latch.rs new file mode 100644 index 00000000..c185642f --- /dev/null +++ b/clients/tests/rust/src/tb04_simple_lightning_latch.rs @@ -0,0 +1,101 @@ +use std::{env, process::Command, thread, time::Duration}; +use anyhow::{Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + +use sha2::{Sha256, Digest}; + +pub async fn tb04(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet) -> Result<()> { + + let amount = 1000; + + // Create first deposit address + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let deposit_address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &deposit_address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &deposit_address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(deposit_address.clone()) && coin.status == CoinStatus::CONFIRMED).unwrap(); + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + let response = mercuryrustlib::lightning_latch::create_pre_image(&client_config, &wallet1.name, statechain_id).await?; + + let batch_id = response.batch_id; + let hash = response.hash; + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + let force_send = false; + + mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, statechain_id, force_send, Some(batch_id.clone())).await?; + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; + + assert!(transfer_receive_result.is_there_batch_locked); + assert!(transfer_receive_result.received_statechain_ids.is_empty()); + + mercuryrustlib::lightning_latch::confirm_pending_invoice(&client_config, &wallet1.name, &statechain_id).await?; + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet2.name).await?; + + assert!(!transfer_receive_result.is_there_batch_locked); + assert!(!transfer_receive_result.received_statechain_ids.is_empty()); + + let pre_image = mercuryrustlib::lightning_latch::retrieve_pre_image(&client_config, &wallet1.name, &statechain_id, &batch_id).await?; + + let pre_image_bytes = hex::decode(pre_image)?; + + let mut hasher = Sha256::new(); + hasher.update(pre_image_bytes); + let result = hasher.finalize(); + + let sha256_pre_image = hex::encode(result); + + assert!(sha256_pre_image == hash); + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + tb04(&client_config, &wallet1, &wallet2).await?; + + println!("TB04 - Simple Lightning Latch completed successfully"); + + Ok(()) +} diff --git a/clients/tests/rust/src/tm01_sender_double_spends.rs b/clients/tests/rust/src/tm01_sender_double_spends.rs new file mode 100644 index 00000000..113a3fc2 --- /dev/null +++ b/clients/tests/rust/src/tm01_sender_double_spends.rs @@ -0,0 +1,120 @@ +use std::{env, process::Command, thread, time::Duration}; +use anyhow::{Result, Ok}; +use mercuryrustlib::{client_config::ClientConfig, CoinStatus, Wallet}; + +use crate::{bitcoin_core, electrs}; + +async fn tm01(client_config: &ClientConfig, wallet1: &Wallet, wallet2: &Wallet, wallet3: &Wallet) -> Result<()> { + + let amount = 1000; + + let token_id = mercuryrustlib::deposit::get_token(client_config).await?; + + let address = mercuryrustlib::deposit::get_deposit_bitcoin_address(&client_config, &wallet1.name, &token_id, amount).await?; + + let _ = bitcoin_core::sendtoaddress(amount, &address)?; + + let core_wallet_address = bitcoin_core::getnewaddress()?; + let remaining_blocks = client_config.confirmation_target; + let _ = bitcoin_core::generatetoaddress(remaining_blocks, &core_wallet_address)?; + + // It appears that Electrs takes a few seconds to index the transaction + let mut is_tx_indexed = false; + + while !is_tx_indexed { + is_tx_indexed = electrs::check_address(client_config, &address, amount).await?; + thread::sleep(Duration::from_secs(1)); + } + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + let new_coin = wallet1.coins.iter().find(|&coin| coin.aggregated_address == Some(address.clone())).unwrap(); + + assert!(new_coin.status == CoinStatus::CONFIRMED); + + let batch_id = None; + + let force_send = false; + + let statechain_id = new_coin.statechain_id.as_ref().unwrap(); + + let wallet2_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet2.name).await?; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_ok()); + + let wallet3_transfer_adress = mercuryrustlib::transfer_receiver::new_transfer_address(&client_config, &wallet3.name).await?; + + let batch_id = None; + + // this first "double spend" is legitimate, as it will overwrite the previous transaction + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet3_transfer_adress, &wallet1.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_ok()); + + let transfer_receive_result = mercuryrustlib::transfer_receiver::execute(&client_config, &wallet3.name).await?; + let received_statechain_ids = transfer_receive_result.received_statechain_ids; + + assert!(received_statechain_ids.len() == 1); + + assert!(received_statechain_ids[0] == statechain_id.to_string()); + + let batch_id = None; + + // this second "double spend" is not legitimate, as the statecoin has already been received by wallet3 + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_err()); + + assert!(result.err().unwrap().to_string().contains("Signature does not match authentication key")); + + // If we update wallet1, the error will happen when we try to send the coin to wallet2 + // The step above tested that the sender can double spend the coin, but the server will not accept it + + mercuryrustlib::coin_status::update_coins(&client_config, &wallet1.name).await?; + let wallet1: mercuryrustlib::Wallet = mercuryrustlib::sqlite_manager::get_wallet(&client_config.pool, &wallet1.name).await?; + + let batch_id = None; + + let result = mercuryrustlib::transfer_sender::execute(&client_config, &wallet2_transfer_adress, &wallet1.name, &statechain_id, force_send, batch_id).await; + + assert!(result.is_err()); + + assert!(result.err().unwrap().to_string().contains("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is TRANSFERRED")); + + Ok(()) +} + +pub async fn execute() -> Result<()> { + + let _ = Command::new("rm").arg("wallet.db").arg("wallet.db-shm").arg("wallet.db-wal").output().expect("failed to execute process"); + + env::set_var("ML_NETWORK", "regtest"); + + let client_config = mercuryrustlib::client_config::load().await; + + let wallet1 = mercuryrustlib::wallet::create_wallet( + "wallet1", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet1).await?; + + let wallet2 = mercuryrustlib::wallet::create_wallet( + "wallet2", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet2).await?; + + let wallet3 = mercuryrustlib::wallet::create_wallet( + "wallet3", + &client_config).await?; + + mercuryrustlib::sqlite_manager::insert_wallet(&client_config.pool, &wallet3).await?; + + tm01(&client_config, &wallet1, &wallet2, &wallet3).await?; + + println!("TM01 - Sender Double Spends Test completed successfully"); + + Ok(()) +} diff --git a/clients/tests/web/.gitignore b/clients/tests/web/.gitignore new file mode 100644 index 00000000..c99d4f2f --- /dev/null +++ b/clients/tests/web/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +data_bitcoin_regtest +test/__screenshots__ diff --git a/clients/tests/web/ClientConfig.js b/clients/tests/web/ClientConfig.js new file mode 100644 index 00000000..a0023153 --- /dev/null +++ b/clients/tests/web/ClientConfig.js @@ -0,0 +1,19 @@ +// const clientConfig = { +// esploraServer: "https://mempool.space/signet", +// statechainEntity: "http://127.0.0.1:8000", +// network: "signet", +// feeRateTolerance: 5, +// confirmationTarget: 2, +// maxFee: 1 +// }; + +const clientConfig = { + esploraServer: "http://localhost:8094/regtest", + statechainEntity: "http://127.0.0.1:8000", + network: "regtest", + feeRateTolerance: 5, + confirmationTarget: 2, + maxFee: 1 +}; + +export default clientConfig; \ No newline at end of file diff --git a/clients/tests/web/counter.js b/clients/tests/web/counter.js new file mode 100644 index 00000000..fc0022ae --- /dev/null +++ b/clients/tests/web/counter.js @@ -0,0 +1,39 @@ +import mercuryweblib from 'mercuryweblib'; +import clientConfig from './ClientConfig.js'; +import { tb01ExecuteSimpleTransfer } from './tb01-simple-transfer.js'; +import { tb03SimpleAtomicTransfer } from './tb03-simple-atomic-transfer.js'; +import { tb04SimpleLightningLatch } from './tb04-simple-lightning-latch.js'; +import { tb02DuplicateDeposits } from './ta02-duplicate-deposits.js' + +function setupCounter(element) { + let counter = 0 + const setCounter = (count) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} + +function setupTests(element) { + + const startTests = async () => { + // tb01ExecuteSimpleTransfer(); + // tb03SimpleAtomicTransfer(); + // tb04SimpleLightningLatch(); + tb02DuplicateDeposits(); + } + element.addEventListener('click', () => startTests()) +} + +function setupdClearDepositAddress(element) { + const eraseText = () => { + const textElement = document.getElementById('depositAddressText'); + textElement.textContent = ''; + } + + element.addEventListener('click', () => eraseText()) +} + +export { setupCounter, setupTests, setupdClearDepositAddress } ; + diff --git a/clients/tests/web/index.html b/clients/tests/web/index.html new file mode 100644 index 00000000..3d084381 --- /dev/null +++ b/clients/tests/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/clients/tests/web/javascript.svg b/clients/tests/web/javascript.svg new file mode 100644 index 00000000..f9abb2b7 --- /dev/null +++ b/clients/tests/web/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/clients/tests/web/main-test.js b/clients/tests/web/main-test.js new file mode 100644 index 00000000..ae6635e2 --- /dev/null +++ b/clients/tests/web/main-test.js @@ -0,0 +1,37 @@ +// Import puppeteer +import puppeteer from 'puppeteer'; +// const puppeteer = require('puppeteer'); +import path from 'path'; +import {expect} from 'chai'; +import mercuryweblib from 'mercuryweblib'; + +(async () => { + // Launch the browser + const browser = await puppeteer.launch(); + + // Create a page + const page = await browser.newPage(); + + // Go to your site + // await page.goto('YOUR_SITE'); + + // Evaluate JavaScript + const three = await page.evaluate(async () => { + // assert(true); + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1"); + return wallet1; + }); + +// await page.goto(`file:${path.join(__dirname, 'test/index.html')}`); + +// const result = await page.evaluate(() => { +// return window.mochaResults; +// }); + +// console.log(result); + + console.log(three); + + // Close browser. + await browser.close(); +})(); \ No newline at end of file diff --git a/clients/tests/web/main.js b/clients/tests/web/main.js new file mode 100644 index 00000000..2094596f --- /dev/null +++ b/clients/tests/web/main.js @@ -0,0 +1,32 @@ +import './style.css' +import javascriptLogo from './javascript.svg' +import viteLogo from '/vite.svg' +import { setupCounter, setupTests, setupdClearDepositAddress } from './counter.js' + +document.querySelector('#app').innerHTML = ` +
+ + + + + + +

Hello Vite!

+
+

Deposit Address:

+

+

Status:

+

+ + + +
+

+ Click on the Vite logo to learn more +

+
+` + +setupCounter(document.querySelector('#counter')) +setupTests(document.querySelector('#startTestsButton')) +setupdClearDepositAddress(document.querySelector('#clearAddressButton')) diff --git a/clients/tests/web/package-lock.json b/clients/tests/web/package-lock.json new file mode 100644 index 00000000..64feedd7 --- /dev/null +++ b/clients/tests/web/package-lock.json @@ -0,0 +1,6802 @@ +{ + "name": "web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.0.0", + "dependencies": { + "axios": "^1.7.2", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.19.2", + "install": "^0.13.0", + "mercuryweblib": "file:../../libs/web", + "npm": "^10.8.2", + "puppeteer": "^22.13.0" + }, + "devDependencies": { + "@testing-library/dom": "^10.4.0", + "@vitest/browser": "^2.0.4", + "jsdom": "^24.1.1", + "playwright": "^1.45.3", + "vite": "^5.3.1", + "vitest": "^2.0.4" + } + }, + "../../libs/web": { + "name": "mercuryweblib", + "version": "0.0.0", + "dependencies": { + "axios": "^1.7.2", + "mercury-wasm": "file:../../../wasm/web_pkg/debug", + "uuid": "^10.0.0" + }, + "devDependencies": { + "vite": "^5.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", + "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.5.0" + } + }, + "node_modules/@bundled-es-modules/cookie/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.1.19", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.19.tgz", + "integrity": "sha512-dcLbnxmhx3a72c4fM6CwhydG8rS8TZCXtCYU7kUraA+qU2Ue8gNCiYOxnlhb0H0wbTKL23lUo68fX0iMP8t2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.0.7", + "@inquirer/type": "^1.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.7.tgz", + "integrity": "sha512-wyqnTmlnd9p7cX6tfMlth+/Nx7vV2t/FvtO9VMSi2XjBkNy0MkPr19RSOyP3qrywdlJT+BQbEnXLPqq0wFMw3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.1", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.0.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-spinners": "^2.9.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.5.tgz", + "integrity": "sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.1.tgz", + "integrity": "sha512-m3YgGQlKNS0BM+8AFiJkCsTqHEFCWn6s/Rqye3mYwvqY6LdfUv12eSwbsgNzrYyrLXiy7IrrjDLPysaSBwEfhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz", + "integrity": "sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.2.1", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@puppeteer/browsers": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", + "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", + "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", + "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", + "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", + "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", + "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", + "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", + "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", + "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", + "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", + "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", + "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", + "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", + "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", + "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", + "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", + "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.11.1" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/browser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-2.0.4.tgz", + "integrity": "sha512-QsIkbqPqHsXvgxjCjjgKjuWKmrC0VJgpaDkuEmOy5gTnErhhifWIfp3HpH92K7cscfaIao+RlKv5f8nUMgjfmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "^10.3.1", + "@testing-library/user-event": "^14.5.2", + "@vitest/utils": "2.0.4", + "magic-string": "^0.30.10", + "msw": "^2.3.1", + "sirv": "^2.0.4", + "ws": "^8.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "2.0.4", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.4.tgz", + "integrity": "sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.0.4", + "@vitest/utils": "2.0.4", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.4.tgz", + "integrity": "sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.4.tgz", + "integrity": "sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.0.4", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.4.tgz", + "integrity": "sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.4", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.4.tgz", + "integrity": "sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.4.tgz", + "integrity": "sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.4", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.0.tgz", + "integrity": "sha512-VnxVrpGojAjkiGFN2I+KtsDILFAjiGWVEDizOEnKzEDkT93eQT1cqTfUkqmOyLq33i1q4a1KDYbH+52CUe4Ufw==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dev": true, + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1299070", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", + "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==" + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsdom": { + "version": "24.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", + "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", + "dev": true, + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mercuryweblib": { + "resolved": "../../libs/web", + "link": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/msw": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.4.tgz", + "integrity": "sha512-sHMlwrajgmZSA2l1o7qRSe+azm/I+x9lvVVcOxAzi4vCtH8uVPJk1K5BQYDkzGl+tt0RvM9huEXXdeGrgcc79g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.0", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^3.0.0", + "@mswjs/interceptors": "^0.29.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "chalk": "^4.1.2", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.2", + "path-to-regexp": "^6.2.0", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.9.0", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.7.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", + "integrity": "sha512-x/AIjFIKRllrhcb48dqUNAAZl0ig9+qMuN91RpZo3Cb2+zuibfh+KISl6+kVVyktDz230JKc208UkQwwMqyB+w==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^7.5.4", + "@npmcli/config": "^8.3.4", + "@npmcli/fs": "^3.1.1", + "@npmcli/map-workspaces": "^3.0.6", + "@npmcli/package-json": "^5.2.0", + "@npmcli/promise-spawn": "^7.0.2", + "@npmcli/redact": "^2.0.1", + "@npmcli/run-script": "^8.1.0", + "@sigstore/tuf": "^2.3.4", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^18.0.3", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.2", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^7.0.2", + "ini": "^4.1.3", + "init-package-json": "^6.0.3", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^3.0.2", + "libnpmaccess": "^8.0.6", + "libnpmdiff": "^6.1.4", + "libnpmexec": "^8.1.3", + "libnpmfund": "^5.0.12", + "libnpmhook": "^10.0.5", + "libnpmorg": "^6.0.6", + "libnpmpack": "^7.0.4", + "libnpmpublish": "^9.0.9", + "libnpmsearch": "^7.0.6", + "libnpmteam": "^6.0.5", + "libnpmversion": "^6.0.3", + "make-fetch-happen": "^13.0.1", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^10.1.0", + "nopt": "^7.2.1", + "normalize-package-data": "^6.0.2", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.1.0", + "npm-profile": "^10.0.0", + "npm-registry-fetch": "^17.1.0", + "npm-user-validate": "^2.0.1", + "p-map": "^4.0.0", + "pacote": "^18.0.6", + "parse-conflict-json": "^3.0.1", + "proc-log": "^4.2.0", + "qrcode-terminal": "^0.12.0", + "read": "^3.0.1", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^10.0.6", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.1", + "which": "^4.0.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "7.5.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.1.1", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", + "parse-conflict-json": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.6", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "8.3.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", + "ci-info": "^4.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "5.0.8", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "7.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "5.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "8.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.3.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "1.1.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.3.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.3.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "1.2.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "18.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.4.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^5.0.0", + "npm-package-arg": "^11.0.0", + "promzard": "^1.0.0", + "read": "^3.0.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "8.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "6.1.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.5.4", + "@npmcli/installed-package-contents": "^2.1.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "tar": "^6.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "8.1.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.5.4", + "@npmcli/run-script": "^8.1.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "proc-log": "^4.2.0", + "read": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "5.0.12", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.5.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "10.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^17.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "6.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^17.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "7.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.5.4", + "@npmcli/run-script": "^8.1.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "9.0.9", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.1", + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.2.0", + "semver": "^7.3.7", + "sigstore": "^2.2.0", + "ssri": "^10.0.6" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "7.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^17.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "6.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^17.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.7", + "@npmcli/run-script": "^8.1.0", + "json-parse-even-better-errors": "^3.0.2", + "proc-log": "^4.2.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.2.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "13.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "10.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "6.0.2", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "11.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "9.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "17.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.1", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "18.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "4.2.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^3.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.6.2", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "2.3.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.18", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/playwright": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz", + "integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.45.3" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz", + "integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "22.13.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.13.0.tgz", + "integrity": "sha512-nmICzeHTBtZiu+y4vs0fboe/NKIFwH5W8RZuxmEVAKNfBQg/8u5FEQAvPlWmyVpJoAVM5kXD5PEl3GlK3F9pPA==", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.2.3", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1299070", + "puppeteer-core": "22.13.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "22.13.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.13.0.tgz", + "integrity": "sha512-ZkpRX8nm/S39BnpcCverMzIc6oGWBPOUeOeaWRLKHqiKVCZ1l28HxPTYLitJlDiB16xZATSKpjul+sl+ZEm0HQ==", + "dependencies": { + "@puppeteer/browsers": "2.2.3", + "chromium-bidi": "0.6.0", + "debug": "^4.3.5", + "devtools-protocol": "0.0.1299070", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", + "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.1", + "@rollup/rollup-android-arm64": "4.18.1", + "@rollup/rollup-darwin-arm64": "4.18.1", + "@rollup/rollup-darwin-x64": "4.18.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", + "@rollup/rollup-linux-arm-musleabihf": "4.18.1", + "@rollup/rollup-linux-arm64-gnu": "4.18.1", + "@rollup/rollup-linux-arm64-musl": "4.18.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", + "@rollup/rollup-linux-riscv64-gnu": "4.18.1", + "@rollup/rollup-linux-s390x-gnu": "4.18.1", + "@rollup/rollup-linux-x64-gnu": "4.18.1", + "@rollup/rollup-linux-x64-musl": "4.18.1", + "@rollup/rollup-win32-arm64-msvc": "4.18.1", + "@rollup/rollup-win32-ia32-msvc": "4.18.1", + "@rollup/rollup-win32-x64-msvc": "4.18.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tar-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tinybench": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "dev": true + }, + "node_modules/tinypool": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", + "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/type-fest": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", + "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", + "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.4.tgz", + "integrity": "sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.4.tgz", + "integrity": "sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.4", + "@vitest/pretty-format": "^2.0.4", + "@vitest/runner": "2.0.4", + "@vitest/snapshot": "2.0.4", + "@vitest/spy": "2.0.4", + "@vitest/utils": "2.0.4", + "chai": "^5.1.1", + "debug": "^4.3.5", + "execa": "^8.0.1", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.0.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.0.4", + "@vitest/ui": "2.0.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/clients/tests/web/package.json b/clients/tests/web/package.json new file mode 100644 index 00000000..6e021223 --- /dev/null +++ b/clients/tests/web/package.json @@ -0,0 +1,31 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest", + "test:browser": "vitest --workspace=vitest.workspace.js" + }, + "dependencies": { + "axios": "^1.7.2", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.19.2", + "install": "^0.13.0", + "mercuryweblib": "file:../../libs/web", + "npm": "^10.8.2", + "puppeteer": "^22.13.0" + }, + "devDependencies": { + "@testing-library/dom": "^10.4.0", + "@vitest/browser": "^2.0.4", + "jsdom": "^24.1.1", + "playwright": "^1.45.3", + "vite": "^5.3.1", + "vitest": "^2.0.4" + } +} diff --git a/clients/tests/web/public/vite.svg b/clients/tests/web/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/clients/tests/web/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/clients/tests/web/server-regtest.cjs b/clients/tests/web/server-regtest.cjs new file mode 100644 index 00000000..b68d64e6 --- /dev/null +++ b/clients/tests/web/server-regtest.cjs @@ -0,0 +1,68 @@ +const express = require('express') +const bodyParser = require('body-parser') +const app = express() +const cors = require('cors') +const port = 3000 +const util = require('node:util'); +const exec = util.promisify(require('node:child_process').exec); + +app.use(bodyParser.json()) +app.use(cors()) + +async function getnewaddress() { + const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli getnewaddress`; + const { stdout, stderr } = await exec(generateBlockCommand); + if (stderr) { + throw new Error(`Error: ${stderr}`); + } + return stdout.trim(); +} + +async function generateBlocks(numBlocks) { + const address = await getnewaddress(); + const generateBlockCommand = `docker exec $(docker ps -qf "name=esplora-container") cli generatetoaddress ${numBlocks} ${address}`; + await exec(generateBlockCommand); +} + +async function depositCoin(amount, address) { + const amountInBtc = amount / 100000000; + const sendBitcoinCommand = `docker exec $(docker ps -qf "name=esplora-container") cli sendtoaddress ${address} ${amountInBtc}`; + await exec(sendBitcoinCommand); +} + +app.post('/deposit_amount', async (req, res) => { + const { address, amount } = req.body + + if (typeof address === 'string' && Number.isInteger(amount)) { + // Process the deposit here + console.log(`Deposit received: Address - ${address}, Amount - ${amount}`) + await depositCoin(amount, address) + res.status(200).send({ message: 'Deposit processed successfully' }) + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + +app.post('/generate_blocks', async (req, res) => { + const { blocks } = req.body + + if (Number.isInteger(blocks)) { + // Process the deposit here + console.log(`Generating ${blocks} blocks ...`) + + try { + await generateBlocks(blocks); + } catch (error) { + console.log(error.message); + res.status(500).send({ message: error.message }) + } + + res.status(200).send({ message: 'Blocks generated successfully' }) + } else { + res.status(400).send({ message: 'Invalid input' }) + } +}) + +app.listen(port, () => { + console.log(`Docker server listening on port ${port}`) +}) \ No newline at end of file diff --git a/clients/tests/web/start-test-components.sh b/clients/tests/web/start-test-components.sh new file mode 100755 index 00000000..f530b563 --- /dev/null +++ b/clients/tests/web/start-test-components.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Initialize variables to store process IDs +NODE_PID="" +DOCKER_PID="" + +# Function to kill only our specific processes +cleanup() { + echo "Stopping our processes..." + [ ! -z "$NODE_PID" ] && kill $NODE_PID + [ ! -z "$DOCKER_PID" ] && docker stop esplora-container >/dev/null 2>&1 + wait $NODE_PID 2>/dev/null + echo "Our processes stopped." + exit +} + +# Set up trap to catch SIGINT (Ctrl+C) and SIGTERM +trap cleanup SIGINT SIGTERM + +# Run node server in the background and capture its PID +node server-regtest.cjs & +NODE_PID=$! + +# Run docker command in the background without -it flags +docker run --name esplora-container -p 50001:50001 -p 8094:80 \ + --volume $PWD/data_bitcoin_regtest:/data \ + --env CORS_ALLOW='*' --rm \ + blockstream/esplora \ + /srv/explorer/run.sh bitcoin-regtest explorer & +DOCKER_PID=$! + +# Wait for both processes to finish +wait $NODE_PID $DOCKER_PID diff --git a/clients/tests/web/style.css b/clients/tests/web/style.css new file mode 100644 index 00000000..30aa8141 --- /dev/null +++ b/clients/tests/web/style.css @@ -0,0 +1,96 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/clients/tests/web/sum.js b/clients/tests/web/sum.js new file mode 100644 index 00000000..ebf62fe8 --- /dev/null +++ b/clients/tests/web/sum.js @@ -0,0 +1,16 @@ +export default function() { + + // Convert arguments object to an array + var args = Array.prototype.slice.call(arguments); + + // Throw error if arguments contain non-finite number values + if (!args.every(Number.isFinite)) { + throw new TypeError('sum() expects only numbers.') + } + + // Return the sum of the arguments + return args.reduce(function(a, b) { + return a + b + }, 0); + + } \ No newline at end of file diff --git a/clients/tests/web/ta02-duplicate-deposits.js b/clients/tests/web/ta02-duplicate-deposits.js new file mode 100644 index 00000000..500f0246 --- /dev/null +++ b/clients/tests/web/ta02-duplicate-deposits.js @@ -0,0 +1,278 @@ +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from './ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from './test-utils.js'; + +const withdrawFlow = async (statusMsg) => { + + statusMsg = statusMsg + "
  • Starting withdraw flow test
  • "; + const statusText = document.getElementById('statusText'); + statusText.innerHTML = `
      ${statusMsg}
    `; + + localStorage.removeItem("mercury-layer:wallet1"); + localStorage.removeItem("mercury-layer:wallet2"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const depositAddressText = document.getElementById('depositAddressText'); + depositAddressText.textContent = result.deposit_address; + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + statusMsg = statusMsg + "
  • Deposit in mempool. Waiting for deposit to be confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + statusMsg = statusMsg + "
  • Deposit confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + statusMsg = statusMsg + "
  • Waiting for the duplicated deposit.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + await depositCoin(result.deposit_address, amount); + + while (true) { + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + let duplicatedCoin = coins.find(coin => coin.statechain_id === statechainId && coin.status === CoinStatus.DUPLICATED); + if (duplicatedCoin) { + statusMsg = statusMsg + "
  • Duplicated deposit found.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + break; + } + await new Promise(r => setTimeout(r, 1000)); + } + + depositAddressText.textContent = ""; + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + const newCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.CONFIRMED + ); + + const duplicatedCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.DUPLICATED + ); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + try { + await mercuryweblib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, false, null); + } catch (error) { + console.log(error.message); + statusMsg = statusMsg + "
  • transferSend correctly throws the 'Coin is duplicated' error.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + const toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId, toAddress, null, 1); + + statusMsg = statusMsg + "
  • Duplicated coin withdrawn.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + try { + await mercuryweblib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, false, null); + } catch (error) { + console.log(error.message); + statusMsg = statusMsg + "
  • transferSend correctly throws the 'There have been withdrawals' error.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId, toAddress, null, 0); + + statusMsg = statusMsg + "
  • Original coin withdrawn.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + statusMsg = statusMsg + "
  • Test 'withdraw flow test' completed successfully.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + return statusMsg; + +}; + +const transferFlow = async (statusMsg) => { + + statusMsg = statusMsg + "
  • Starting transfer flow test
  • "; + const statusText = document.getElementById('statusText'); + statusText.innerHTML = `
      ${statusMsg}
    `; + + localStorage.removeItem("mercury-layer:wallet1"); + localStorage.removeItem("mercury-layer:wallet2"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const depositAddressText = document.getElementById('depositAddressText'); + depositAddressText.textContent = result.deposit_address; + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + statusMsg = statusMsg + "
  • Deposit in mempool. Waiting for deposit to be confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + statusMsg = statusMsg + "
  • Deposit confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + statusMsg = statusMsg + "
  • Waiting for the duplicated deposit.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + await depositCoin(result.deposit_address, amount); + + while (true) { + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + let duplicatedCoin = coins.find(coin => coin.statechain_id === statechainId && coin.status === CoinStatus.DUPLICATED); + if (duplicatedCoin) { + statusMsg = statusMsg + "
  • Duplicated deposit found.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + break; + } + await new Promise(r => setTimeout(r, 1000)); + } + + depositAddressText.textContent = ""; + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + const newCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.CONFIRMED + ); + + let duplicatedCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.DUPLICATED + ); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + statusMsg = statusMsg + "
  • Transferring to wallet 2.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, true, null); + + let transferReceiveResult = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + if (transferReceiveResult.receivedStatechainIds.includes(newCoin.statechain_id) && transferReceiveResult.receivedStatechainIds.length === 1) { + statusMsg = statusMsg + "
  • Transfer received correctly.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Transfer not received correctly.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + const transferredCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.TRANSFERRED + ); + + duplicatedCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.DUPLICATED + ); + + if (transferredCoin && transferredCoin.duplicate_index === 0) { + statusMsg = statusMsg + "
  • Transferred coin found.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + if (duplicatedCoin && duplicatedCoin.duplicate_index === 1) { + statusMsg = statusMsg + "
  • Duplicated coin found.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + try { + const withdrawAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId, withdrawAddress, null, 1); + } catch (error) { + console.log(error.message); + } + + statusMsg = statusMsg + "
  • Test 'transfer flow test' completed successfully.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + return statusMsg; +} + +const tb02DuplicateDeposits = async () => { + + let statusMsg = "
  • Starting test 'TA02 - Duplicated Deposits'
  • "; + const statusText = document.getElementById('statusText'); + statusText.innerHTML = `
      ${statusMsg}
    `; + + statusMsg = await withdrawFlow(statusMsg); + + statusMsg = await transferFlow(statusMsg); + + statusMsg = statusMsg + "
  • Test 'TA02 - Duplicated Deposits' completed successfully.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + +} + +export { tb02DuplicateDeposits }; \ No newline at end of file diff --git a/clients/tests/web/tb01-simple-transfer.js b/clients/tests/web/tb01-simple-transfer.js new file mode 100644 index 00000000..ed943fc8 --- /dev/null +++ b/clients/tests/web/tb01-simple-transfer.js @@ -0,0 +1,93 @@ +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from './ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from './test-utils.js'; + +// docker run --name esplora-container -p 50001:50001 -p 8094:80 --volume $PWD/data_bitcoin_regtest:/data --env CORS_ALLOW='*' --rm -i -t blockstream/esplora bash -c "/srv/explorer/run.sh bitcoin-regtest explorer" + +const tb01ExecuteSimpleTransfer = async () => { + + let statusMsg = "
  • Starting test TB01 - Simple Transfer
  • " + const statusText = document.getElementById('statusText'); + statusText.innerHTML = `
      ${statusMsg}
    `; + + localStorage.removeItem("mercury-layer:wallet1"); + localStorage.removeItem("mercury-layer:wallet2"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const depositAddressText = document.getElementById('depositAddressText'); + depositAddressText.textContent = result.deposit_address; + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let areBlocksGenerated = false; + let isDepositConfirmed = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + statusMsg = statusMsg + "
  • Deposit in mempool. Waiting for deposit to be confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + statusMsg = statusMsg + "
  • Deposit confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + depositAddressText.textContent = ""; + + let toAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + statusMsg = statusMsg + "
  • Transferring to wallet 2.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, toAddress.transfer_receive, false, null); + + const transferReceiveResult = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + if (transferReceiveResult.receivedStatechainIds.includes(statechainId) && transferReceiveResult.receivedStatechainIds.length === 1) { + statusMsg = statusMsg + "
  • Transfer received correctly.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Transfer not received correctly.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + statusMsg = statusMsg + "
  • Coin withdrawn.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + statusMsg = statusMsg + "
  • Test TB01 - Simple Transfer completed successfully.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + +} + +export { tb01ExecuteSimpleTransfer }; \ No newline at end of file diff --git a/clients/tests/web/tb03-simple-atomic-transfer.js b/clients/tests/web/tb03-simple-atomic-transfer.js new file mode 100644 index 00000000..2e92205b --- /dev/null +++ b/clients/tests/web/tb03-simple-atomic-transfer.js @@ -0,0 +1,139 @@ +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from './ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from './test-utils.js'; + +const tb03SimpleAtomicTransfer = async () => { + + let statusMsg = "
  • Starting test TB03 - Simple Atomic Transfer
  • " + const statusText = document.getElementById('statusText'); + statusText.innerHTML = `
      ${statusMsg}
    `; + + localStorage.removeItem("mercury-layer:wallet1"); + localStorage.removeItem("mercury-layer:wallet2"); + localStorage.removeItem("mercury-layer:wallet3"); + localStorage.removeItem("mercury-layer:wallet4"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const depositAddressText = document.getElementById('depositAddressText'); + depositAddressText.innerHTML = result1.deposit_address + "
    " + result2.deposit_address; + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 && !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + statusMsg = statusMsg + "
  • Deposit 1 in mempool. Waiting to be confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + statusMsg = statusMsg + "
  • Deposit 1 confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + statusMsg = statusMsg + "
  • Deposit 2 in mempool. Waiting to be confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + statusMsg = statusMsg + "
  • Deposit 2 confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + isDepositConfirmed1 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + depositAddressText.textContent = ""; + + const toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + const toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + statusMsg = statusMsg + "
  • Transferring to wallets 3 and 4 with a batch id.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + if (transferReceive3.isThereBatchLocked) { + statusMsg = statusMsg + "
  • Batch locked for wallet 3.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Batch not locked for wallet 3. Something got wrong.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + + if (transferReceive4.isThereBatchLocked) { + statusMsg = statusMsg + "
  • Batch locked for wallet 4. Something got wrong.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Batch unlocked for wallet 4.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + if (transferReceive3.isThereBatchLocked) { + statusMsg = statusMsg + "
  • Batch locked for wallet 3. Something got wrong.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Batch is now unlocked for wallet 3.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + const toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet3.name, statechainId1, toAddress, null, null); + + await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); + + statusMsg = statusMsg + "
  • Coins withdrawn.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + statusMsg = statusMsg + "
  • Test TB03 - Simple Atomic Transfer completed successfully.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; +} + +export { tb03SimpleAtomicTransfer }; \ No newline at end of file diff --git a/clients/tests/web/tb04-simple-lightning-latch.js b/clients/tests/web/tb04-simple-lightning-latch.js new file mode 100644 index 00000000..95571d63 --- /dev/null +++ b/clients/tests/web/tb04-simple-lightning-latch.js @@ -0,0 +1,150 @@ +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from './ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from './test-utils.js'; + +async function sha256(preimage) { + let buffer; + + if (typeof preimage === 'string') { + // Check if the string is already in hex format + if (/^[0-9A-Fa-f]+$/.test(preimage)) { + // Convert hex string to ArrayBuffer + buffer = new Uint8Array(preimage.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer; + } else { + // Treat as UTF-8 string + buffer = new TextEncoder().encode(preimage); + } + } else if (preimage instanceof ArrayBuffer) { + buffer = preimage; + } else if (ArrayBuffer.isView(preimage)) { + buffer = preimage.buffer; + } else { + throw new Error('Unsupported input type'); + } + + // Calculate the SHA-256 hash + const hashBuffer = await window.crypto.subtle.digest('SHA-256', buffer); + + // Convert the hash to a hex string + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; +} + +const tb04SimpleLightningLatch = async () => { + + let statusMsg = "
  • Starting test TB04 - Simple Lightning Latch
  • " + const statusText = document.getElementById('statusText'); + statusText.innerHTML = `
      ${statusMsg}
    `; + + localStorage.removeItem("mercury-layer:wallet1"); + localStorage.removeItem("mercury-layer:wallet2"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const depositAddressText = document.getElementById('depositAddressText'); + depositAddressText.textContent = result.deposit_address; + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + statusMsg = statusMsg + "
  • Deposit in mempool. Waiting for deposit to be confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + statusMsg = statusMsg + "
  • Deposit confirmed.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + depositAddressText.textContent = ""; + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + console.log(paymentHash); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + if (transferReceive.isThereBatchLocked) { + statusMsg = statusMsg + "
  • Batch locked for wallet 2.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Batch unlocked for wallet 4. Something got wrong.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + statusMsg = statusMsg + "
  • Wallet 1 confirmed the invoice.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + if (transferReceive.isThereBatchLocked) { + statusMsg = statusMsg + "
  • Batch still locked for wallet 2. Something got wrong.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } else { + statusMsg = statusMsg + "
  • Batch now is unlocked for wallet 4.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + } + + let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + statusMsg = statusMsg + "
  • Coins withdrawn.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; + + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + statusMsg = statusMsg + `
  • The pre-image is ${preimage}.
  • `; + statusText.innerHTML = `
      ${statusMsg}
    `; + + let hashPreImage = await sha256(preimage); + + statusMsg = statusMsg + `
  • The hash from server is ${paymentHash.hash}.
  • `; + statusMsg = statusMsg + `
  • The calculated hash is ${hashPreImage}.
  • `; + statusText.innerHTML = `
      ${statusMsg}
    `; + + if (paymentHash.hash === hashPreImage) { + statusMsg = statusMsg + "
  • Hashes match.
  • "; + } + + statusMsg = statusMsg + "
  • Test TB04 - Simple Lightning Latch completed successfully.
  • "; + statusText.innerHTML = `
      ${statusMsg}
    `; +} + +export { tb04SimpleLightningLatch }; diff --git a/clients/tests/web/test-utils.js b/clients/tests/web/test-utils.js new file mode 100644 index 00000000..7ea3f9b8 --- /dev/null +++ b/clients/tests/web/test-utils.js @@ -0,0 +1,40 @@ +import axios from 'axios'; + +const generateBlocks = async (blocks) => { + const body = { + blocks + }; + + const url = `http://localhost:3000/generate_blocks`; + + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to generate new blocks`); + } +} + +const depositCoin = async (address, amount) => { + + const body = { + address, + amount + }; + + const url = `http://localhost:3000/deposit_amount`; + + let response = await axios.post(url, body); + + if (response.status != 200) { + throw new Error(`Failed to unlock transfer message`); + } +} + +const testEsplora = async () => { + + const response = await axios.get(`${clientConfig.esploraServer}/api/blocks/tip/height`); + const block_header = response.data; + console.log(block_header); +} + +export { generateBlocks, depositCoin }; diff --git a/clients/tests/web/test/ta02-duplicate-deposits.test.js b/clients/tests/web/test/ta02-duplicate-deposits.test.js new file mode 100644 index 00000000..f577d4f9 --- /dev/null +++ b/clients/tests/web/test/ta02-duplicate-deposits.test.js @@ -0,0 +1,213 @@ +import { describe, test, expect } from "vitest"; + +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from '../ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from '../test-utils.js'; + +describe('TA02 - Duplicated Deposits', () => { + test("withdraw flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb02_1"); + localStorage.removeItem("mercury-layer:wallet2_tb02_1"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb02_1"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb02_1"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + await depositCoin(result.deposit_address, amount); + + while (true) { + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + let duplicatedCoin = coins.find(coin => coin.statechain_id === statechainId && coin.status === CoinStatus.DUPLICATED); + if (duplicatedCoin) { + break; + } + await new Promise(r => setTimeout(r, 1000)); + } + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + const newCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.CONFIRMED + ); + + const duplicatedCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.DUPLICATED + ); + + expect(newCoin).to.not.be.null; + expect(duplicatedCoin).to.not.be.null; + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + try { + await mercuryweblib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, false, null); + } catch (error) { + expect(error.message).to.include("Coin is duplicated. If you want to proceed, use the command '--force, -f' option. " + + "You will no longer be able to move other duplicate coins with the same statechain_id and this will cause PERMANENT LOSS of these duplicate coin funds."); + } + + const toAddress = "bcrt1qn5948np2j8t68xgpceneg3ua4wcwhwrsqj8scv"; + + let txid = await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId, toAddress, null, 1); + + expect(txid).to.be.string; + + try { + await mercuryweblib.transferSend(clientConfig, wallet1.name, newCoin.statechain_id, transferAddress.transfer_receive, false, null); + } catch (error) { + expect(error.message).to.include("There have been withdrawals of other coins with this same statechain_id (possibly duplicates). " + + "This transfer cannot be performed because the recipient would reject it due to the difference in signature count. This coin can be withdrawn, however."); + } + + txid = await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId, toAddress, null, 0); + + expect(txid).to.be.string; + + }); + + test("transfer flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb02_2"); + localStorage.removeItem("mercury-layer:wallet2_tb02_2"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb02_2"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb02_2"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + await depositCoin(result.deposit_address, amount); + + while (true) { + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + let duplicatedCoin = coins.find(coin => coin.statechain_id === statechainId && coin.status === CoinStatus.DUPLICATED); + if (duplicatedCoin) { + break; + } + await new Promise(r => setTimeout(r, 1000)); + } + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + const newCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.CONFIRMED + ); + + let duplicatedCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.DUPLICATED + ); + + expect(newCoin).to.not.be.null; + expect(duplicatedCoin).to.not.be.null; + + expect(newCoin.duplicate_index).to.equal(0); + expect(duplicatedCoin.duplicate_index).to.equal(1); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + result = await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, true, null); + expect(result).to.have.property('statechain_id'); + + let transferReceiveResult = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + expect(transferReceiveResult.receivedStatechainIds).contains(newCoin.statechain_id); + expect(transferReceiveResult.receivedStatechainIds.length).to.equal(1); + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + const transferredCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.TRANSFERRED + ); + + duplicatedCoin = coins.find(coin => + coin.statechain_id === statechainId && + coin.status == CoinStatus.DUPLICATED + ); + + expect(transferredCoin).to.not.be.null; + expect(transferredCoin.duplicate_index).to.equal(0); + + expect(duplicatedCoin).to.not.be.null; + expect(duplicatedCoin.duplicate_index).to.equal(1); + + try { + const withdrawAddress = "bcrt1qn5948np2j8t68xgpceneg3ua4wcwhwrsqj8scv"; + await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId, withdrawAddress, null, 1); + } catch (error) { + // expect(error.message).to.equal("Signature does not match authentication key."); + expect(error.message).to.equal("Request failed with status code 401"); + } + }); +}, 100000); \ No newline at end of file diff --git a/clients/tests/web/test/tb01-simple-transfer.test.js b/clients/tests/web/test/tb01-simple-transfer.test.js new file mode 100644 index 00000000..50188b74 --- /dev/null +++ b/clients/tests/web/test/tb01-simple-transfer.test.js @@ -0,0 +1,66 @@ +import { describe, test, expect } from "vitest"; + +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from '../ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from '../test-utils.js'; + +describe('TB01 - Simple Transfer', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb01"); + localStorage.removeItem("mercury-layer:wallet2_tb01"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb01"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb01"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let areBlocksGenerated = false; + let isDepositConfirmed = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + let toAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, toAddress.transfer_receive, false, null); + + const transferReceiveResult = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceiveResult.receivedStatechainIds).includes(statechainId); + expect(transferReceiveResult.receivedStatechainIds.length).toEqual(1); + + toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + }); +}, 50000); \ No newline at end of file diff --git a/clients/tests/web/test/tb03-simple-atomic-transfer.test.js b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js new file mode 100644 index 00000000..a93adf0b --- /dev/null +++ b/clients/tests/web/test/tb03-simple-atomic-transfer.test.js @@ -0,0 +1,98 @@ +import { describe, test, expect } from "vitest"; + +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from '../ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from '../test-utils.js'; + +describe('TB03 - Simple Atomic Transfer', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb03"); + localStorage.removeItem("mercury-layer:wallet2_tb03"); + localStorage.removeItem("mercury-layer:wallet3_tb03"); + localStorage.removeItem("mercury-layer:wallet4_tb03"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb03"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb03"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb03"); + let wallet4 = await mercuryweblib.createWallet(clientConfig, "wallet4_tb03"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + await mercuryweblib.newToken(clientConfig, wallet2.name); + + const amount = 1000; + + let result1 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + let result2 = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet2.name, amount); + + const statechainId1 = result1.statechain_id; + const statechainId2 = result2.statechain_id; + + let isDepositInMempool1 = false; + let isDepositConfirmed1 = false; + + let isDepositInMempool2 = false; + let isDepositConfirmed2 = false; + + let areBlocksGenerated = false; + + await depositCoin(result1.deposit_address, amount); + await depositCoin(result2.deposit_address, amount); + + while (!isDepositConfirmed2 || !isDepositConfirmed1) { + + let coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool1) { + isDepositInMempool1 = true; + } else if (coin.statechain_id === statechainId1 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed1 = true; + } + } + + coins = await mercuryweblib.listStatecoins(clientConfig, wallet2.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool2) { + isDepositInMempool2 = true; + } else if (coin.statechain_id === statechainId2 && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed2 = true; + } + } + + if (isDepositInMempool1 && isDepositInMempool2 && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const toAddress3 = await mercuryweblib.newTransferAddress(wallet3.name, true); + const toAddress4 = await mercuryweblib.newTransferAddress(wallet4.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, toAddress3.transfer_receive, false, toAddress3.batch_id); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, toAddress4.transfer_receive, false, toAddress3.batch_id); + + let transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(true); + + const transferReceive4 = await mercuryweblib.transferReceive(clientConfig, wallet4.name); + + expect(transferReceive4.isThereBatchLocked).toBe(false); + + transferReceive3 = await mercuryweblib.transferReceive(clientConfig, wallet3.name); + + expect(transferReceive3.isThereBatchLocked).toBe(false); + + const toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet3.name, statechainId1, toAddress, null, null); + + await mercuryweblib.withdrawCoin(clientConfig, wallet4.name, statechainId2, toAddress, null, null); + + }); +}, 50000); \ No newline at end of file diff --git a/clients/tests/web/test/tb04-simple-lightning-latch.test.js b/clients/tests/web/test/tb04-simple-lightning-latch.test.js new file mode 100644 index 00000000..ef9e030b --- /dev/null +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -0,0 +1,108 @@ +import { describe, test, expect } from "vitest"; + +import CoinStatus from 'mercuryweblib/coin_enum.js'; +import clientConfig from '../ClientConfig.js'; +import mercuryweblib from 'mercuryweblib'; +import { generateBlocks, depositCoin } from '../test-utils.js'; + +async function sha256(preimage) { + let buffer; + + if (typeof preimage === 'string') { + // Check if the string is already in hex format + if (/^[0-9A-Fa-f]+$/.test(preimage)) { + // Convert hex string to ArrayBuffer + buffer = new Uint8Array(preimage.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer; + } else { + // Treat as UTF-8 string + buffer = new TextEncoder().encode(preimage); + } + } else if (preimage instanceof ArrayBuffer) { + buffer = preimage; + } else if (ArrayBuffer.isView(preimage)) { + buffer = preimage.buffer; + } else { + throw new Error('Unsupported input type'); + } + + // Calculate the SHA-256 hash + const hashBuffer = await window.crypto.subtle.digest('SHA-256', buffer); + + // Convert the hash to a hex string + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; +} + +describe('TB04 - Simple Lightning Latch', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb04"); + localStorage.removeItem("mercury-layer:wallet2_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb04"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb04"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(true); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + + let hashPreImage = await sha256(preimage); + + expect(hashPreImage).toEqual(paymentHash.hash); + }); +}, 50000); \ No newline at end of file diff --git a/clients/tests/web/vite.config.js b/clients/tests/web/vite.config.js new file mode 100644 index 00000000..4016ad20 --- /dev/null +++ b/clients/tests/web/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { configDefaults } from 'vitest/config' + +// https://vitejs.dev/config/ +export default defineConfig({ + test: { + browser: { + provider: 'playwright', + enabled: true, + headless: true, + }, + exclude:[ + ...configDefaults.exclude, + 'data_bitcoin_regtest/*' + ] + } +}) \ No newline at end of file diff --git a/clients/tests/web/vitest.workspace.js b/clients/tests/web/vitest.workspace.js new file mode 100644 index 00000000..c72e861d --- /dev/null +++ b/clients/tests/web/vitest.workspace.js @@ -0,0 +1,25 @@ +import { defineWorkspace, configDefaults } from 'vitest/config' + +export default defineWorkspace([ + // This will keep running your existing tests. + // If you don't need to run those in Node.js anymore, + // You can safely remove it from the workspace file + // Or move the browser test configuration to the config file. + 'vite.config.js', + { + extends: 'vite.config.js', + test: { + browser: { + enabled: true, + name: 'chromium', + provider: 'playwright', + // https://playwright.dev + providerOptions: {}, + }, + }, + exclude:[ + ...configDefaults.exclude, + './data_bitcoin_regtest/*' + ], + }, +]) diff --git a/docker-compose-hw.yml b/docker-compose-hw.yml new file mode 100644 index 00000000..e9d9b8ee --- /dev/null +++ b/docker-compose-hw.yml @@ -0,0 +1,77 @@ +version: '3.8' + +services: + aesm: + build: + context: enclave + dockerfile: Dockerfiles/HW/Dockerfile + target: aesm + image: sgx_aesm + devices: + - /dev/isgx + volumes: + - aesmd-socket:/var/run/aesmd + stdin_open: true + tty: true + + enclave-sgx: + build: + context: enclave + dockerfile: Dockerfiles/HW/Dockerfile + target: sample + depends_on: + - aesm + - db_enclave + environment: + - ENCLAVE_DATABASE_URL=postgres://postgres:postgres@db_enclave:5432/enclave + - ENCLAVE_PORT=18080 + - SEED_DIR=./seed + devices: + - /dev/isgx + volumes: + - aesmd-socket:/var/run/aesmd + stdin_open: true + tty: true + + mercury-server: + build: + context: . + dockerfile: server/Dockerfile + depends_on: + - db_server + environment: + - LOCKBOX_URL=http://enclave-sgx:18080 + - BITCOIN_NETWORK=testnet + - LOCKHEIGHT_INIT=1000 + - LH_DECREMENT=10 + - CONNECTION_STRING=postgres://postgres:postgres@db_server:5432/mercury + ports: + - "8000:8000" + + db_enclave: + image: postgres:16.2 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: enclave + ports: + - "5433:5432" + volumes: + - postgres_enclave_data:/var/lib/postgresql/data + + db_server: + image: postgres:16.2 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mercury + ports: + - "5432:5432" + volumes: + - postgres_server_data:/var/lib/postgresql/data + + +volumes: + aesmd-socket: + external: true + postgres_enclave_data: + postgres_server_data: + diff --git a/docker-compose-sim.yml b/docker-compose-sim.yml new file mode 100644 index 00000000..929d4442 --- /dev/null +++ b/docker-compose-sim.yml @@ -0,0 +1,53 @@ +version: '3.8' + +services: + enclave-sgx: + build: + context: enclave + dockerfile: Dockerfiles/SIM/Dockerfile + depends_on: + - db_enclave + environment: + - ENCLAVE_DATABASE_URL=postgres://postgres:postgres@db_enclave:5432/enclave + - ENCLAVE_PORT=18080 + - SEED_DIR=./seed + mercury-server: + build: + context: . + dockerfile: server/Dockerfile + depends_on: + - db_server + environment: + - LOCKBOX_URL=http://enclave-sgx:18080 + - BITCOIN_NETWORK=testnet + - LOCKHEIGHT_INIT=1000 + - LH_DECREMENT=10 + - CONNECTION_STRING=postgres://postgres:postgres@db_server:5432/mercury + ports: + - "8000:8000" + + db_enclave: + image: postgres:16.2 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: enclave + ports: + - "5433:5432" + volumes: + - postgres_enclave_data:/var/lib/postgresql/data + + db_server: + image: postgres:16.2 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mercury + ports: + - "5432:5432" + volumes: + - postgres_server_data:/var/lib/postgresql/data + + +volumes: + postgres_enclave_data: + postgres_server_data: + diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 00000000..7cce6ae7 --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,69 @@ +version: '3.8' + +services: + postgres: + image: postgres + environment: + POSTGRES_PASSWORD: pgpassword + ports: + - "5432:5432" + + bitcoind: + image: lncm/bitcoind:v22.0@sha256:37a1adb29b3abc9f972f0d981f45e41e5fca2e22816a023faa9fdc0084aa4507 + user: root + command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=user:63cf03615adebaa9356591f95b07ec7b$$920588e53f94798bda636acac1b6a77e10e3ee7fe57e414d62f3ee9e580cd27a -fallbackfee=0.0001 + ports: + - "18443:18443" + volumes: + - bitcoin_data:/root/.bitcoin + + electrs: + image: getumbrel/electrs:v0.9.4@sha256:b1590ac6cfb0e5b481c6a7af7f0626d76cbb91c63702b0f5c47e2829e9c37997 + user: root + environment: + ELECTRS_LOG_FILTERS: "INFO" + ELECTRS_NETWORK: "regtest" + ELECTRS_DAEMON_RPC_ADDR: "bitcoind:18443" + ELECTRS_DAEMON_P2P_ADDR: "bitcoind:18444" + ELECTRS_ELECTRUM_RPC_ADDR: "0.0.0.0:50001" + ELECTRS_SERVER_BANNER: "Umbrel Electrs" + ports: + - "50001:50001" + volumes: + - electrs-data:/data + - bitcoin_data:/root/.bitcoin + depends_on: + - bitcoind + + enclave-sgx: + build: + context: enclave + dockerfile: Dockerfiles/SIM/Dockerfile + depends_on: + - postgres + environment: + ENCLAVE_DATABASE_URL: postgres://postgres:pgpassword@postgres:5432/postgres + ENCLAVE_PORT: 18080 + SEED_DIR: ./seed + ports: + - "18080:18080" + + mercury: + build: + context: . + dockerfile: ./server/Dockerfile + environment: + NETWORK: regtest + LOCKHEIGHT_INIT: 1100 + LH_DECREMENT: 1 + CONNECTION_STRING: postgres://postgres:pgpassword@postgres:5432/postgres + BATCH_TIMEOUT: 20 + ENCLAVES: '[{"url": "http://mercurylayer_enclave-sgx_1:18080", "allow_deposit": true}]' + ports: + - "8000:8000" + depends_on: + - postgres + +volumes: + bitcoin_data: + electrs-data: \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 67198814..997e3fd9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -48,7 +48,7 @@ The design changes required are then as follows: ### Blind two-party Schnorr signatures -Mercury layer by default employs Schnorr signatures via Taproot addresses for statecoins. To enable a signature to be generated over a shared public key (by the two private key shares of the server and owner) a blinded variant of the Musig2 protocol is employed. In this variant, one of the co-signing parties (the server) does not learn of 1) The full shared public key or 2) The final signature generated. An ephemeral key commitment scheme is employed to ensure Wagner based attacks are not possible. +Mercury layer by default employs Schnorr signatures via Taproot addresses for statecoins. To enable a signature to be generated over a shared public key (by the two private key shares of the server and owner) a blinded variant of the Musig2 protocol is employed. In this variant, one of the co-signing parties (the server) does not learn of 1) The full shared public key or 2) The final signature generated. ### Client transaction verification diff --git a/docs/atomic_transfer.md b/docs/atomic_transfer.md new file mode 100644 index 00000000..ea73940d --- /dev/null +++ b/docs/atomic_transfer.md @@ -0,0 +1,85 @@ +# Atomic transfer protocols + +## Multiple statecoins atomic transfer + +The purpose of the atomic transfer protocol is to enable two (or more) coin transfers to be completed in a way that they all complete or none of them do. This effectively means that the transfer-receiver function and key update is only completed if both receivers have the correct information to proceed. Once each receiver has verified that the transfer message they have received is valid, then the other parties can be allowed to complete transfer-receiver. + +To enable this there needs to be a mechanism to prevent the transfer-receiver being completed before the other parties have verified and confirmed their transfer messages. If all parties verify, then transfer-receiver can proceed, otherwise the key shares are not updated and ownership of the coins remains with the inital owner. + +The process is performed by each party in the atomic transfer supplying a `batch_id` in the transfer-sender message. The use of this (as opposed to leaving this null) is that the statecoin is put into a special *locked* state for a configured timeout period after the first `batch_id` is submitted. During this locked status two things are prevented 1) The sender cannot perform another `/transfer/sender` operation on the same coin (i.e. to send to a different address) and 2) `/transfer/receiver` cannot be performed until all coins with the same `batch_id` are *unlocked* by all the owners unlocking the coins with a new `/transfer/unlock` function. At the end of the timeout (if all coins with a specified `batch_id` have not been unlocked), the `/transfer/receiver` function will still be blocked (i.e. return error) but the original owner can repeat `/transfer/sender` again with a null `batch_id` to regain control of the coin. + +### Protocol + +The `statechain_transfer` DB table can have 3 additional columns: `batch_id` (string), `batch-time` (integer) and `locked` (boolen). By default `batch_id`, `batch-time` are null and `locked` is `false`. The server is configured with `batch-timeout` parameter in seconds (in practice this will be some number of minutes). This is set in the server Settings. + +The atomic transfer will then proceed as follows: + +1. The two or more parties that want to engage in an atomic transfer cooperate and share a `batch_id` (this is just a random UUID generated by one of the participants and shared with the other). One party can generate it and share with the others. +2. The parties each generate a `ml1` address that they want the coin they will recieve paid to and send to the sender. +3. Each party performs `/transfer/sender {statechain_id, batch_id, auth_sig, new_user_auth_key}` with the `batch_id` paying to the receiving party `new_user_auth_key` and then create and upload the transfer message. +4. If the `batch_id` is set in a `/transfer/sender` call (and not null) the server adds this to the `batch_id` DB column for the specified `statechain_id` and updates the `locked` status to `true`. +5. The server then checks all other statecoins to see if the same `batch_id` is used for any other coin in the table. +6. If there are no other coins with this `batch_id`, then it adds the current time (unix epoch time in seconds) to the `batch-time` column. +7. If there are any other coin(s) with this same `batch_id` then the `batch-time` of those coin(s) is copied to the specified coin `batch-time` column. + +> This means that all coins where `/transfer/sender` have the same `batch_id` will have the same `batch-time` set. + +> With this logic, it does not mater which order the coins call `/transfer/sender`, the first call with a specific `batch_id` will set the initial `batch-time` for the atomic transfer. + +When `/transfer/sender` is called for any coin, if `batch-time` + `batch-timeout` (as read from the DB table) is *greater* than or equal to the current time (i.e. the coin is still within the timeout period) then it will return an error (`ERROR: statecoin batch locked`). + +When `/transfer/sender` is called for any coin, if `batch-time` + `batch-timeout` (as read from the DB table) is *less* the current time (i.e. the timeout period has ended) the server will check the DB table for any other coins have the same `batch_id`. For each coin that shares the same`batch_id`, if all `locked` values are false, then the transfer-sender returns with an error (`ERROR: batch transfer completed`) (in this case all participants have verified the transfer message and the atomic transfer must complete - all parties must perform transfer-receiver). +If all `locked` values are NOT false `/transfer/sender` can be called again with a null or different `batch_id`. If called with null `batch_id` then `batch-time` is set to null and `locked` to false. + +8. Each participant in the atomic transfer then polls `/transfer/get_msg_addr` with their receive address within the timeout period. If they receive a transfer message, they fully verify as usual. If the verification passes, then the participant calls a function `/transfer/unlock {statechain_id, auth_sig}` which changes the `locked` status of the coin (`statechain_id`) in the DB table to `false`. +If the `batch-time` + `batch-timeout` is less than the current time, then the `/transfer/unlock` function should not unlock and instead return an error. + +9. Each participant then calls `transfer-reciever`. If `/transfer/receiver` is called on any coin and `batch_id` and `batch-time` are set (not null) and `locked` is false, then the server checks if any other coins in the DB table have the same `batch_id`. For alls coin that share the same `batch_id`, if all `locked` are false, then the function proceeds with the key update, otherwise it returns with an error (`ERROR: coin in locked state`). +If `/transfer/receiver` is called on any coin and `batch_id` and `batch-time` are set (not null) and `locked` is true, then this function should return an error. + +The outcomes of this protocol: + +Any coins where `/transfer/sender` is called with a specific `batch-id` within the `batch-time` of the first one to call, will be added to the batch transfer. + +Then, if all parties coins progress to call `/transfer/unlock` within the `batch-time` then `/transfer/receiver` can be performed for all coins by each party. + +If any single participant doesn't call `/transfer/unlock` within the `batch-time` then no-one can call `/transfer/receiver` but after the `batch-time` expiry each owner can call `/transfer/sender` again to recover the coin (i.e. send to to their own address with null `batch_id` and then calling transfer-receiver). + +## Lightning Latch atomic transfer + +A *latch* transfer enables a statecoin to be transferred on condition of the sucessful payment of a Lightning network invoice. This is a protocol that enforces atomicity of a statecoin transfer and lightning payment - it can be used for the sale of a statecoin UTXO for an arbtrary amount of bitcoin in a private and non-custodial way without counterparty risk. + +The latch transfer protocol uses the method of *Hodl invoices* (https://guide.bolt.fun/guide/invoices/hodl-invoice) where funds are locked until the payment hash pre-image is revealed. A hodl invoice can be resolved in one of two ways: + +1. The payment is complete when the recipient releases the preimage (to the payment route). +2. The payment is canceled if the recipient does not release the preimage and the invoice expires. + +Using this mechanism, a lightning network payment can be made but only completed if some condition is met - specifically if a specified statecoin transfer is made and verified. In this case the statechain entity (i.e. the mercury server) can both generate and release the payment hash pre-image on successful completion of the statecoin transfer in order to enforce atomicity. In this case the mercury server is trusted, but never has custody of any assets and never learns anything about the payment. + +### Protocol + +The statecoin server database will have 4 additional values for each `statechain_id`: `batch_id` (string), `batch-time` (integer), `pre-image` (string), `locked_1` (boolen) and `locked_2` (boolen). By default `batch_id`, `batch-time` are null and `locked` is `false`. The server is configured with `batch-timeout` parameter in seconds (in practice this will be some number of minutes to enable both parties to complete the trade). + +The transfer will then proceed as follows: + +1. There are two parties: party 1 has a statecoin they want to sell and party 2 wants to receivie it and pay an agreed price via LN. +2. Party 1 generates a `batch_id` (this is just a random UUID) and shares with party 2. +3. Party 1 calls `/transfer/paymenthash` with the `batch_id` and authenticated `statechain_id` for the coin. The server generates a secret `preimage` and stores it in the statecoin table with a new row and `batch-time` set as current time and `locked_1` to `true` and `locked_2` to `true`, and returns the `paymenthash` to Party 1. +4. Party 2 generates a `sc` addresses that they want the UTXO sent to. This is sent to Party 1. +5. Party 1 generates an invoice with the `paymenthash` and sends to Party 2. Party 2 can verify that the server has generated the pre-image to this by calling `/transfer/paymenthash` with the `batch_id` provided by Party 1. +6. Party 1 performs `/transfer/sender {statechain_id, batch_id, auth_sig, new_user_auth_key}` with the `batch_id` paying to the party 1 `new_user_auth_key` (derived from their address) and then creates and uploads the encrypted transfer message. +7. Party 2 makes the LN payment for the invoice, but cannot complete this as they don't know the pre-image. +8. Party 1 verifies the payment is pending and calls `/transfer/unlock` with `statechain_id` and signed with their `auth_key`. If time is within `batch-time` + `batch-timeout` this sets the `locked_1` value to `false`. +9. Party 2 retrives and verifies the transfer message for the UTXO with their `new_user_auth_key` and calls `/transfer/unlock` with `statechain_id` and signed with their `new_user_auth_key`. If time is within `batch-time` + `batch-timeout` this sets the `locked_2` value to `false`. +10. Party 1 then calls `/transfer/preimage` with `statechain_id` and signed with their `new_user_auth_key`. If `locked_1` and `locked_2` and both false, then the `preimage` is returned and the LN payment can be finalised. +11. Party 2 completes `/transfer/receiver`. If `locked_1` and `locked_2` and both false then the keyupdate is completed. If either is `true` then `/transfer/receiver` will return an error. + +If both of the `/transfer/unlock` operations are not completed within the `batch-timeout` then both: 1. the LN invoice will timeout and cancel and 2. Party 1 performs another `/transfer/sender` and `/transfer/receiver` operation returning control of the coin. + +The outcomes of this protocol: + +All participants call `/transfer/unlock` within the `batch-time` then `/transfer/receiver` can be performed and the pre-image revealed. + +Any participant doesn't call `/transfer/unlock` within the `batch-time` (because they have failed to verify either that the transfer message is incorrect, or the LN payment was not made correctly) then no-one can call `/transfer/receiver` but party 1 can call `/transfer/sender` again to recover the coin, and the LN payment will fail. + +Calling `/transfer/sender` on the coin a second time will be blocked until `batch-time` expires. diff --git a/docs/client_guide.md b/docs/client_guide.md new file mode 100644 index 00000000..b4a8b6eb --- /dev/null +++ b/docs/client_guide.md @@ -0,0 +1,340 @@ +# Mercury layer client guide + +The Mercury layer client has implimentations for rust, kotlin and nodejs/web. These instructions describe setting up and using the standalone rust client to send testnet coins on mercurylayer. + +This guide describes the use of the client implimentations to deposit, transfer and withdraw bitcoins via a test enviroment. The clients communicate with the bitcoin network via an Electrum server, where the electrum server type and URL need to be specified in the client config. + +For the purposes of the examples in this guide, a version of the bitcoin signet is used to simulate the main bitcoin network. + + +## Rust client + +The rust client acts as a standalone command line client for mercury layer that can demonstrate the main wallet operations. The wallet state is saved in a local `sqlite` database. + +Initially, Rust must be installed. To do this on a Linux or MacOS system, simply run: + +``` +curl https://sh.rustup.rs -sSf | sh +``` + +To use the standalone client app, first clone the mercurylayer repository: + +``` +git clone https://github.com/commerceblock/mercurylayer.git +``` + +Then switch to the `dev` branch: + +``` +cd mercurylayer +git checkout dev +``` + +Then change directory to the standalone client: + +``` +cd /clients/apps/rust +``` + +In this directory is the `Settings.toml` file for the client. This needs to be edited (using any text editor, e.g. vi or nano) to set the mercury server endpoint and Electrum server URL. + +For the purposes of demonstration, use the following `Settings.toml`: + +``` +statechain_entity = "http://test.mercurylayer.com:8500" +electrum_server = "tcp://mutinynet.com:50001" +electrum_type = "electrs" +network = "signet" +fee_rate_tolerance = 5 +database_file="wallet.db" +confirmation_target = 2 +max_fee_rate = 1 +``` + +The test mercury key server URL is: `http://test.mercurylayer.com:8500` + +> Note that this is a test server. It is free to use, but there is no guarantee of persistence or security. Use only for testnet or signet coins. + +Once the settings are complete, initialise a wallet using: + +``` +cargo run create-wallet +``` + +Running this with name `test_wallet` should return an object like: + +``` +Wallet created: Wallet { name: "test_wallet", mnemonic: "core parade visual doctor region beach approve slim refuse drip rigid develop", version: "0.1.0", state_entity_endpoint: "http://test.mercurylayer.com:8500", electrum_endpoint: "tcp://mutinynet.com:50001", network: "signet", blockheight: 2820795, initlock: 25920, interval: 6, tokens: [], activities: [], coins: [] } +``` + +In order to use the mercury layer key server, an access token is required in order to create a shared key. For the test server, tokens can be generated as follows from the command line: + +``` +cargo run new-token +``` + +Which will return a `token_id` e.g. `e7d8c299-7121-48b3-bc78-8c70bf4c9691` + +With a valid token, it is then possible to generate a shared key and address to deposit testnet bitcoin into: + +``` +cargo run new-deposit-address +``` + +For example the following will initialise a coin for an amount of 200000 sats. + +``` +cargo run new-deposit-address test_wallet e7d8c299-7121-48b3-bc78-8c70bf4c9691 200000 +``` + +This command will then generate the shared key with the server and display the address, e.g.: + +``` +{ + "address": "tb1p0rl49a3ddl44y9y9wsazp29ez3rgp97dqljsdrpqmanklppx4qgscahkex" +} +``` + +Copy the address and pay the specified amount to it. For the mutinynet signet network, this payment can be made directly via the faucet: [faucet.mutinynet.com](https://faucet.mutinynet.com/) + +Once paid, run: + +``` +cargo run list-statecoins +``` + +To list the coins in the wallet and complete the deposit process. + +This will return e.g.: + +``` +[ + { + "coin.address": "tml1qqpruc0ty5zl4z25juqaq5d8vrgl6d28tktl7cuh8ygus47vgy0d9kgr2weknc939dy8sdlxy8w8ffwaczvzu844rcs33cwvdgerh2ytdwtsvajeh6", + "coin.aggregated_address": "tb1p0rl49a3ddl44y9y9wsazp29ez3rgp97dqljsdrpqmanklppx4qgscahkex", + "coin.amount": 100000, + "coin.locktime": 1189682, + "coin.statechain_id": "99e79535933642758735620e621e8e9b", + "coin.status": "UNCONFIRMED", + "coin.user_pubkey": "023e61eb2505fa89549701d051a760d1fd35475d97ff63973911c857cc411ed2d9" + } +] +``` + +Once the `coin.status` shows `CONFIRMED`, the coin can be transferred. + +To generate a statecoin receieve address, run: + +``` +cargo run new-transfer-address +``` + +Which will generate a mercury layer address to recieve a coin to. e.g.: + +``` +{ + "new_transfer_address:": "tml1qqp7m5tc9auxgwka84tez9vksky2lsp5uftyhhduakt75j8yq46wh3crh26fwzxw2akc43d9m0pvuhmuq57tcdtw7pz96zfsz8ck3d3jjf3q04re26" +} +``` + +This address can then be used to send a specified coin (with `statechain_id`) coin to a specified statechain address: + +``` +cargo run transfer-send +``` + +For example, with the confirmed coin above (`coin.statechain_id: "99e79535933642758735620e621e8e9b"`) + +``` +cargo run transfer-send test_wallet tml1qqp7m5tc9auxgwka84tez9vksky2lsp5uftyhhduakt75j8yq46wh3crh26fwzxw2akc43d9m0pvuhmuq57tcdtw7pz96zfsz8ck3d3jjf3q04re26 99e79535933642758735620e621e8e9b +``` + +This will return: + +``` +{ + "Transfer": "sent" +} +``` + +The recevier then runs the following command to finalise the receipt of the coin and update the key share: + +``` +cargo run transfer-receive +``` + +The coin will then appear in the coins list: + +``` +cargo run list-statecoins +``` + +To send the coin back to a standard on-chain bitcoin address, simply run: + +``` +cargo run withdraw +``` + +For the demo test coins, these can be sent back to an address generated by the faucet: [faucet.mutinynet.com](https://faucet.mutinynet.com/) + +In case the coin expires and the mercury server is unavailable, the backup transaction can be used as follows: + +``` +cargo run broadcast-backup-transaction +``` + +## Atomic swap process + +To perform an atomic swap of two separate mercurylayer coins, first create four separate wallets: + +``` +cargo run create-wallet wallet1 +cargo run create-wallet wallet2 +cargo run create-wallet wallet3 +cargo run create-wallet wallet4 +``` + +Generate a new deposit token: + +``` +cargo run new-token +``` + +``` +cargo run cargo run new-deposit-address wallet1 100000 +``` + +Deposit signet bitcoin to the generated address, then: + +``` +cargo run list-statecoins wallet1 +``` + +Repeat this for `wallet2`: + +``` +cargo run new-token +``` + +``` +cargo run cargo run new-deposit-address wallet2 100000 +``` + +Deposit signet bitcoin to the generated address, then: + +``` +cargo run list-statecoins wallet2 +``` + +Generate a transfer address for `wallet3` with the `-b` flag (this generates a `bacth_id` for the atomic transfer. + +``` +cargo run new-transfer-address wallet3 -b +``` + +Returning, e.g.: + +``` +New transfer address: tml1qqprzt7lf9p2zcjwflh6cywsce2v9mkl6ns8gucahd8mhlcq95cxj6gz5mrr3m9yjekk75pshe2reylpud0utvtj88g86qvzng2d20rrs36qmlg40j # (example) +Batch Id: 2e9ac416-24e1-4c29-b4d7-5f7d35b062f8 # (example) +``` + +Generate a transfer address for `wallet4`: + +``` +cargo run new-transfer-address w4 +``` + +Returning, e.g.: + +``` +New transfer address: tml1qqplxlutx9asvxycyd9yqf9vf0gk2j4cnxzqgt7csz09mt8hdttq25grrsr32ma25el9c760je3w3m305r0hskul3cjguwlx39jrsnr94ljswq87hv # (example) +``` + +Then `wallet1` send to `wallet3` supplying the `bacth_id`: + +``` +cargo run transfer-send +``` + +E.g.: + +``` +cargo run transfer-send wallet1 b8a2a15d508743609600bb93b7d75c9e tml1qqprzt7lf9p2zcjwflh6cywsce2v9mkl6ns8gucahd8mhlcq95cxj6gz5mrr3m9yjekk75pshe2reylpud0utvtj88g86qvzng2d20rrs36qmlg40j 2e9ac416-24e1-4c29-b4d7-5f7d35b062f8 +``` + +Then `wallet2` send their coin to `wallet4` supplying the same `bacth_id`: + +``` +cargo run transfer-send wallet2 c3da091f8c3f46438c4f51aa6f7de2e4 tml1qqplxlutx9asvxycyd9yqf9vf0gk2j4cnxzqgt7csz09mt8hdttq25grrsr32ma25el9c760je3w3m305r0hskul3cjguwlx39jrsnr94ljswq87hv 2e9ac416-24e1-4c29-b4d7-5f7d35b062f8 +``` + +Both receiving wallets then need to perform `transfer-recieve` within the timeout period specified by the server. + +``` +cargo run transfer-receive wallet3 +``` + +This will show the message `Statecoin batch still locked. Waiting until expiration or unlock.`, until the other coin is also succesfully recieved: + +``` +cargo run transfer-receive wallet4 +``` + +## NodeJS client + +To use the NodeJS client app, first clone the mercurylayer repository: + +``` +git clone https://github.com/commerceblock/mercurylayer.git +``` + +Then switch to the `dev` branch: + +``` +cd mercurylayer +git checkout dev +``` + +Then change directory to the standalone client: + +``` +cd /clients/apps/nodejs +``` + +In this directory is the `config/default.json` file for the client. This needs to be edited (using any text editor, e.g. vi or nano) to set the mercury server endpoint and Electrum server URL. + +For the purposes of demonstration, use the following `Settings.toml`: + +``` +{ + "statechainEntity": "http://test.mercurylayer.com:8500", + "electrumServer": "tcp://mutinynet.com:50001", + "electrumType": "electrs", + "network": "signet", + "feeRateTolerance": 5, + "databaseFile": "wallet.db", + "confirmationTarget": 2, + "maxFeeRate": 1 +} +``` + +The file `test_basic_workflow.js` gives examples of wallet operations, including creation, and transfering of the coin. + +To create a wallet, use the function: + +``` +createWallet(wallet_1_name); +``` + +Then the function: + +``` +walletTransfersToItselfAndWithdraw(wallet_1_name) +``` + +Will perform a deposit, auto transfers and withdrawal, providing prompts for deposting the coin. + +When prompted, copy the deposit address and pay the specified amount to it. For the mutinynet signet network in this demo, this payment can be made directly via the faucet: [faucet.mutinynet.com](https://faucet.mutinynet.com/) + +For withdrawal, for the demo test coins, these can be sent back to an address generated by the faucet: [faucet.mutinynet.com](https://faucet.mutinynet.com/) diff --git a/docs/deposit_sequence.md b/docs/deposit_sequence.md index bc68aa16..05d1aa76 100644 --- a/docs/deposit_sequence.md +++ b/docs/deposit_sequence.md @@ -6,11 +6,11 @@ sequenceDiagram participant Server participant Client Client->>Server: /info/fee - Server-->>Client: {backup_fee_rate,initlock,interval} - note over Client: Derive privkey, privkey_auth - note over Client: Compute user_pubkey, auth_key - Client->>Server: /deposit/init/pod {amount, token_id, auth_key} - note over Server: Verify mark token_id as spent + Server-->>Client: {initlock,interval} + note over Client: Derive user_privkey, privkey_auth + note over Client: Compute user_pubkey, pubkey_auth + Client->>Server: /deposit/init/pod {token_id, auth_key, auth_sig} + note over Server: Verify token_id and auth_sig Server->>Enclave: /get_public_key note over Enclave: Generate statechain_id note over Enclave: Generate enclave_privkey @@ -19,17 +19,16 @@ sequenceDiagram note over Enclave: Save sealed enclave_privkey and sig_count with ID statechain_id Enclave-->>Server: {enclave_pubkey, statechain_id} note over Server: Save enclave_pubkey and auth_key with ID statechain_id - Server-->>Client: {statechain_id, enclave_pubkey} + Server-->>Client: {statechain_id, enclave_pubkey} note over Client: Compute pubkey = enclave_pubkey + user_pubkey note over Client: [Pay amount to taproot address pubkey: Tx0] note over Client: Construct Tx1 spending Tx0 output to user_pubkey note over Client: set nLocktime = current_block + initlock note over Client: Compute Tx1 sighash note over Client: Generate random r2, f - note over Client: Compute R2, SHA256(R2) and SHA256(f) - Client->>Server: /sign/first {r2_com,blind_com,statechain_id,auth_sig} + note over Client: Compute R2 + Client->>Server: /sign/first {statechain_id,auth_sig} note over Server: Verify auth_sig with statechain_id and auth_key - note over Server: Save r2_com and blind_com with ID statechain_id Server->>Enclave: /get_public_nonce {statechain_id} note over Enclave: Generate private_nonce note over Enclave: Compute r1_public diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f08edd1e..db2bcd60 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -182,34 +182,6 @@ paths: could not recover from. - If you get this response please report this as an issue at - github.com/commerceblock/mercury. - /info/transfer-batch/{batch_id}: - get: - summary: Get batch transfer status and statecoin IDs for specified batch ID - operationId: util_get_transfer_batch_status - parameters: - - name: batch_id - in: path - required: true - schema: - type: string - responses: - '200': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/TransferBatchDataAPI' - '500': - description: >- - # [500 Internal Server - Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) - - This response is given when the server has an internal error that it - could not recover from. - - If you get this response please report this as an issue at github.com/commerceblock/mercury. /pod/token/init/{value}: @@ -268,62 +240,6 @@ paths: could not recover from. - If you get this response please report this as an issue at - github.com/commerceblock/mercury. - /transfer/batch/init: - post: - summary: 'Initiate the batch transfer protocol: provide statechain signatures' - operationId: transfer_batch_transfer_batch_init - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/TransferBatchInitMsg' - required: true - responses: - '200': - description: '' - content: - application/json: - schema: - type: 'boolean' - '500': - description: >- - # [500 Internal Server - Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) - - This response is given when the server has an internal error that it - could not recover from. - - - If you get this response please report this as an issue at - github.com/commerceblock/mercury. - /transfer/batch/reveal: - post: - summary: 'Complete Batch transfer: reveal transfer nonce' - operationId: transfer_batch_transfer_reveal_nonce - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/TransferRevealNonce' - required: true - responses: - '200': - description: '' - content: - application/json: - schema: - type: 'boolean' - '500': - description: >- - # [500 Internal Server - Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) - - This response is given when the server has an internal error that it - could not recover from. - - If you get this response please report this as an issue at github.com/commerceblock/mercury. /transfer/get_msg_addr/{auth_key}: @@ -354,34 +270,6 @@ paths: could not recover from. - If you get this response please report this as an issue at - github.com/commerceblock/mercury. - /transfer/keyupdate_complete: - post: - summary: 'Transfer completing by receiver: key share update and deletion' - operationId: transfer_keyupdate_complete - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/KUFinalize' - required: true - responses: - '200': - description: '' - content: - application/json: - schema: - type: 'boolean' - '500': - description: >- - # [500 Internal Server - Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) - - This response is given when the server has an internal error that it - could not recover from. - - If you get this response please report this as an issue at github.com/commerceblock/mercury. /transfer/receiver: @@ -789,15 +677,9 @@ components: description: User commitments for signing type: object required: - - r2_commitment - - blind_commitment - statechain_id - auth_sig properties: - r2_commitment: - type: string - blind_commitment: - type: string statechain_id: $ref: '#/components/schemas/Uuid' auth_sig: @@ -858,10 +740,9 @@ components: - num_sigs - s2_pub - signature_count - - blind_commits - - r2_commits - r1_values - blind_challenges + - x1_pub properties: amount: description: The value of the statecoin (in satoshis) @@ -876,19 +757,15 @@ components: type: integer s2_pub: type: string - blind_commits: - type: array - items: - type: string - r2_commits: + r1_values: type: array items: type: string - r1_values: + blind_challenges: type: array items: type: string - blind_challenges: + x1_pub: type: array items: type: string @@ -1070,21 +947,11 @@ components: required: - s2_pub - signature_count - - blind_commits - - r2_commits - r1_values - blind_challenges properties: s2_pub: type: string - blind_commits: - type: array - items: - type: string - r2_commits: - type: array - items: - type: string r1_values: type: array items: diff --git a/docs/protocol.md b/docs/protocol.md index d47be245..94d5ec7e 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -6,48 +6,50 @@ The SE and each owner are required to generate private keys securely. Owners are In addition, a public key encryption scheme is required for blinded private key information sent between parties. This should be compatible with the EC keys used for signatures, and ECIES is used. The notation for the use of ECIES operations is as follows: `Enc(m,K)` denotes the encryption of message `m` with public key `K = k.G` and `Dec(m,k)` denotes the decryption of message `m` using private key `k`. -All transactions are taproot addresses using segregated witness, which enables input transaction IDs to be determined before signing and prevents their malleability. +All transactions are pay to taproot addresses using segregated witness, which enables input transaction IDs to be determined before signing and prevents their malleability. -## Deposit +The service is operated by the 'statechain entity' (SE) server. -A user wants to deposit an amount of BTC into the platform, and they request that the SE initialize the process. To begin, the user must provide a valid `token_id` (UUID), which will be listed in the the server token database. This `token_id` is generated by the SE on payment of a deposit fee (via a separate lighning, bitcoin or other out of band payment). +## Initialisation + +A user wants to create a shared key, and they request that the SE initialize the process. To begin, the user must provide a valid `token_id` (UUID) in order to initialise the key, which will be listed in the the server token database. This `token_id` is generated by the SE on payment of a deposit fee (via a separate lighning, bitcoin or other out of band payment) and is used to provide access to the service. 1. The depositor (Owner 1) generates a private key: `o1` and then calculates the corresponding public key `O1 = o1.G`. They also generate an authentication key `a1` and corresponding public key `A1 = a1.G`. These two private keys can be derived from a single BIP32 seed via different paths. -2. Owner 1 requests a key share from the SE with a valid `token_id`, amount value `v` and authentication key `A1`. -3. The SE then generates a private key: `s1` (the SE private key share), calculates the corresponding public key and sends it to Owner 1: `S1 = s1.G` along with a `statechain_id` (UUID). The SE then stores `s1`, `S1`, `v` and `A1` indexed with `statechain_id`. +2. Owner 1 requests a key share from the SE with a valid `token_id` (for access) and authentication key `A1`, and signature for `A1`. +3. The SE then generates a private key: `s1` (the SE private key share), calculates the corresponding public key and sends it to Owner 1: `S1 = s1.G` along with a new random `statechain_id` (UUID - used for the server to reference this shared key). The SE then stores `s1`, `S1` and `A1` indexed with `statechain_id`. 4. Owner 1 then adds the public key they receive by their own public key to obtain the shared (aggregated) public key `P` (which corresponds to a shared private key of `p = o1 + s1`): `P = O1 + S1` -5. Owner 1 creates and broadcasts a funding transaction (`Tx0`) to pay an amount `v` to the address corresponding to `P`. This defines the UTXO `TxID:vout` (the outpoint). +5. Owner 1 creates and broadcasts a funding transaction (`Tx0`) to pay an amount `v` to the bitcoin address derived from `P`. This defines the UTXO `TxID:vout` (the coin outpoint). 6. Owner 1 creates an unsigned *backup transaction* (`Tx1`) that pays the `P` output of `Tx0` to an address `O1`, and sets the `nLocktime` to the initial future block height `h0` (where `h0 = cheight + hinit`, `cheight` is the current Bitcoin block height and `hinit` is the initial locktime specified by the server). -7. Owner 1 cooperates with SE to generate a valid signature on `Tx1` as follows: +7. Owner 1 cooperates with SE to generate a valid signature on `Tx1` as follows (MuSig2): > To generate a signature on `Tx1`, the owner first computes the sighash `m1`. > Owner 1 then generates a random ephemeral nonce `r2_1` and blinding nonce `b1` and computes `R2_1 = r2_1.G` -> Owner 1 then requests a co-signing from the SE sending commitments: `SHA256(R2_1)` and `SHA256(b1)`, authenticated with a signature on `A1`. -> SE verifies the signature against `A1` and stores the commitments for `statechain_id` and generates a random `r1_1` and computes `R1_1 = r1_1.G`. `R1_1` is returned to Owner 1. +> Owner 1 then requests a co-signing from the SE authenticated with a signature on `A1`. +> SE verifies the signature against the `A1` stored in the database for `statechain_id` and generates a random `r1_1` and computes `R1_1 = r1_1.G`. `R1_1` is returned to Owner 1. > Owner 1 then computes `R_1 = R1_1 + r2_1.G + b1.P`, `e1 = SHA256(P||R_1||m1)` and `c1 = e1 + b1` and sends `c1` to the SE. -> The SE then computes `sig1_1 = r1_1 + c1.s1` and sends to Owner 1 (and stores `c1` with `statechain_id`). +> The SE then computes `sig1_1 = r1_1 + c1.s1` and sends to Owner 1. > Owner 1 computes `sig2_1 = r2_1 + c1.o1` and `sig_1 = sig1_1 + sig2_1`. The full signature `(sig_1,R_1)` is then added to `Tx1`. -8. `Tx1` is verified and stored by Owner 1, along with the values `b1`, `R1_1` and `e1`. +8. `Tx1` signature is verified and stored by Owner 1, along with the values `b1`, `R1_1` and `e1`. 9. The SE then adds the public key `S1` to the list of current statechain SE keys and publishes. ## Transfer -Owner 1 wishes to transfer the value of the deposit `v` to a new owner (Owner 2) (as a payment or as part of a swap). The protocol then proceeds as follows: +Owner 1 wishes to transfer the value of the UTXO `TxID:vout` `v` to a new owner (Owner 2) (as a payment or as part of a swap). The protocol then proceeds as follows: ### Sender 1. The receiver (Owner 2) generates a private key `o2`. They then compute the corresponding public key `O2 = o2.G`. They also generate an authentication key `a2` and corresponding public key `A2 = a2.G`. -2. `O2 || A2` is the Owner 2 'address' and is communicated to Owner 1 (or published) in order for them to send the statecoin to it. +2. `O2 || A2` is the Owner 2 'mercury address' and is communicated to Owner 1 (or published) in order for them to send the statecoin to it. 3. Owner 1 then creates a new unsigned backup transaction `Tx2` paying the output of `Tx0` to the bitcoin address defined from `O2`, and sets the `nLocktime` to `h0 - (n-1)*c` where `c` is the confirmation interval and `n` is the owner number (i.e. 2). 4. Owner 1 cooperates with SE to generate a valid signature on `Tx2` as follows: > To generate a signature on `Tx2`, the owner first computes the sighash `m2`. > Owner 1 then generates a random ephemeral nonce `r2_2` and blinding nonce `b2` and computes `R2_2 = r2_2.G` -> Owner 1 then requests a co-signing from the SE sending commitments: `SHA256(R2_2)` and `SHA256(b2)`, authenticated with a signature on `A1`. -> SE stores the commitments for `statechain_id` and generates a random `r1_2` and computes `R1_2 = r1_2.G`. `R1_2` is returned to Owner 1. +> Owner 1 then requests a co-signing from the SE, authenticated with a signature on `A1`. +> SE verifies the signature against the `A1` stored in the database and generates a random `r1_2` and computes `R1_2 = r1_2.G`. `R1_2` is returned to Owner 1. > Owner 1 then computes `R_2 = R1_2 + r2_2.G + b2.P`, `e2 = SHA256(P||R_1||m1)` and `c2 = e2 + b2` and sends `c2` to the SE. -> The SE then computes `sig1_2 = r1_2 + c2.s1` and sends to Owner 1 (and stores `c2` with `statechain_id`). +> The SE then computes `sig1_2 = r1_2 + c2.s1` and sends to Owner 1. > Owner 1 computes `sig2_2 = r2_2 + c2.o1` and `sig_2 = sig1_2 + sig2_2`. The full signature `(sig_2,R_2)` is then added to `Tx2`. 5. SE generates a random key `x1` and sends it to Owner 1. @@ -57,17 +59,15 @@ Owner 1 wishes to transfer the value of the deposit `v` to a new owner (Owner 2) a. All `K` (here `K = 2`) signed backup transactions (`Txi i=1,...,K`): `Tx1` and `Tx2` - b. For each backup transaction signature (`bi`,`R2_i i=1,...,K`): `b1`,`b2`,`R2_1` and `R2_2`. - - c. `SC_sig_1` + b. `SC_sig_1` - d. `t2` + c. `t1` - e. `statechain_id` + d. `statechain_id` -9. `TransferMsg` is encrypted with `A2`: `EncTransferMsg = Enc(TransferMsg,A2)`. `EncTransferMsg` is then sent to Owner 2. As it is encrypted, this can be via the server - i.e. the message is uploaded to the server and stored indexed with `A2`. +9. `TransferMsg` is encrypted with the receiver key `A2`: `EncTransferMsg = Enc(TransferMsg,A2)`. `EncTransferMsg` is then sent to Owner 2. As it is encrypted, this can be relayed via the server - i.e. the message is uploaded to the server and stored indexed with `A2`. -> At this point the Owner 1 has sent all the information required to complete the transfer to Owner 2 and is no longer involved in the protocol. Owner 2 then verifies the correctness and validity of the objects. +> At this point the Owner 1 has sent all the information required to complete the transfer to Owner 2 and is no longer involved in the protocol. Owner 2 then verifies the correctness and validity of the objects as follows: ### Receiver @@ -80,12 +80,9 @@ Owner 1 wishes to transfer the value of the deposit `v` to a new owner (Owner 2) b. The `nLocktimes` are decremented correctly (i.e. the latest `TxK` has the lowest). - c. Retreives `ci`, and commitments `SHA256(R2_i)` and `SHA256(bi)` from the SE. - - d. Verifies the commitments to `R2_i` and `bi` and verfies that `ci = bi + SHA256(P||R_i||mi)` (where `mi` is the sighash of `Txi`). - -5. Owner 2 queries SE for 1) The total number of signatures generated for `statechain_id`: `N` and 2) Current SE public key: `S1`. -6. Owner 2 then verifies that `K = N` and then `O1 + S1 = P` +5. Owner 2 queries SE for 1) The total number of signatures generated for `statechain_id`: `N` and 2) Current SE public key share. 3) The public point `X1 = x1.G` +6. Owner 2 then verifies that `K = N` and that `t1.G = O1 + X1`. This check verifies that the value `t1` has been computed correctly from the sender private key. +7. Owner 2 verifies the signature `SC_sig_1` verifies against `O1` and that `P = S1 + O1`. This check mitigates the key cancellation vulnerability. The SE key share update then proceeds as follows: @@ -93,20 +90,20 @@ The SE key share update then proceeds as follows: 6. Owner 2 then sends `t2` to the SE, authenticated with a signature on `A2`. 7. SE the updates the private key share `s2 = s1 + t2 - x1 = s1 + x1 + o1 - o2 - x1 = s1 + o1 - o2` -> `s2` and `o2` are now key the private key shares of `P = (s2 + o2).G` which remains unchanged (i.e. `s2 + o2 = s1 + o1`), without anyone having learnt the full private key. Provided the SE deletes `s1`, then there is no way anyone but the current owner (with `o2`) can spend the output. +> `s2` and `o2` are now key the private key shares of `P = (s2 + o2).G` which remains unchanged (i.e. `s2 + o2 = s1 + o1`), without anyone having learnt the full private key. Provided the SE deletes `s1`, then there is no way anyone but the current owner (with `o2`) can spend the output. -8. The SE then adds the public key `S2` to the list of active key shares and publishes. +8. The SE then adds the current server public key share `S2` and signature count `K` to the list of active key shares and publishes. This enables the current owner to verify that their share (`O2`) uniquely combines with the published share. -## Orderly Withdrawal +## Orderly Closure -The current owner of a coin can at any time withdraw from the platform by simply co-signing a withdrawal transaction. The server cannot identify a withdrawal, but the coin can no longer be transferred to a new owner because the server will have produced an additional signature that cannot be verified as a valid backup by a receiver. +The current owner of a coin can at any time close the shared key session from the platform by simply co-signing a withdrawal transaction. The server cannot identify a closure transaction, but the coin can no longer be transferred to a new owner because the server will have produced an additional signature that cannot be verified as a valid backup by a receiver. Withdrawal proceeds as follows: 1. The current owner (e.g. Owner 2) creates an unsigned transaction `TxW` that spends `Tx0` to a withdrawal address `W`. 2. The owner then co-signs this transaction with the SE (as above). `TxW` is broadcast. -3. The owner then sends the SE a withdrawal notification (with their current `user_id`) that the coin is withdrawn so the SE can remove the coin public from the published key share list. +3. The owner then sends the SE a withdrawal notification (with their current `user_id`) that the coin is withdrawn so the SE can remove the coin public key from the published key share list. -## Backup withdrawal +## Backup Closure In the case that the SE disappears or does not cooperate with the current owner, the current owner can reclaim their funds to an address they control by submitting their backup transaction when the `nLocktime` is reached. diff --git a/docs/server_db.md b/docs/server_db.md index 9f966919..fbc01847 100644 --- a/docs/server_db.md +++ b/docs/server_db.md @@ -2,9 +2,9 @@ ## Statechains -| statechain_id | auth_key | new_auth_key | enclave_pubkey | r2_com | blind_com | public_nonce | challenge | x1 | batch_id | +| statechain_id | auth_key | new_auth_key | enclave_pubkey | public_nonce | challenge | x1 | batch_id | | --- | ----------- | ----------- | ----------- | ----------- | ----------- | ----------- | ----------- |----------- | ----------- | -| UUId | string | string | string | array | array | array | array | string | UUId | +| UUId | string | string | string | array | array | string | UUId | ## Transfer Messages diff --git a/docs/swap_protocol.md b/docs/swap_protocol.md new file mode 100644 index 00000000..85b02e82 --- /dev/null +++ b/docs/swap_protocol.md @@ -0,0 +1,61 @@ +# Mercury Layer coinswap protocol + +The Mercury layer server API supports atomic bacth transfers where two (or more parties) agree to a process where two (or more) coins are transfered to new specified addresses in a single operation, where either they all complete the transfer or none of them do. + +This atomic transfer API can be used to enable two (or more) statecoin owners to swap their coins. In order to perform a swap of two coins, the following events must occur: + +1. The two coin owners must be made aware of each others desire to swap and to communicate. This is achieved via nostr messages. +2. One user will be the `proposer` and the other the `taker`. +3. The proposer will broadcast a message labeled as a swap request with a randomly generated `batch_id` and the coin amount and their `sc` address. +4. The `taker` will query the relay node for any message labeled as a swap request for a specified amount. +5. When the `taker` sees the swap request, then broadcast a message with the same `batch_id` and their own `sc` address. +6. Any other potential `taker` will see there are more than one message with the same `batch_id` and ignore them (as they are already paired). +7. Once the `proposer` sees the `taker` message (sharing the same `batch_id`), they both then commence the atomic transfer to the new `sc` address. +8. This starts the atomic transfer timeout, and once comlplete either both coins are transfered to the new address, or neither are. + +## Nostr protocol + +The proposer generates a key pair for the nostr event. +The `proposer` generates a nostr *event* with a `kind` that is a mercury swap event (e.g. `4521`). + +``` +{ + "id": "4376c65d2f232afbe9b882a35baa4f6fe8667c4e684749af565f981833ed6a65", + "pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", + "created_at": 1673347337, + "kind": 4521, + "content": "Mercury swap request", + "tags": [ + ["address", "sc1qd8tt2cme0heruuf9zlxeygq96lum7qzl4tf32hnkyvlta9gqvumud8su7pvdzelx5ku2hrggwhuv5v3x824re8gcjl7yhq4quhtf5vfgwszp5"], + ["amount", "500000"], + ["batch_id"], "b94cba9b-93f8-419f-8adb-a943125a20f8"] + ], + "sig": "908a15e46fb4d8675bab026fc230a0e3542bfade63da02d542fb78b2a8513fcd0092619a2c8c1221e581946e0191f2af505dfdf8657a414dbca329186f009262" +} +``` + +This event to sent to the relay. + +The `taker` via the relay checks for any messages with the `"kind": 4521` and specified amount they want to swap `["amount", "500000"]`. They check for any events that there is a unique `batch_id`. They can also apply filtering based on the `created_at` timestamp (to ignore any very old events for example). + +They then reply with their own event message with the same `batch_id` and address. They also initiate `transfer/sender` paying to the specified address with `batch_id`. + +``` +{ + "id": "a3e88f9f13ef06cd35ac55d2c34c8ce2e8874cccb50e2545975f046f7acee8f1", + "pubkey": "84fa1bd2ff0fb9bd5ee4256671c4f6a40dca311e5301668b282dbf66a6bedcc6100p", + "created_at": 1673347337, + "kind": 4521, + "content": "Mercury swap request", + "tags": [ + ["address", "sc1qd8tt2cme0heruuf9zlxeygq96lum7qzl4tf32hnkyvlta9gqvumud8su7pvdzelx5ku2hrggwhuv5v3x824re8gcjl7yhq4quhtf5vfgwszp5"], + ["amount", "500000"], + ["batch_id"], "b94cba9b-93f8-419f-8adb-a943125a20f8"] + ], + "sig": "908a15e46fb4d8675bab026fc230a0e3542bfade63da02d542fb78b2a8513fcd0092619a2c8c1221e581946e0191f2af505dfdf8657a414dbca329186f009262" +} +``` + +The proposer listens for any events of `"kind": 4521`, and if the `batch_id` matches, then they also initiate `transfer/sender` paying to the specified address with `batch_id`. + +The swap then completes. If either party fail to complete, the coins can revert to the original owners. \ No newline at end of file diff --git a/docs/test_cases.md b/docs/test_cases.md new file mode 100644 index 00000000..770fc63d --- /dev/null +++ b/docs/test_cases.md @@ -0,0 +1,100 @@ +# Test Cases + +## Basic Workflow + +### TB01 - Simple transfer + +01. Create wallet 1 and 2 +02. Create a token +03. Generate wallet 1 deposit address with this token +04. Confirm the intial state of the coin is `INITIALISED` +05. Confirm the amount is correct +06. Confirm there is a statechain id +07. Send funds to the address of the new coin +08. Wait for Electrs to index this deposit transaction +09. Confirm that the coin status changed to `IN_MEMPOOL` status +11. Try to transfer the coin. It must fail. +12. Generate a new block +13. Confirm that the coin status changed to `UNCONFIRMED` status +14. Try to transfer the coin. It must fail. +15. Generate blocks enough to confirm the coin (according to the client's Settings.toml) +16. Confirm that the coin status changed to `CONFIRMED` status +17. Try to transfer the coin. This time, it must work. +18. Wallet 2 generates a new transfer address. +19. Wallet 1 sends the coin to this wallet 2's address +20. Confirm that the coin status changed to `IN_TRANSFER` status in wallet 1 +21. Wallet 2 executes `transfer-receive` +22. Confirm that the coin status changed to `TRANSFERRED` status in wallet 1 +23. Confirm that the coin status changed to `CONFIRMED` status in wallet 2 +24. Wallet 2 withdraws the coin +25. Confirm that the coin status changed to `WITHDRAWING` status in wallet 2 +26. Generate blocks enough to confirm the withdrawal (according to the client's Settings.toml) +25. Confirm that the coin status changed to `WITHDRAWN` status in wallet 2 + +### TB02 - Transfer Address Reuse + +01. Create wallet 1 and 2 +02. Create a token +03. Generate wallet 1 deposit address with this token (2x) +04. Send funds to the address of the new coin (2x) +05. Generate blocks enough to confirm the coin (according to the client's Settings.toml) +06. Wait for Electrs to index this deposit transaction +07. Confirm wallet 1 has two coins +08. Confirm these two coins have the `CONFIRMED` status +09. Wallet 2 generates a new transfer address. +10. Wallet 1 sends the two coins to this same wallet 2's address +11. Wallet 2 executes `transfer-receive` and stores the received statechain ids +12. Confirm wallet 2 now has two coins +13. Confirm those coins have the status `CONFIRMED` +14. Confirm the coins have the received statechain ids +15. Confirm those two coins have same `user_privkey`, `auth_privkey` and `address` +16. Confirm those two coins have different `server_pubkey`, `statechain_id` and `aggregated_address` +17. Wallet 2 withdraws the first coin +18. Confirm that the coin status changed to `WITHDRAWING` status in wallet 2 +19. Generate blocks enough to confirm the withdrawal (according to the client's Settings.toml) +20. Confirm that the coin status changed to `WITHDRAWN` status in wallet 2 +21. Wallet 1 generates a new transfer address. +22. Wallet 2 sends the second coin to this wallet 1's address +23. Confirm that the coin status changed to `TRANSFERRED` status in wallet 2 +24. Confirm that the coin status changed to `WITHDRAWING` status in wallet 1 +25. Generate blocks enough to confirm the withdrawal (according to the client's Settings.toml) +26. Confirm that the coin status changed to `WITHDRAWN` status in wallet 1 + +## Alternative Workflow + +### TA01 - SignSecond not called + +01. Create wallet 1 and 2 +02. Create a token +03. Generate wallet 1 deposit address with this token +04. Send funds to the address of the new coin +05. Generate blocks enough to confirm the coin (according to the client's Settings.toml) +06. Wait for Electrs to index this deposit transaction +07. Confirm wallet 1 has a coin +08. Wallet 2 generates a new transfer address. +09. Wallet 1 calls `transfer/send` and `sign/first` but not `sign/second` using the coin' statechain id and the transfer address of wallet 2 +10. Using the same parameters, wallet 1 then calls `transfer-send` command, which includes the complete process (transfer/send`, `sign/first`, `sign/second` and `transfer/update`) +11. Wallet 2 executes `transfer-receive` +12. Confirm that the coin status changed to `TRANSFERRED` status in wallet 1 +13. Confirm that the coin status changed to `CONFIRMED` status in wallet 2 + +## Malicious Workflow + +### TM01 - Sender Double Spends + +01. Create wallet 1, 2 and 3 +02. Create a token +03. Generate wallet 1 deposit address with this token +04. Generate blocks enough to confirm the coin (according to the client's Settings.toml) +05. Wait for Electrs to index this deposit transaction +06. Confirm that there is a coin in `CONFIRMED` status in wallet 1 +07. Wallet 2 generates a new transfer address. +08. Wallet 1 sends the coin to this wallet 2's address. +09. Wallet 3 generates a new transfer address. +10. Wallet 1 sends the coin to this wallet 3's address. +11. Wallet 3 executes `transfer-receive`. This must succeed. +12. Wallet 1 tries to send the coin to this wallet 2's address again. +13. This time, the server must not allow it because the wallet 3 has already received the coin. +14. Update wallet 1 +15. Wallet 1 tries to send the coin to this wallet 2's address again. +16. This time, the client must not allow it because coin is in the `TRANSFERRED` state. diff --git a/docs/tokens.md b/docs/tokens.md new file mode 100644 index 00000000..9dcef645 --- /dev/null +++ b/docs/tokens.md @@ -0,0 +1,65 @@ +# Token payment system + +To enable many ways to give permission to deposit coins, the deposit permission system will utilize tokens that can be issued separately to the deposit process and then redeemed on deposit. This will enable fees to be paid via any mechanism and also managed separately. + +## Tokens + +**Deposit tokens** will be managed via a new table `tokens` (which is a separate DB to the main `mercury` DB so separate permissions can be applied). This table will have 4 columns: `token_id` (Uuid), `processor_id` (string), `confirmed` (boolean) and `spent` (boolean). + +The `tokens` table will be interacted with via two new mercury server functions: `token_init` and `token_verify`. + +The `token_init` function will take no arguments. This function will generate a new random `token_id` (Uuid) and then call the payment processor API (`https://docs.swiss-bitcoin-pay.ch/checkout`) with this as the label and the fixed configured fee. The payment processor API will then return a JSON object containing the `processor_id`. +This `processor_id` will then be added to the token DB with the `token_id` and `confirmed = false` and `spent = false`. The `token_id` and `processor_id` are returned to the client. + +The client/wallet then retrieves/displays the payment information (Lightning invoice and on-chain address) from the payment processor API using `processor_id` (e.g. `https://checkout.swiss-bitcoin-pay.ch/{processor_id}`). + +The `token_verify` function will take one argument (`token_id`) and return a boolean (`valid`). This function will first query the `tokens` table with the `token_id`. If no entry found, it will return an error. If a row is found, it will return false if `spent = true`. If `spent = false` and `confirmed = true` it will return `true`. If `spent = false` and `confirmed = false` it will then query the payment processor API with the `processor_id` to verify payment: if confirmed it will update `confirmed = true` and return `true`. + +### Deposit process + +The new deposit process will verify that a valid (i.e. confirmed and unspent) token is in the `tokens` table before creating a `statechain_id`. +When `deposit_init` is called, a `token_id` must now be supplied as an argument. This function will then query the `tokens` table with the `token_id`. + +## Sequence + +```mermaid +sequenceDiagram + participant Server + participant Client + Client->>Server: GET /token/init + note over Server: Get processor_id from payment processor + note over Server: Generate random token_id + note over Server: Add to Token DB + Server-->>Client: {token_id, processor_id} + note over Client: Use processor_id to get payment details from processor API + note over Client: Display invoice/address + note over Client: Enable user to manually enter different token_id + note over Client: Wait until payment received redirect or user continue button pressed + Client->>Server: GET /token/verify/{token_id} + note over Server: Get processor_id from DB + note over Server: Verify payment via processor API with processor_id + note over Server: Update DB with confirmation + Server-->>Client: {true/false} + note over Client: If true, continue with /deposit/init/pod with token_id + note over Client: If false, 'Payment not verified' message +``` + +## Wallet token management + +Clients (wallets) keep the wallet state in the named wallet JSON object. This object contains a `tokens` array, where each element has the fields: + +``` +{ +token_id, +processor_id, +confirmed, +spent +} +``` + +The logic for initialising a statecoin deposit proceeds as follows: + +1. First deposit screen: user selects statecoin deposit amount +2. Wallet accesses `tokens` array. If there is a `token_id` with `confirmed = true` and `spent = false`, then deposit init is performed using this `token_id`. Once deposit init has completed, that `token_id` in the tokens array is updated as status `spent = true` and the wallet saved. +3. If there is a `token_id` with `confirmed = false` and `spent = false`, then the `token/token_verify/` endpoint is called. If it returns `true`, then update `token_id` entry in `tokens` to `confirmed = true` and save the wallet. Then deposit init is performed using this `token_id`. Once deposit init has completed, that `token_id` in the tokens array is updated as status `spent = true` and the wallet saved. If `token/token_verify/` returns `false`, go to next step. +4. Otherwise, call `token/token_init` endpoint. Save returned `token_id` and `processor_id` in `tokens` array with `confirmed = false` and `spent = false`. Use `processor_id` to get invoice and fee address from the payment processor and display (as QR codes) in the wallet UI. Then poll `token/token_verify/` until it returns `true`, and then update `token_id` entry in `tokens` to `confirmed = true` and save the wallet. Then deposit init is performed using this `token_id`. Once deposit init has completed, that `token_id` in the tokens array is updated as status `spent = true` and the wallet saved. diff --git a/docs/transfer_receiver_sequence.md b/docs/transfer_receiver_sequence.md index f6405c27..cd860d7c 100644 --- a/docs/transfer_receiver_sequence.md +++ b/docs/transfer_receiver_sequence.md @@ -9,7 +9,7 @@ sequenceDiagram note over Client: Compute new_user_pubkey, new_auth_key note over Client: Send new_user_pubkey || new_auth_key to sender Client->>Server: /info/fee - Server-->>Client: {backup_fee_rate,initlock,interval} + Server-->>Client: {initlock,interval} note over Client: batch_data is null note over Client: Query server to check for transfers Client->>Server: /transfer/get_msg_addr/{new_auth_key} @@ -19,17 +19,17 @@ sequenceDiagram Client->>Server: /info/statechain/ {statechain_id} Server->>Enclave: /signature_count {statechain_id} Enclave-->>Server: {num_sigs} - Server-->>Client: {pubkey, num_sigs, blind_commits, r2_commits, r1_values, blind_challenges} + Server-->>Client: {enclave_pubkey, num_sigs, x1_pub} note over Client: Verify TransferMsg: note over Client: Verify latest backup transaction pays to new_user_pubkey note over Client: Verify that the input (Tx0) is unspent. + note over Client: Verify Tx0 address is enclave_pubkey + user_pubkey = P + note over Client: Verify that t1.G = user_pubkey + x1_pub note over Client: For each previous K backup transactions (Txi i=1,...,K): - note over Client: Verify the signature is valid. - note over Client: Verify the nLocktimes are decremented correctly - note over Client: Verify backup_fee_rate - note over Client: Verify SHA256(R2_i) and SHA256(bi) with blind_commits and r2_commits - note over Client: Verify that c = b + SHA256(P||R||m) (where m is the sighash of Tx). - note over Client: b from blind_challenges and R = R1 + R2 + note over Client: Verify the signature is valid. + note over Client: Verify the nLocktimes are decremented correctly + note over Client: Verify SC_sig with user_pubkey + note over Client: Verify user_pubkey + enclave_pubkey = P note over Client: Computes: t2 = t1 - privkey Client->>Server: /transfer/receiver {statechain_id,batch_data,t2,auth_sig} Server->>Enclave: /keyupdate {statechain_id, t2, x1} diff --git a/docs/transfer_sender_sequence.md b/docs/transfer_sender_sequence.md index 3456133e..acbce420 100644 --- a/docs/transfer_sender_sequence.md +++ b/docs/transfer_sender_sequence.md @@ -17,11 +17,10 @@ sequenceDiagram note over Client: Construct Tx2 spending Tx0 output to new_user_pubkey note over Client: set nLocktime = nLocktime - interval note over Client: Compute Tx1 sighash - note over Client: Generate random r2, f - note over Client: Compute R2, SHA256(R2) and SHA256(f) - Client->>Server: /sign/first {r2_com,blind_com,statechain_id,auth_sig} + note over Client: Generate random r2, f + note over Client: Compute R2 + Client->>Server: /sign/first {statechain_id,auth_sig} note over Server: Verify auth_sig with statechain_id and auth_key - note over Server: Save r2_com and blind_com with ID statechain_id Server->>Enclave: /get_public_nonce {statechain_id} note over Enclave: Generate private_nonce note over Enclave: Compute r1_public @@ -31,7 +30,7 @@ sequenceDiagram note over Client: Compute R and challenge Client->>Server: /sign/second {statechain_id,challenge,auth_sig} note over Server: Verify auth_sig with statechain_id and auth_key - note over Server: Save challenge with ID statechain_id + note over Server: Save challenge with ID statechain_id Server->>Enclave: /get_partial_signature {statechain_id, challenge} note over Enclave: Compute blind partial signature note over Enclave: Increment sig_count by 1 @@ -39,12 +38,13 @@ sequenceDiagram Server-->>Client: {partial_signature} note over Client: Compute full signature and add to Tx1 note over Client: Compute t1 = privkey + x1 + note over Client: Concatenate Tx0 outpoint with user_pubkey and sign with privkey (SC_sig) note over Client: Compile TransferMsg: note over Client: All signed backup transactions (Txi i=1,...,K) - note over Client: For each backup transaction signature (bi,R2_i i=1,...,K) - note over Client: t2 + note over Client: user_pubkey and SC_sig + note over Client: t1 note over Client: statechain_id note over Client: Encrypt TransferMsg with new_auth_key: EncTransferMsg - Client->>Server: /transfer/update_msg {new_auth_key,EncTransferMsg} - note over Server: Save EncTransferMsg with new_auth_key + Client->>Server: /transfer/update_msg {new_auth_key,EncTransferMsg,statechain_id} + note over Server: Save EncTransferMsg in DB with new_auth_key ``` diff --git a/docs/wasm.md b/docs/wasm.md index ac5ccaca..68f38d7e 100644 --- a/docs/wasm.md +++ b/docs/wasm.md @@ -8,12 +8,11 @@ The Mercury layer operates as a client/server application. The Mercury client is Create a new wallet from 12 word BIP39 seed phrase -**input:** +**input:** ``` { network: String, // mainnet, testnet or regtest mnemonic: String, // 12 word seed phrase - password: String, // encryption password wallet_name: String, // wallet name } ``` @@ -25,6 +24,106 @@ Create a new wallet from 12 word BIP39 seed phrase } ``` +#### setConfig + +Set locktime config + +**input:** +``` +{ + wallet: Wallet, // wallet object + server_config: ServerConfig +} +``` + +*output* +``` +{ + wallet: Wallet, // wallet object +} +``` + +#### setBlockheight + +Set blockheight + +**input:** +``` +{ + wallet: Wallet, // wallet object + blockheight: number +} +``` + +*output* +``` +{ + wallet: Wallet, // wallet object +} +``` + +#### addToken + +Add a new token to the wallet + +**input:** +``` +{ + wallet: Wallet, + token_id: String, + invoice: String, + amount: number, + confirmed: bool +} +``` + +*output* +``` +{ + wallet: Wallet, // wallet object +} +``` + +#### confirmToken + +Confirm token payment + +**input:** +``` +{ + wallet: Wallet, + token_id: String, +} +``` + +*output* +``` +{ + wallet: Wallet, // wallet object +} +``` + +#### getTokens + +Get array of tokens from wallet + +**input:** +``` +{ + wallet: Wallet, +} +``` + +*output* +``` +[{ + token_id: String, + invoice: String, + amount: number, + confirmed: bool +}] +``` + #### getBalance Get current wallet balance @@ -65,7 +164,7 @@ Get SC address #### getNumAddress -Get number of SC address +Get number of SC address (i.e. current index) **input:** ``` @@ -114,7 +213,7 @@ Get list of statecoins *output* ``` { - statcoins: [statechain_id:user_id, status, value, txid:vout, locktime] + statcoins: [Statecoin] } ``` @@ -133,13 +232,13 @@ Get statecoin object *output* ``` { - statcoin: {} + statcoin: Statecoin } ``` #### getExpiredCoins -Get statecoin object +Get all expired coins **input:** ``` @@ -286,3 +385,308 @@ Deposit a statecoin - part 4 wallet: Wallet } ``` + +#### getUnconfirmed + +Get all unconfirmed coins + +**input:** +``` +{ + wallet: Wallet +} +``` + +*output* +``` +{ + coins: [utxo] +} +``` + +#### setConfirmed + +Set confirmation status of coins + +**input:** +``` +{ + wallet: Wallet + coins: [utxo] +} +``` + +*output* +``` +{ + wallet: Wallet +} +``` + +#### transferSender1 + +Transfer sender - part 1 + +Signs and initiates transfer + +transfer_send_1 sent to `/transfer/sender` (response is transfer_send_2) + +**input:** +``` +{ + wallet: Wallet, // wallet name + utxo: String // coin to be sent +} +``` + +*output* +``` +{ + wallet: Wallet // TR deposit address + transfer_send_1: TransferSend1 +} +``` + +#### transferSender2 + +Transfer sender - part 2 + +Create backup tx + +transfer_send_3 sent to `/sign/first` (response is transfer_send_4) + +**input:** +``` +{ + wallet: Wallet, // wallet name + transfer_send_2: TransferSend2 +} +``` + +*output* +``` +{ + wallet: Wallet // TR deposit address + transfer_send_3: TransferSend3 +} +``` + +#### transferSender3 + +Transfer sender - part 3 + +Sign backup tx + +transfer_send_5 sent to `/sign/second` (response is transfer_msg_6) + +**input:** +``` +{ + wallet: Wallet, // wallet name + transfer_send_4: TransferSend4 +} +``` + +*output* +``` +{ + wallet: Wallet // TR deposit address + transfer_send_5: TransferSend5 +} +``` + +#### transferSender4 + +Transfer sender - part 4 + +Generate transfer message + +transfer_send_6 sent to `/sign/second` (response is transfer_send_7) + +transfer_send_7 sent to `/transfer/update_msg` + +**input:** +``` +{ + wallet: Wallet, // wallet name + backup_txs: BackupTxs // backup_txs from DB + transfer_send_6: TransferSend6 +} +``` + +*output* +``` +{ + wallet: Wallet // TR deposit address + transfer_send_7: TransferSend7 +} +``` + +#### transferReceiver1 + +Transfer receiver - part 1 + +Get transfer message + +Send response to `/transfer/get_msg_addr/{new_auth_key}` (response is transfer_rec_1) + +**input:** +``` +{ + wallet: Wallet, // wallet name + index: number // address index +} +``` + +*output* +``` +{ + new_auth_key: String // auth key +} +``` + +#### transferReceiver2 + +Transfer receiver - part 2 + +Get statechain info + +Send response to `/info/statechain/{statechain_id}` (response is transfer_rec_2) + +**input:** +``` +{ + wallet: Wallet, // wallet name + transfer_rec_1: TransferRec1 +} +``` + +*output* +``` +{ + statechain_id: String +} +``` + +#### transferReceiver3 + +Transfer receiver - part 3 + +Verify and Key update + +Send response to `/transfer/receiver` (response is transfer_rec_4) + +Save backup_txs to DB + +**input:** +``` +{ + wallet: Wallet, // wallet name + transfer_rec_2: TransferRec2 +} +``` + +*output* +``` +{ + wallet: Wallet, + transfer_rec_3: TransferRec3 + backup_txs: BackupTxs // backup_txs +} +``` + +#### transferReceiver4 + +Transfer receiver - part 4 + +Complete + +**input:** +``` +{ + wallet: Wallet, + transfer_rec_4: TransferRec4 +} +``` + +*output* +``` +{ + wallet: Wallet +} +``` + +#### withdraw1 + +Withdraw - part 1 + +Create tx + +transfer_wd_1 sent to `/sign/first` (response is transfer_wd_2) + +**input:** +``` +{ + wallet: Wallet, // wallet name + statechain_id: String, + address: String // Withdrawal address, + fee_rate: number +} +``` + +*output* +``` +{ + wallet: Wallet // TR deposit address + transfer_wd_1: TransferWD1 +} +``` + +#### withdraw2 + +Withdraw - part 2 + +Sign tx + +transfer_wd_3 sent to `/sign/second` (response is transfer_wd_4) + +**input:** +``` +{ + wallet: Wallet, // wallet name + transfer_wd_2: TransferWD2 +} +``` + +*output* +``` +{ + wallet: Wallet, // TR deposit address + transfer_wd_3: TransferWD3 +} +``` + +#### withdraw3 + +Withdraw - part 3 + +Complete + +Withdraw_tx broadcast via electrum interface + +**input:** +``` +{ + wallet: Wallet, // wallet name + transfer_wd_4: TransferWD4 +} +``` + +*output* +``` +{ + wallet: Wallet, // TR deposit address + withdraw_tx: String +} +``` + diff --git a/docs/withdraw_sequence.md b/docs/withdraw_sequence.md new file mode 100644 index 00000000..1c0f9a99 --- /dev/null +++ b/docs/withdraw_sequence.md @@ -0,0 +1,33 @@ +```mermaid +sequenceDiagram + participant Enclave + participant Server + participant Client + note over Client: Get withdraw address WA + note over Client: Construct TxW spending Tx0 output to address WA + note over Client: Compute TxW sighash + note over Client: Generate random r2, f + note over Client: Compute R2 + Client->>Server: /sign/first {statechain_id,auth_sig} + note over Server: Verify auth_sig with statechain_id and auth_key + Server->>Enclave: /get_public_nonce {statechain_id} + note over Enclave: Generate private_nonce + note over Enclave: Compute r1_public + Enclave-->>Server: {r1_public} + note over Server: Save public_nonce with ID statechain_id + Server-->>Client: {r1_public} + note over Client: Compute R and challenge + Client->>Server: /sign/second {statechain_id,challenge,auth_sig} + note over Server: Verify auth_sig with statechain_id and auth_key + note over Server: Save challenge with ID statechain_id + Server->>Enclave: /get_partial_signature {statechain_id, challenge} + note over Enclave: Compute blind partial signature + note over Enclave: Increment sig_count by 1 + Enclave-->>Server: {partial_signature} + Server-->>Client: {partial_signature} + note over Client: Compute full signature and add to TxW + note over Client: Broadcast TxW and wait for confirmation + Client->>Server: /withdraw/complete {auth_sig,statechain_id} + note over Server: Verify auth_sig with statechain_id and auth_key + note over Server: Remove statechain_id entry from DB and key list +``` diff --git a/enclave/.gitignore b/enclave/.gitignore new file mode 100644 index 00000000..ef345d26 --- /dev/null +++ b/enclave/.gitignore @@ -0,0 +1,40 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +.config_SIM_DEBUG_x64 +App/Enclave_u.c +App/Enclave_u.h +Enclave/Enclave_t.c +Enclave/Enclave_t.h +app +node.sealed_seed \ No newline at end of file diff --git a/enclave/App/App.cpp b/enclave/App/App.cpp new file mode 100644 index 00000000..226d7eda --- /dev/null +++ b/enclave/App/App.cpp @@ -0,0 +1,204 @@ +#include "App.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#include +#pragma GCC diagnostic pop +#include + +#include +#include +#include +#include +#include +#include +#include // for testing secp256k1-zkp. Can be removed after this. + +#include "../utils/include_secp256k1_zkp_lib.h" +#include "../utils/strencodings.h" +#include "utilities/utilities.h" +#include "database/db_manager.h" +#include "sealing_key_manager/sealing_key_manager.h" + +#include "endpoints/deposit.h" +#include "endpoints/secret.h" +#include "endpoints/sign.h" +#include "endpoints/transfer_receiver.h" +#include "endpoints/withdraw.h" + +#include "Enclave_u.h" +#include "sgx_urts.h" +#include "sgx_tcrypto.h" + +# define ENCLAVE_FILENAME "enclave.signed.so" + +/* ocall functions (untrusted) */ +void ocall_print_string(const char *str) +{ + printf("%s\n", str); +} + +void ocall_print_int(const char *str, const int *number) +{ + printf("%s%d\n", str, *number); +} + +void ocall_print_hex(const unsigned char** key, const int *keylen) +{ + printf("%s\n", key_to_string(*key, *keylen).c_str()); +} + +bool start_server(sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) +{ + crow::SimpleApp app; + + CROW_ROUTE(app, "/get_public_key") + .methods("POST"_method)([&enclave_id, &mutex_enclave_id, &sealing_key_manager](const crow::request& req) { + return endpoinDeposit::handleGetPublicKey(req, enclave_id, mutex_enclave_id, sealing_key_manager); + }); + + CROW_ROUTE(app, "/get_public_nonce") + .methods("POST"_method)([&enclave_id, &mutex_enclave_id, &sealing_key_manager](const crow::request& req) { + return endpointSignature::handleGetPublicNonce(req, enclave_id, mutex_enclave_id, sealing_key_manager); + }); + + CROW_ROUTE(app, "/get_partial_signature") + .methods("POST"_method)([&enclave_id, &mutex_enclave_id, &sealing_key_manager](const crow::request& req) { + return endpointSignature::handleGetPartialSignature(req, enclave_id, mutex_enclave_id, sealing_key_manager); + }); + + CROW_ROUTE(app,"/signature_count/") + ([](std::string statechain_id){ + return endpointSignature::signatureCount(statechain_id); + }); + + CROW_ROUTE(app, "/keyupdate") + .methods("POST"_method)([&enclave_id, &mutex_enclave_id, &sealing_key_manager](const crow::request& req) { + return endpointTransferReceiver::handleKeyUpdate(req, enclave_id, mutex_enclave_id, sealing_key_manager); + }); + + CROW_ROUTE(app,"/delete_statechain/") + .methods("DELETE"_method)([](std::string statechain_id){ + return endpointWithdraw::handleWithdraw(statechain_id); + }); + + CROW_ROUTE(app, "/add_mnemonic") + .methods("POST"_method)([&enclave_id, &mutex_enclave_id, &sealing_key_manager](const crow::request& req) { + return endpointSecret::handleAddMnemonic(req, enclave_id, mutex_enclave_id, sealing_key_manager); + }); + + CROW_ROUTE(app, "/get_ephemeral_public_key") + ([&sealing_key_manager](){ + return endpointSecret::getEphemeralPublicKey(sealing_key_manager); + }); + + CROW_ROUTE(app, "/add_secret") + .methods("POST"_method)([&enclave_id, &mutex_enclave_id, &sealing_key_manager](const crow::request& req) { + return endpointSecret::handleAddSecret(req, enclave_id, mutex_enclave_id, sealing_key_manager); + }); + + uint16_t server_port = 0; + + try { + server_port = utils::getEnclavePort(); + } catch (const std::exception& e) { + std::cerr << "Error enclave port: " << e.what() << std::endl; + return false; + } + + app.port(server_port).multithreaded().run(); + + return true; +} + +int SGX_CDECL main(int argc, char *argv[]) +{ + CLI::App app{"Lockbox Server"}; + app.set_version_flag("--version", std::string("0.0.1")); + + bool replicate_key = false; + bool generate_new_secret = false; + + // Add the options to the app + app.add_flag("-r,--replicate-key", replicate_key, "Replicate key"); + app.add_flag("-g,--generate-new-secret", generate_new_secret, "Generate a new secret"); + + // Parse the arguments + CLI11_PARSE(app, argc, argv); + + sgx_enclave_id_t enclave_id = 0; + std::mutex mutex_enclave_id; // protects map_aggregate_key_data + + { + const std::lock_guard lock(mutex_enclave_id); + + // initialize enclave + sgx_status_t enclave_created = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, NULL, NULL, &enclave_id, NULL); + if (enclave_created != SGX_SUCCESS) { + printf("Enclave init error\n"); + return -1; + } + } + + sealing_key_manager::SealingKeyManager sealing_key_manager; + if (sealing_key_manager.readSeedFromFile()) { + std::cout << "Seed loaded" << std::endl; + } else { + std::cout << "Seed not loaded" << std::endl; + } + + try { + sealing_key_manager.generateEphemeralKeys(enclave_id); + } catch (const std::runtime_error& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + if (generate_new_secret) { + const std::lock_guard lock(mutex_enclave_id); + + try { + if (sealing_key_manager.generateSecret(enclave_id)) { + std::cout << "New secret sucessfully generated." << std::endl; + } else { + std::cout << "Seed already exists. A new secret won't be generated." << std::endl; + } + + } catch (const std::runtime_error& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 2; + } + } + + if (replicate_key) { + try { + if (sealing_key_manager.replicateSecret(enclave_id)) { + std::cout << "Seed sucessfully replicated." << std::endl; + } else { + std::cout << "The other server refused replication. The seed must already exist." << std::endl; + } + } catch (const std::runtime_error& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 3; + } + } + + if (!start_server(enclave_id, mutex_enclave_id, sealing_key_manager)) { + std::cerr << "Error starting server." << std::endl; + return 4; + } + + { + const std::lock_guard lock(mutex_enclave_id); + + // destroy the enclave + sgx_destroy_enclave(enclave_id); + } + + return 0; +} diff --git a/enclave/App/App.h b/enclave/App/App.h new file mode 100644 index 00000000..a3fc0aba --- /dev/null +++ b/enclave/App/App.h @@ -0,0 +1,21 @@ +#ifndef _APP_H_ +#define _APP_H_ + +#include +#include +#include + +#include "sgx_error.h" /* sgx_status_t */ +#include "sgx_eid.h" /* sgx_enclave_id_t */ + +extern sgx_enclave_id_t global_eid; /* global enclave id */ + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* !_APP_H_ */ diff --git a/enclave/App/database/db_manager.cpp b/enclave/App/database/db_manager.cpp new file mode 100644 index 00000000..96fafacc --- /dev/null +++ b/enclave/App/database/db_manager.cpp @@ -0,0 +1,412 @@ +#include "db_manager.h" + +#include "../../utils/strencodings.h" +#include "../Enclave_u.h" +#include "../lib/toml.hpp" +#include "../utilities/utilities.h" +#include +#include +#include +#include +namespace db_manager { + + std::string getDatabaseConnectionString() { + const char* value = std::getenv("ENCLAVE_DATABASE_URL"); + + if (value == nullptr) { + auto config = toml::parse_file("Settings.toml"); + return config["intel_sgx"]["database_connection_string"].as_string()->get(); + } else { + return std::string(value); + } + } + + // Assumes the buffer is large enough. In a real application, ensure buffer safety. + void serialize(const chacha20_poly1305_encrypted_data* src, unsigned char* buffer, size_t* serialized_len) { + // Copy `data_len`, `nonce`, and `mac` directly + size_t offset = 0; + memcpy(buffer + offset, &src->data_len, sizeof(src->data_len)); + offset += sizeof(src->data_len); + + memcpy(buffer + offset, src->nonce, sizeof(src->nonce)); + offset += sizeof(src->nonce); + + memcpy(buffer + offset, src->mac, sizeof(src->mac)); + offset += sizeof(src->mac); + + // Now copy dynamic `data` + memcpy(buffer + offset, src->data, src->data_len); + offset += src->data_len; + + *serialized_len = offset; + } + + // Returns a newly allocated structure that must be freed by the caller. + bool deserialize(const unsigned char* buffer, chacha20_poly1305_encrypted_data* dest) { + + if (!dest) return false; + + size_t offset = 0; + memcpy(&dest->data_len, buffer + offset, sizeof(dest->data_len)); + offset += sizeof(dest->data_len); + + memcpy(dest->nonce, buffer + offset, sizeof(dest->nonce)); + offset += sizeof(dest->nonce); + + memcpy(dest->mac, buffer + offset, sizeof(dest->mac)); + offset += sizeof(dest->mac); + + dest->data = new unsigned char[dest->data_len]; + if (!dest->data) { + return false; // NULL; + } + memcpy(dest->data, buffer + offset, dest->data_len); + + return true; + } + + bool save_generated_public_key( + const chacha20_poly1305_encrypted_data& encrypted_keypair, + unsigned char* server_public_key, size_t server_public_key_size, + const std::string& statechain_id, + std::string& error_message) { + + auto database_connection_string = getDatabaseConnectionString(); + + try + { + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + std::string create_table_query = + "CREATE TABLE IF NOT EXISTS generated_public_key ( " + "id SERIAL PRIMARY KEY, " + "statechain_id varchar(50), " + "sealed_keypair BYTEA, " + "sealed_secnonce BYTEA, " + "public_nonce BYTEA, " + "public_key BYTEA UNIQUE, " + "sig_count INTEGER DEFAULT 0);"; + + pqxx::work txn(conn); + txn.exec(create_table_query); + txn.commit(); + + + size_t serialized_len = 0; + + size_t bufferSize = sizeof(encrypted_keypair.data_len) + sizeof(encrypted_keypair.nonce) + sizeof(encrypted_keypair.mac) + encrypted_keypair.data_len; + unsigned char* buffer = (unsigned char*) malloc(bufferSize); + + if (!buffer) { + error_message = "Failed to allocate memory for serialization!"; + return false; + } + + serialize(&encrypted_keypair, buffer, &serialized_len); + assert(serialized_len == bufferSize); + + std::basic_string_view sealed_data_view(reinterpret_cast(buffer), bufferSize); + std::basic_string_view public_key_data_view(reinterpret_cast(server_public_key), server_public_key_size); + + std::string insert_query = + "INSERT INTO generated_public_key (sealed_keypair, public_key, statechain_id) VALUES ($1, $2, $3);"; + pqxx::work txn2(conn); + + txn2.exec_params(insert_query, sealed_data_view, public_key_data_view, statechain_id); + txn2.commit(); + + conn.close(); + return true; + + return true; + } else { + error_message = "Failed to connect to the database!"; + return false; + } + } + catch (std::exception const &e) + { + error_message = e.what(); + return false; + } + + return true; + } + + bool load_generated_key_data( + const std::string& statechain_id, + std::unique_ptr& encrypted_keypair, + std::unique_ptr& encrypted_secnonce, + unsigned char* public_nonce, const size_t public_nonce_size, + std::string& error_message) + { + auto database_connection_string = getDatabaseConnectionString(); + + try + { + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + std::string sealed_keypair_query = + "SELECT sealed_keypair, sealed_secnonce, public_nonce FROM generated_public_key WHERE statechain_id = $1;"; + + pqxx::nontransaction ntxn(conn); + + conn.prepare("load_generated_key_data_query", sealed_keypair_query); + + pqxx::result result = ntxn.exec_prepared("load_generated_key_data_query", statechain_id); + + if (!result.empty()) { + auto sealed_keypair_field = result[0]["sealed_keypair"]; + auto sealed_secnonce_field = result[0]["sealed_secnonce"]; + auto public_nonce_field = result[0]["public_nonce"]; + + if (sealed_keypair_field.is_null()) { + encrypted_keypair.reset(); + } else if (encrypted_keypair != nullptr) { + auto sealed_keypair_view = sealed_keypair_field.as>(); + + std::vector sealed_keypair(sealed_keypair_view.size()); + memcpy(sealed_keypair.data(), sealed_keypair_view.data(), sealed_keypair_view.size()); + + if (!deserialize(sealed_keypair.data(), encrypted_keypair.get())) { + error_message = "Failed to deserialize keypair!"; + return false; + } + } + + if (sealed_secnonce_field.is_null()) { + encrypted_secnonce.reset(); + } else if (encrypted_secnonce != nullptr) { + auto sealed_secnonce_view = sealed_secnonce_field.as>(); + + std::vector sealed_secnonce(sealed_secnonce_view.size()); + memcpy(sealed_secnonce.data(), sealed_secnonce_view.data(), sealed_secnonce_view.size()); + + if (!deserialize(sealed_secnonce.data(), encrypted_secnonce.get())) { + error_message = "Failed to deserialize keypair!"; + return false; + } + } + + if (!public_nonce_field.is_null() && public_nonce != nullptr) { + auto public_nonce_view = public_nonce_field.as>(); + + if (public_nonce_view.size() != public_nonce_size) { + error_message = "Failed to retrieve public nonce. Different size than expected !"; + return false; + } + + memcpy(public_nonce, public_nonce_view.data(), public_nonce_size); + } + } + else { + error_message = "Failed to retrieve keypair. No data found !"; + return false; + } + + conn.close(); + return true; + } else { + error_message = "Failed to connect to the database!"; + return false; + } + } + catch (std::exception const &e) + { + error_message = e.what(); + return false; + } + } + + bool update_sealed_secnonce( + const std::string& statechain_id, + unsigned char* serialized_server_pubnonce, const size_t serialized_server_pubnonce_size, + const chacha20_poly1305_encrypted_data& encrypted_secnonce, + std::string& error_message) + { + auto database_connection_string = getDatabaseConnectionString(); + + try + { + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + size_t serialized_len = 0; + + size_t bufferSize = sizeof(encrypted_secnonce.data_len) + sizeof(encrypted_secnonce.nonce) + sizeof(encrypted_secnonce.mac) + encrypted_secnonce.data_len; + unsigned char* buffer = (unsigned char*) malloc(bufferSize); + + if (!buffer) { + error_message = "Failed to allocate memory for serialization!"; + return false; + } + + serialize(&encrypted_secnonce, buffer, &serialized_len); + assert(serialized_len == bufferSize); + + std::basic_string_view sealed_secnonce_view(reinterpret_cast(buffer), bufferSize); + std::basic_string_view serialized_server_pubnonce_view(reinterpret_cast(serialized_server_pubnonce), serialized_server_pubnonce_size); + + std::string updated_query = + "UPDATE generated_public_key SET public_nonce = $1, sealed_secnonce = $2 WHERE statechain_id = $3"; + pqxx::work txn(conn); + + txn.exec_params(updated_query, serialized_server_pubnonce_view, sealed_secnonce_view, statechain_id); + txn.commit(); + + conn.close(); + return true; + } else { + error_message = "Failed to connect to the database!"; + return false; + } + } + catch (std::exception const &e) + { + error_message = e.what(); + return false; + } + } + + bool update_sig_count(const std::string& statechain_id) + { + auto database_connection_string = getDatabaseConnectionString(); + + try + { + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + std::string update_query = + "UPDATE generated_public_key SET sig_count = sig_count + 1 WHERE statechain_id = $1;"; + pqxx::work txn(conn); + + txn.exec_params(update_query, statechain_id); + txn.commit(); + + conn.close(); + return true; + } else { + return false; + } + } + catch (std::exception const &e) + { + return false; + } + } + + bool signature_count(const std::string& statechain_id, int& sig_count) { + + auto database_connection_string = getDatabaseConnectionString(); + + try + { + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + std::string sig_count_query = + "SELECT sig_count FROM generated_public_key WHERE statechain_id = $1;"; + + pqxx::nontransaction ntxn(conn); + + conn.prepare("sig_count_query", sig_count_query); + + pqxx::result result = ntxn.exec_prepared("sig_count_query", statechain_id); + + if (!result.empty()) { + auto sig_count_field = result[0]["sig_count"]; + if (!sig_count_field.is_null()) { + sig_count = sig_count_field.as(); + return true; + } + } + + conn.close(); + return true; + } else { + return false; + } + } + catch (std::exception const &e) + { + return false; + } + } + + bool update_sealed_keypair( + const chacha20_poly1305_encrypted_data& encrypted_keypair, + unsigned char* server_public_key, size_t server_public_key_size, + const std::string& statechain_id, + std::string& error_message) + { + auto database_connection_string = getDatabaseConnectionString(); + + try + { + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + std::string insert_query = + "UPDATE generated_public_key " + "SET sealed_keypair = $1, public_key = $2, sealed_secnonce = NULL, public_nonce = NULL " + "WHERE statechain_id = $3;"; + pqxx::work txn2(conn); + + size_t serialized_len = 0; + + size_t bufferSize = sizeof(encrypted_keypair.data_len) + sizeof(encrypted_keypair.nonce) + sizeof(encrypted_keypair.mac) + encrypted_keypair.data_len; + unsigned char* buffer = (unsigned char*) malloc(bufferSize); + + if (!buffer) { + error_message = "Failed to allocate memory for serialization!"; + return false; + } + + serialize(&encrypted_keypair, buffer, &serialized_len); + assert(serialized_len == bufferSize); + + std::basic_string_view sealed_data_view(reinterpret_cast(buffer), bufferSize); + std::basic_string_view public_key_data_view(reinterpret_cast(server_public_key), server_public_key_size); + + txn2.exec_params(insert_query, sealed_data_view, public_key_data_view, statechain_id); + txn2.commit(); + + conn.close(); + return true; + } else { + error_message = "Failed to connect to the database!"; + return false; + } + } + catch (std::exception const &e) + { + error_message = e.what(); + return false; + } + } + + bool delete_statechain(const std::string& statechain_id) { + auto database_connection_string = getDatabaseConnectionString(); + + std::string error_message; + pqxx::connection conn(database_connection_string); + if (conn.is_open()) { + + std::string delete_comm = + "DELETE FROM generated_public_key WHERE statechain_id = $1;"; + pqxx::work txn2(conn); + + txn2.exec_params(delete_comm, statechain_id); + txn2.commit(); + + conn.close(); + + return true; + } else { + return false; + } + } +} \ No newline at end of file diff --git a/enclave/App/database/db_manager.h b/enclave/App/database/db_manager.h new file mode 100644 index 00000000..3ff33777 --- /dev/null +++ b/enclave/App/database/db_manager.h @@ -0,0 +1,48 @@ +#pragma once + +#ifndef DB_MANAGER_H +#define DB_MANAGER_H + +#include "../Enclave_u.h" +#include +#include + +namespace db_manager { + + void serialize(const chacha20_poly1305_encrypted_data* src, unsigned char* buffer, size_t* serialized_len); + + bool deserialize(const unsigned char* buffer, chacha20_poly1305_encrypted_data* dest); + + bool save_generated_public_key( + const chacha20_poly1305_encrypted_data& encrypted_keypair, + unsigned char* server_public_key, size_t server_public_key_size, + const std::string& statechain_id, + std::string& error_message); + + bool load_generated_key_data( + const std::string& statechain_id, + std::unique_ptr& encrypted_keypair, + std::unique_ptr& encrypted_secnonce, + unsigned char* public_nonce, const size_t public_nonce_size, + std::string& error_message); + + bool update_sealed_secnonce( + const std::string& statechain_id, + unsigned char* serialized_server_pubnonce, const size_t serialized_server_pubnonce_size, + const chacha20_poly1305_encrypted_data& encrypted_secnonce, + std::string& error_message); + + bool update_sig_count(const std::string& statechain_id); + + bool signature_count(const std::string& statechain_id, int& sig_count); + + bool update_sealed_keypair( + const chacha20_poly1305_encrypted_data& encrypted_keypair, + unsigned char* server_public_key, size_t server_public_key_size, + const std::string& statechain_id, + std::string& error_message); + + bool delete_statechain(const std::string& statechain_id); +} + +#endif // DB_MANAGER_H \ No newline at end of file diff --git a/enclave/App/endpoints/deposit.cpp b/enclave/App/endpoints/deposit.cpp new file mode 100644 index 00000000..bfccede1 --- /dev/null +++ b/enclave/App/endpoints/deposit.cpp @@ -0,0 +1,35 @@ +#include "deposit.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" +#include +#include "statechain/deposit.h" + +namespace endpoinDeposit { + crow::response handleGetPublicKey(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) { + if (sealing_key_manager.isSeedEmpty()) { + return crow::response(500, "Sealing key is empty."); + } + + auto req_body = crow::json::load(req.body); + if (!req_body) + return crow::response(400); + + if (req_body.count("statechain_id") == 0) + return crow::response(400, "Invalid parameter. It must be 'statechain_id'."); + + std::string statechain_id = req_body["statechain_id"].s(); + + const std::lock_guard lock(mutex_enclave_id); + + return deposit::get_public_key(enclave_id, statechain_id, sealing_key_manager); + } +} // namespace deposit diff --git a/enclave/App/endpoints/deposit.h b/enclave/App/endpoints/deposit.h new file mode 100644 index 00000000..eba25ad5 --- /dev/null +++ b/enclave/App/endpoints/deposit.h @@ -0,0 +1,21 @@ +#pragma once + +#ifndef ENDPOINT_DEPOSIT_H +#define ENDPOINT_DEPOSIT_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" + +namespace endpoinDeposit { + crow::response handleGetPublicKey(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager); +} // namespace endpoinDeposit + +#endif // ENDPOINT_DEPOSIT_H diff --git a/enclave/App/endpoints/secret.cpp b/enclave/App/endpoints/secret.cpp new file mode 100644 index 00000000..b1729e37 --- /dev/null +++ b/enclave/App/endpoints/secret.cpp @@ -0,0 +1,90 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../../utils/strencodings.h" +#include "../sealing_key_manager/sealing_key_manager.h" + +namespace endpointSecret { + crow::response handleAddMnemonic(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) { + + auto req_body = crow::json::load(req.body); + if (!req_body) { + return crow::response(400); + } + + if (req_body.count("mnemonic") == 0 || + req_body.count("password") == 0 || + req_body.count("index") == 0 || + req_body.count("threshold") == 0) { + return crow::response(400, "Invalid parameters. They must be 'mnemonic', 'password', 'index' and 'threshold'."); + } + + std::string mnemonic = req_body["mnemonic"].s(); + std::string password = req_body["password"].s(); + int64_t index = req_body["index"].i(); + int64_t threshold = req_body["threshold"].i(); + + const std::lock_guard lock(mutex_enclave_id); + + auto ret = sealing_key_manager.addMnemonic(enclave_id, mnemonic, password, (size_t) index, (size_t) threshold); + + if (!ret.success) { + return crow::response(ret.code, ret.message); + } + + crow::json::wvalue result({{"message", "OK."}}); + return crow::response{result}; + } + + crow::response handleAddSecret(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) { + + auto req_body = crow::json::load(req.body); + if (!req_body) { + return crow::response(400); + } + + if (req_body.count("encrypted_secret_key") == 0 || + req_body.count("sender_public_key") == 0) { + return crow::response(400, "Invalid parameters. They must be 'encrypted_secret_key', 'sender_public_key'."); + } + + std::string encrypted_secret_key = req_body["encrypted_secret_key"].s(); + std::string sender_public_key = req_body["sender_public_key"].s(); + + const std::lock_guard lock(mutex_enclave_id); + + auto isKeyAdded = false; + + try { + isKeyAdded = sealing_key_manager.addSecret(enclave_id, encrypted_secret_key, sender_public_key); + } catch (const std::exception& e) { + std::cerr << "Failed to add secret: " << e.what() << std::endl; + return crow::response(500, e.what()); + } + + if (!isKeyAdded) { + return crow::response(400, "Seed already exists. Cannot add a new secret."); + } + + crow::json::wvalue result({{"message", "Secret added successfully."}}); + return crow::response{result}; + } + + crow::response getEphemeralPublicKey(sealing_key_manager::SealingKeyManager& sealing_key_manager) { + + if (!sealing_key_manager.isSeedEmpty()) { + return crow::response(401, "Seed already exists. No need for replication."); + } + + auto public_key_hex = key_to_string(sealing_key_manager.ephemeral_exchange_public_key, sizeof(sealing_key_manager.ephemeral_exchange_public_key)); + + crow::json::wvalue result({{"ephemeral_public_key", public_key_hex}}); + return crow::response{result}; + } +} // namespace endpointSecret \ No newline at end of file diff --git a/enclave/App/endpoints/secret.h b/enclave/App/endpoints/secret.h new file mode 100644 index 00000000..dc411f72 --- /dev/null +++ b/enclave/App/endpoints/secret.h @@ -0,0 +1,23 @@ +#pragma once + +#ifndef ENDPOINT_SECRET_H +#define ENDPOINT_SECRET_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" + +namespace endpointSecret { + crow::response handleAddMnemonic(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager); + crow::response handleAddSecret(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager); + crow::response getEphemeralPublicKey(sealing_key_manager::SealingKeyManager& sealing_key_manager); +} // namespace endpointSecret + +#endif // ENDPOINT_SECRET_H \ No newline at end of file diff --git a/enclave/App/endpoints/sign.cpp b/enclave/App/endpoints/sign.cpp new file mode 100644 index 00000000..7b2f9373 --- /dev/null +++ b/enclave/App/endpoints/sign.cpp @@ -0,0 +1,78 @@ +#include "sign.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" +#include "../../utils/strencodings.h" +#include +#include "statechain/sign.h" + +namespace endpointSignature { + crow::response handleGetPublicNonce(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) { + + if (sealing_key_manager.isSeedEmpty()) { + return crow::response(500, "Sealing key is empty."); + } + + auto req_body = crow::json::load(req.body); + if (!req_body) + return crow::response(400); + + if (req_body.count("statechain_id") == 0) { + return crow::response(400, "Invalid parameters. They must be 'statechain_id'."); + } + + std::string statechain_id = req_body["statechain_id"].s(); + + const std::lock_guard lock(mutex_enclave_id); + + return signature::get_public_nonce(enclave_id, statechain_id, sealing_key_manager); + } + + crow::response handleGetPartialSignature(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) { + + if (sealing_key_manager.isSeedEmpty()) { + return crow::response(500, "Sealing key is empty."); + } + + auto req_body = crow::json::load(req.body); + if (!req_body) + return crow::response(400); + + if (req_body.count("statechain_id") == 0 || + req_body.count("negate_seckey") == 0 || + req_body.count("session") == 0) { + return crow::response(400, "Invalid parameters. They must be 'statechain_id', 'negate_seckey' and 'session'."); + } + + std::string statechain_id = req_body["statechain_id"].s(); + int64_t negate_seckey = req_body["negate_seckey"].i(); + std::string session_hex = req_body["session"].s(); + + + if (session_hex.substr(0, 2) == "0x") { + session_hex = session_hex.substr(2); + } + + std::vector serialized_session = ParseHex(session_hex); + + if (serialized_session.size() != 133) { + return crow::response(400, "Invalid session length. Must be 133 bytes!"); + } + + const std::lock_guard lock(mutex_enclave_id); + + return signature::get_partial_signature(enclave_id, statechain_id, negate_seckey, serialized_session, sealing_key_manager); + } + + crow::response signatureCount(const std::string& statechain_id) { + return signature::signature_count(statechain_id); + } +} // namespace deposit diff --git a/enclave/App/endpoints/sign.h b/enclave/App/endpoints/sign.h new file mode 100644 index 00000000..81b67f76 --- /dev/null +++ b/enclave/App/endpoints/sign.h @@ -0,0 +1,24 @@ +#pragma once + +#ifndef ENDPOINT_SIGN_H +#define ENDPOINT_SIGN_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" +#include + +namespace endpointSignature { + crow::response handleGetPublicNonce(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager); + crow::response handleGetPartialSignature(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager); + crow::response signatureCount(const std::string& statechain_id); +} // namespace endpointSignature + +#endif // ENDPOINT_SIGN_H diff --git a/enclave/App/endpoints/transfer_receiver.cpp b/enclave/App/endpoints/transfer_receiver.cpp new file mode 100644 index 00000000..ebaee36e --- /dev/null +++ b/enclave/App/endpoints/transfer_receiver.cpp @@ -0,0 +1,68 @@ +#include "transfer_receiver.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" +#include "../../utils/strencodings.h" +#include +#include "statechain/transfer_receiver.h" + +namespace endpointTransferReceiver { + crow::response handleKeyUpdate(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager) { + + if (sealing_key_manager.isSeedEmpty()) { + return crow::response(500, "Sealing key is empty."); + } + + auto req_body = crow::json::load(req.body); + if (!req_body) + return crow::response(400); + + if (req_body.count("statechain_id") == 0 || + req_body.count("t2") == 0 || + req_body.count("x1") == 0) { + return crow::response(400, "Invalid parameters. They must be 'statechain_id', 't2' and 'x1'."); + } + + std::string statechain_id = req_body["statechain_id"].s(); + std::string t2_hex = req_body["t2"].s(); + std::string x1_hex = req_body["x1"].s(); + + if (t2_hex.substr(0, 2) == "0x") { + t2_hex = t2_hex.substr(2); + } + + std::vector serialized_t2 = ParseHex(t2_hex); + + if (serialized_t2.size() != 32) { + return crow::response(400, "Invalid t2 length. Must be 32 bytes!"); + } + + if (x1_hex.substr(0, 2) == "0x") { + x1_hex = x1_hex.substr(2); + } + + std::vector serialized_x1 = ParseHex(x1_hex); + + if (serialized_x1.size() != 32) { + return crow::response(400, "Invalid x1 length. Must be 32 bytes!"); + } + + const std::lock_guard lock(mutex_enclave_id); + + return transfer_receiver::keyupdate( + enclave_id, + statechain_id, + serialized_t2, + serialized_x1, + sealing_key_manager + ); + } +} // namespace endpointTransferReceiver \ No newline at end of file diff --git a/enclave/App/endpoints/transfer_receiver.h b/enclave/App/endpoints/transfer_receiver.h new file mode 100644 index 00000000..20d02838 --- /dev/null +++ b/enclave/App/endpoints/transfer_receiver.h @@ -0,0 +1,21 @@ +#pragma once + +#ifndef ENDPOINT_RECEIVER_H +#define ENDPOINT_RECEIVER_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" + +namespace endpointTransferReceiver { + crow::response handleKeyUpdate(const crow::request& req, sgx_enclave_id_t& enclave_id, std::mutex& mutex_enclave_id, sealing_key_manager::SealingKeyManager& sealing_key_manager); +} // namespace endpointTransferReceiver + +#endif // ENDPOINT_RECEIVER_H \ No newline at end of file diff --git a/enclave/App/endpoints/withdraw.cpp b/enclave/App/endpoints/withdraw.cpp new file mode 100644 index 00000000..8c63da94 --- /dev/null +++ b/enclave/App/endpoints/withdraw.cpp @@ -0,0 +1,23 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../database/db_manager.h" +#include "../sealing_key_manager/sealing_key_manager.h" + +namespace endpointWithdraw { + crow::response handleWithdraw(const std::string& statechain_id) { + + if (db_manager::delete_statechain(statechain_id)) { + return crow::response(200, "Statechain deleted."); + } else { + return crow::response(500, "Failed to connect to the database and delete statechain."); + } + + } +} // namespace endpointWithdraw \ No newline at end of file diff --git a/enclave/App/endpoints/withdraw.h b/enclave/App/endpoints/withdraw.h new file mode 100644 index 00000000..a0c56514 --- /dev/null +++ b/enclave/App/endpoints/withdraw.h @@ -0,0 +1,21 @@ +#pragma once + +#ifndef ENDPOINT_WITHDRAW_H +#define ENDPOINT_WITHDRAW_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include "../sealing_key_manager/sealing_key_manager.h" + +namespace endpointWithdraw { + crow::response handleWithdraw(const std::string& statechain_id); +} // namespace endpointWithdraw + +#endif // ENDPOINT_WITHDRAW_H \ No newline at end of file diff --git a/enclave/App/lib/CLI11.hpp b/enclave/App/lib/CLI11.hpp new file mode 100644 index 00000000..8a5b4c54 --- /dev/null +++ b/enclave/App/lib/CLI11.hpp @@ -0,0 +1,10998 @@ +// CLI11: Version 2.4.2 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.4.2 +// +// CLI11 2.4.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// Standard combined includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.4.2" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + +/** availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + + +namespace CLI { + + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +CLI11_INLINE std::vector split(const std::string &s, char delim); + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +CLI11_INLINE std::string <rim(std::string &str); + +/// Trim anything from left of string +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); + +/// Trim whitespace from right of string +CLI11_INLINE std::string &rtrim(std::string &str); + +/// Trim anything from right of string +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +CLI11_INLINE std::string &remove_quotes(std::string &str); + +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid); + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} + +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags); + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); + +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); + +} // namespace detail + + + + +namespace detail { +CLI11_INLINE std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +CLI11_INLINE std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; +} + +} // namespace detail + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } + + CLI11_NODISCARD std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return {"Requires at least " + std::to_string(min_option) + " options used but only " + + std::to_string(used) + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } + if(max_option == 1) + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; + + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { + using type = void; +}; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { + using type = T; +}; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { + using type = std::string; +}; + +namespace adl_detail { +/// Check for existence of user-supplied lexical_cast. +/// +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). +template class is_lexical_castable { + template + static auto test(int) -> decltype(lexical_cast(std::declval(), std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; +} // namespace adl_detail + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { + using type = T; +}; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { + using type = typename element_type::type::value_type; +}; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else +#pragma diag_suppress 2361 +#endif +#endif + TT{std::declval()} +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else +#pragma diag_default 2361 +#endif +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); // NOLINT(google-readability-casting) +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return {}; +} + +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return {"[" + detail::join(defaults) + "]"}; +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { + static const int value{0}; +}; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret = 0; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + errno = EINVAL; + return -1; + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + return integral_conversion(input, output); +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { + output = (out > 0); + } else if(errno == ERANGE) { + output = (input[0] != '-'); + } else { + return false; + } + return true; +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } + return false; +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val = 0.0; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value && + is_istreamable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return from_stream(input, output); +} + +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a +/// user-supplied lexical_cast overload. +template ::value == object_category::other && !std::is_assignable::value && + !is_istreamable::value && !adl_detail::is_lexical_castable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { + static_assert(!std::is_same::value, // Can't just write false here. + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return false; +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } + return lexical_assign(strings[0], output); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; +} + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input); + +} // namespace detail + + + +namespace detail { + +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE std::vector split_names(std::string current) { + std::vector output; + std::size_t val = 0; + while((val = current.find(',')) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else if(name.length() > 2) + throw BadNameString::MissingDash(name); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(!pos_name.empty()) + throw BadNameString::MultiPositionalNames(name); + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } + } + } + return std::make_tuple(short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + /// Listing of inputs + std::vector inputs{}; + + /// The list of parents and name joined by "." + CLI11_NODISCARD std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + CLI11_NODISCARD std::vector from_file(const std::string &name) const { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters and literal strings + char literalQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { + stringQuote = qString; + literalQuote = literalChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + CLI11_NODISCARD const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + CLI11_NODISCARD int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + Validator(std::string validator_desc, std::function func) + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const; + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator description(std::string validator_desc) const; + + /// Generate type description information for the Validator + CLI11_NODISCARD std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + CLI11_NODISCARD const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + CLI11_NODISCARD Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + CLI11_NODISCARD Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + CLI11_NODISCARD int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + CLI11_NODISCARD bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const; + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const; + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const; + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); +}; + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator(); +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator(); +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator(); +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator(); +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +namespace detail { +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail +/// @} + + + + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + std::size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + CLI11_NODISCARD std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + return labels_.at(key); + } + + /// Get the current column width + CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + CLI11_NODISCARD virtual std::string + make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr