diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b0ca26..5275ba9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,12 +17,6 @@ on: tagged-release: required: true type: boolean - python-version-1: - required: true - type: string - python-version-2: - required: true - type: string jobs: build: @@ -33,22 +27,24 @@ jobs: bitcoind-version: ["31.0"] experimental: [1] deprecated: [0] - python-version: [ "${{ inputs.python-version-1 }}", "${{ inputs.python-version-2 }}"] + python-version: ["3.10", "3.14"] os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@v6 - name: Create cache paths run: | - sudo mkdir -p /usr/local/libexec/c-lightning/plugins + sudo mkdir /usr/local/libexec + sudo mkdir /usr/local/libexec/c-lightning + sudo mkdir /usr/local/libexec/c-lightning/plugins sudo chown -R $USER /usr/local/libexec - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v7 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -64,7 +60,7 @@ jobs: - name: Restore CLN cache id: cache-cln - uses: actions/cache/restore@v6 + uses: actions/cache/restore@v5 with: path: | /usr/local/bin/lightning* @@ -73,7 +69,7 @@ jobs: - name: Restore bitcoind cache id: cache-bitcoind - uses: actions/cache/restore@v6 + uses: actions/cache/restore@v5 with: path: /usr/local/bin/bitcoin* key: cache-bitcoind-${{ matrix.bitcoind-version }}-${{ steps.exact_versions.outputs.os_version }} @@ -97,7 +93,7 @@ jobs: - name: Save bitcoind cache id: save-cache-bitcoind if : ${{ success() && steps.cache-bitcoind.outputs.cache-hit != 'true'}} - uses: actions/cache/save@v6 + uses: actions/cache/save@v5 with: path: /usr/local/bin/bitcoin* key: ${{ steps.cache-bitcoind.outputs.cache-primary-key }} @@ -115,7 +111,7 @@ jobs: - name: Save CLN cache id: save-cache-cln if : ${{ success() && steps.cache-cln.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v6 + uses: actions/cache/save@v5 with: path: | /usr/local/bin/lightning* @@ -123,21 +119,25 @@ jobs: key: ${{ steps.cache-cln.outputs.cache-primary-key }} - name: Set up Rust - if: ${{ inputs.tagged-release == false }} + if: ${{ inputs.tagged-release == false}} uses: dtolnay/rust-toolchain@stable + - name: Set up protoc + if: ${{ inputs.tagged-release == false}} + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install cargo-binstall - if: ${{ inputs.tagged-release == false }} + if: ${{ inputs.tagged-release == false}} uses: taiki-e/install-action@cargo-binstall - name: Install cargo-msrv - if: ${{ inputs.tagged-release == false }} - env: - GITHUB_TOKEN: ${{ github.token }} + if: ${{ inputs.tagged-release == false}} run: cargo binstall --no-confirm cargo-msrv - name: Verify MSRV - if: ${{ inputs.tagged-release == false }} + if: ${{ inputs.tagged-release == false}} run: cargo msrv verify - name: Get plugin binary @@ -151,13 +151,6 @@ jobs: uv run python -m grpc_tools.protoc --proto_path="proto" --python_out="tests" --grpc_python_out="tests" proto/*.proto fi - url=$(curl -f --retry 3 --retry-delay 3 -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/BoltzExchange/hold/releases/latest \ - | jq '.assets[] | select(.name | endswith("linux-amd64.tar.gz")) | .browser_download_url' \ - | tr -d '\"') - wget $url - tar -xvf ${url##*/} -C tests - mv "tests/build/hold-linux-amd64" "tests/hold" - cargo build cargo test fi diff --git a/.github/workflows/latest_v25.05.yml b/.github/workflows/latest_v25.05.yml new file mode 100644 index 0000000..b5502c9 --- /dev/null +++ b/.github/workflows/latest_v25.05.yml @@ -0,0 +1,14 @@ +name: Latest release on CLN v25.05.x + +on: + release: + types: [published, edited] + workflow_dispatch: + +jobs: + call-ci: + uses: ./.github/workflows/ci.yml + with: + cln-version: "v25.05" + pyln-version: "25.5" + tagged-release: true \ No newline at end of file diff --git a/.github/workflows/latest_v25.09.yml b/.github/workflows/latest_v25.09.yml index d96d48d..2926b68 100644 --- a/.github/workflows/latest_v25.09.yml +++ b/.github/workflows/latest_v25.09.yml @@ -9,8 +9,6 @@ jobs: call-ci: uses: ./.github/workflows/ci.yml with: - cln-version: "v25.09.3" - pyln-version: "25.9.3" + cln-version: "v25.09.2" + pyln-version: "25.9.2" tagged-release: true - python-version-1: "3.10" - python-version-2: "3.12" diff --git a/.github/workflows/latest_v25.12.yml b/.github/workflows/latest_v25.12.yml index 3a86b18..268f5f6 100644 --- a/.github/workflows/latest_v25.12.yml +++ b/.github/workflows/latest_v25.12.yml @@ -9,8 +9,6 @@ jobs: call-ci: uses: ./.github/workflows/ci.yml with: - cln-version: "v25.12.1" - pyln-version: "25.12.1" - tagged-release: true - python-version-1: "3.10" - python-version-2: "3.12" + cln-version: "v25.12" + pyln-version: "25.12" + tagged-release: true \ No newline at end of file diff --git a/.github/workflows/latest_v26.04.yml b/.github/workflows/latest_v26.04.yml index a0dc3a0..f545ca4 100644 --- a/.github/workflows/latest_v26.04.yml +++ b/.github/workflows/latest_v26.04.yml @@ -12,5 +12,3 @@ jobs: cln-version: "v26.04" pyln-version: "26.4" tagged-release: true - python-version-1: "3.10" - python-version-2: "3.12" diff --git a/.github/workflows/latest_v26.06.yml b/.github/workflows/latest_v26.06.yml deleted file mode 100644 index 27edc2d..0000000 --- a/.github/workflows/latest_v26.06.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Latest release on CLN v26.06.x - -on: - release: - types: [published, edited] - workflow_dispatch: - -jobs: - call-ci: - uses: ./.github/workflows/ci.yml - with: - cln-version: "v26.06.6" - pyln-version: "26.6.6" - tagged-release: true - python-version-1: "3.10" - python-version-2: "3.14" diff --git a/.github/workflows/master_v26.06.yml b/.github/workflows/master_v25.05.yml similarity index 70% rename from .github/workflows/master_v26.06.yml rename to .github/workflows/master_v25.05.yml index 25fe3a8..dfd753c 100644 --- a/.github/workflows/master_v26.06.yml +++ b/.github/workflows/master_v25.05.yml @@ -1,4 +1,4 @@ -name: master on CLN v26.06.x +name: master on CLN v25.05.x on: push: @@ -19,8 +19,6 @@ jobs: call-ci: uses: ./.github/workflows/ci.yml with: - cln-version: "v26.06.6" - pyln-version: "26.6.6" + cln-version: "v25.05" + pyln-version: "25.5" tagged-release: false - python-version-1: "3.10" - python-version-2: "3.14" diff --git a/.github/workflows/master_v25.09.yml b/.github/workflows/master_v25.09.yml index 1a73ecd..7b62c39 100644 --- a/.github/workflows/master_v25.09.yml +++ b/.github/workflows/master_v25.09.yml @@ -19,8 +19,6 @@ jobs: call-ci: uses: ./.github/workflows/ci.yml with: - cln-version: "v25.09.3" - pyln-version: "25.9.3" + cln-version: "v25.09.2" + pyln-version: "25.9.2" tagged-release: false - python-version-1: "3.10" - python-version-2: "3.12" diff --git a/.github/workflows/master_v25.12.yml b/.github/workflows/master_v25.12.yml index 7e0ae62..5df85d3 100644 --- a/.github/workflows/master_v25.12.yml +++ b/.github/workflows/master_v25.12.yml @@ -19,8 +19,6 @@ jobs: call-ci: uses: ./.github/workflows/ci.yml with: - cln-version: "v25.12.1" - pyln-version: "25.12.1" + cln-version: "v25.12" + pyln-version: "25.12" tagged-release: false - python-version-1: "3.10" - python-version-2: "3.12" diff --git a/.github/workflows/master_v26.04.yml b/.github/workflows/master_v26.04.yml index 82715be..5fcd616 100644 --- a/.github/workflows/master_v26.04.yml +++ b/.github/workflows/master_v26.04.yml @@ -22,5 +22,3 @@ jobs: cln-version: "v26.04" pyln-version: "26.4" tagged-release: false - python-version-1: "3.10" - python-version-2: "3.12" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39f2ae2..56b76c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: rust-version: ${{ steps.versions.outputs.rust-version }} steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -25,6 +25,12 @@ jobs: sudo apt update sudo apt install -y gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf + - name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: "21.12" + - name: Build Linux binaries (release) run: | cargo build --profile optimized --locked --target x86_64-unknown-linux-gnu @@ -57,13 +63,19 @@ jobs: macos-deployment-target: ${{ steps.build.outputs.macos-deployment-target }} steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: targets: x86_64-apple-darwin, aarch64-apple-darwin + - name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: "21.12" + - name: Build macOS universal (release) id: build env: @@ -135,7 +147,7 @@ jobs: id: tag_name run: echo "current_version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@v6 - name: Get Changelog Entry id: changelog_reader uses: mindsers/changelog-reader-action@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index e77df0d..6eb4680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,5 @@ # Changelog -## [0.2.0] 2026-08-09 - -### Added -- holdinvoice methods: ``make_hold_invoice``, ``cancel_hold_invoice``, ``settle_hold_invoice`` -- holdinvoice notification: ``hold_invoice_accepted`` - -### Changed -- there are now five default relays that will be used if ``nip47-relays`` is not set -- `pay_keysend` will use the new CLN `xkeysend` command using CLN v26.06+ -- Budget is updated on demand instead of by a background task -- `list_transactions` is bounded (at most 500 transactions and ~128kB response) to prevent DoS -- Upgrade ``nostr_sdk`` and CLN dependencies - -### Removed -- ``multi_pay_invoice`` and ``multi_pay_keysend``, they were removed from the spec - -### Fixed -- ``nip47-create`` no longer fails if the NWC fails to start, the error is only logged, since the NWC at that point is already persisted to the DB -- Paying amount-less invoices with a request amount -- Legacy keysend TLV byte decoding -- Report the correct error when the payment route is too expensive -- Notifications are sent per client, so one failing client no longer prevents the others from receiving them -- Available payment methods are now determined from the node's RPC instead of it's (custom) version string -- Deduplication of request events instead of filtering unreliably - ## [0.1.9] 2026-04-23 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index a6393f7..99c47f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,41 +25,30 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.104" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arrayvec" -version = "0.7.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-utility" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "188f83b9a198af8c336e505611edb00d6d2ac5c694241c5a4f9a12316938cfe9" +checksum = "a34a3b57207a7a1007832416c3e4862378c8451b4e8e093e436f48c2d3d2c151" dependencies = [ "futures-util", "gloo-timers", @@ -69,37 +58,37 @@ dependencies = [ [[package]] name = "async-wsocket" -version = "0.17.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c713e1f14c7b82e32ea159af1c6e2f070cfadbdf23fb2512acce9af0a26f1a2" +checksum = "1c92385c7c8b3eb2de1b78aeca225212e4c9a69a78b802832759b108681a5069" dependencies = [ + "async-utility", "futures", "futures-util", "js-sys", "tokio", - "tokio-happy-eyeballs", "tokio-rustls", "tokio-socks", "tokio-tungstenite", "url", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "atomic-destructor" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4" [[package]] name = "base58ck" -version = "0.1.101" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin_hashes 0.14.101", + "bitcoin-internals", + "bitcoin_hashes", ] [[package]] @@ -108,110 +97,88 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bech32" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" -[[package]] -name = "bech32" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8" - [[package]] name = "bip39" version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "bitcoin_hashes 0.14.101", + "bitcoin_hashes", "serde", "unicode-normalization", ] [[package]] name = "bitcoin" -version = "0.32.102" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0ce8bd5baaa0d303a19915a6d93afed161f528654e42da2a7a97d05c59499a" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", - "bech32 0.11.1", + "bech32", + "bitcoin-internals", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes 0.14.101", - "hex-conservative 0.2.2", + "bitcoin_hashes", + "hex-conservative", "hex_lit", - "secp256k1 0.29.1", - "serde", -] - -[[package]] -name = "bitcoin-consensus-encoding" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" -dependencies = [ - "bitcoin-internals", - "hex-conservative 1.2.0", + "secp256k1", "serde", ] [[package]] name = "bitcoin-internals" -version = "0.6.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] [[package]] name = "bitcoin-io" -version = "0.1.101" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" -dependencies = [ - "bitcoin-consensus-encoding", -] +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-units" -version = "0.1.101" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cb95693f371d089a4b5b6fc41c6f3ea6e01ee8c15388335dfac8ea685173b51" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-consensus-encoding", + "bitcoin-internals", "serde", ] [[package]] name = "bitcoin_hashes" -version = "0.14.101" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.2", - "serde", -] - -[[package]] -name = "bitcoin_hashes" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e" -dependencies = [ - "bitcoin-consensus-encoding", - "bitcoin-internals", - "hex-conservative 1.2.0", + "hex-conservative", "serde", ] [[package]] name = "bitflags" -version = "2.13.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -233,21 +200,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.3" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" -version = "1.12.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cbc" @@ -260,9 +221,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.2" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "shlex", @@ -311,35 +272,28 @@ dependencies = [ [[package]] name = "cln-nip47" -version = "0.2.0" +version = "0.1.9" dependencies = [ "anyhow", "cln-plugin", "cln-rpc", - "futures", "hex", "log", "log-panics", - "nostr", "nostr-sdk", "parking_lot", - "prost", - "protoc-bin-vendored", "regex", "serde", "serde_json", "tokio", - "tonic", - "tonic-prost", - "tonic-prost-build", "uuid", ] [[package]] name = "cln-plugin" -version = "0.7.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a4bb9c7335040d49c51900305b91155303539e9f54839bb194e915277bf882" +checksum = "c340e01d74b553f761dd5636b8238cb92cacdf7d9d27a8820dad42da76ce0cb5" dependencies = [ "anyhow", "bytes", @@ -356,9 +310,9 @@ dependencies = [ [[package]] name = "cln-rpc" -version = "0.7.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d9f5e26f0a2713ea1393eb3117c148295fa4c5f7db529713b20323ca7cc37aa" +checksum = "d8c63eb175d58f0212e35e427237cf3e4f856f47ec7b8ad95110484d292fc819" dependencies = [ "anyhow", "bitcoin", @@ -388,14 +342,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] [[package]] name = "data-encoding" -version = "2.11.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "digest" @@ -405,24 +360,25 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] name = "displaydoc" -version = "0.2.7" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "either" -version = "1.17.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" @@ -430,49 +386,11 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "faster-hex" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" -dependencies = [ - "heapless", - "serde", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - [[package]] name = "find-msvc-tools" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foldhash" @@ -491,9 +409,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -506,9 +424,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -516,15 +434,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -533,38 +451,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -594,8 +512,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -612,14 +532,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.1", + "wasip2", + "wasip3", ] [[package]] @@ -634,34 +555,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -673,19 +566,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -708,15 +591,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "hex-conservative" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" -dependencies = [ - "arrayvec", -] - [[package]] name = "hex_lit" version = "0.1.1" @@ -724,105 +598,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] -name = "http" -version = "1.5.0" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", "itoa", ] -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - [[package]] name = "icu_collections" version = "2.1.1" @@ -904,6 +703,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -932,7 +737,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] @@ -946,12 +753,15 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.14.0" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "either", + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -962,12 +772,13 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "cfg-if", "futures-util", + "once_cell", "wasm-bindgen", ] @@ -978,16 +789,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.189" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "libc" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "litemap" @@ -1006,9 +817,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "log-panics" @@ -1021,9 +832,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.18.2" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" [[package]] name = "matchers" @@ -1036,27 +847,21 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.3" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mio" -version = "1.2.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - [[package]] name = "negentropy" version = "0.5.0" @@ -1065,71 +870,80 @@ checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" [[package]] name = "nostr" -version = "0.45.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dde8c76076d334409d86c2e1db3e97abe5deb8cb92744f939cbc1fa45bd69e7" +checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1" dependencies = [ "aes", "base64", - "bech32 0.12.0", + "bech32", "bip39", - "bitcoin_hashes 1.2.0", + "bitcoin_hashes", "cbc", "chacha20", "chacha20poly1305", - "faster-hex", - "opaquerr", - "rand 0.10.2", - "secp256k1 0.30.0", + "getrandom 0.2.17", + "hex", + "instant", + "scrypt", + "secp256k1", "serde", "serde_json", "unicode-normalization", - "universal-time", "url", - "zeroize", ] [[package]] name = "nostr-database" -version = "0.45.1" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b1fdb9fcba732e32719662afad1b267e50322dbe89e506017ec13f24361bddf" +checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1" dependencies = [ + "lru", "nostr", - "opaquerr", + "tokio", ] [[package]] name = "nostr-gossip" -version = "0.45.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa07539e52a71cb91fe0d693facaa298f03fcf9edcd66a521094e18e286e2336" +checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6" dependencies = [ "nostr", - "opaquerr", ] [[package]] -name = "nostr-sdk" -version = "0.45.1" +name = "nostr-relay-pool" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c86342f367bd9b173ec4a697e936e3a82d6dad5b4aa06c0d35d9b4f88a8e72" +checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b" dependencies = [ "async-utility", "async-wsocket", - "faster-hex", - "futures", + "atomic-destructor", + "hex", "lru", "negentropy", "nostr", "nostr-database", - "nostr-gossip", - "opaquerr", - "rand 0.10.2", "tokio", - "tokio-stream", "tracing", - "universal-time", +] + +[[package]] +name = "nostr-sdk" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393" +dependencies = [ + "async-utility", + "nostr", + "nostr-database", + "nostr-gossip", + "nostr-relay-pool", + "tokio", + "tracing", ] [[package]] @@ -1153,12 +967,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "opaquerr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f933a4265d5cdad61d19bbdfc972ea5726d56cd8d3d57b8f2d3c365dd42bee9" - [[package]] name = "parking_lot" version = "0.12.5" @@ -1182,43 +990,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1261,160 +1059,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.119", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.107" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "pulldown-cmark", - "pulldown-cmark-to-cmark", - "regex", - "syn 2.0.119", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "prost-types" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" -dependencies = [ - "prost", -] - -[[package]] -name = "protoc-bin-vendored" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" -dependencies = [ - "protoc-bin-vendored-linux-aarch_64", - "protoc-bin-vendored-linux-ppcle_64", - "protoc-bin-vendored-linux-s390_64", - "protoc-bin-vendored-linux-x86_32", - "protoc-bin-vendored-linux-x86_64", - "protoc-bin-vendored-macos-aarch_64", - "protoc-bin-vendored-macos-x86_64", - "protoc-bin-vendored-win32", -] - -[[package]] -name = "protoc-bin-vendored-linux-aarch_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" - -[[package]] -name = "protoc-bin-vendored-linux-ppcle_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" - -[[package]] -name = "protoc-bin-vendored-linux-s390_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" - -[[package]] -name = "protoc-bin-vendored-linux-x86_32" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" - -[[package]] -name = "protoc-bin-vendored-linux-x86_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" - -[[package]] -name = "protoc-bin-vendored-macos-aarch_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" - -[[package]] -name = "protoc-bin-vendored-macos-x86_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" - -[[package]] -name = "protoc-bin-vendored-win32" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" - -[[package]] -name = "pulldown-cmark" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - -[[package]] -name = "pulldown-cmark-to-cmark" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" -dependencies = [ - "pulldown-cmark", -] - [[package]] name = "quote" -version = "1.0.47" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1433,9 +1094,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.7" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -1444,24 +1105,14 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "getrandom 0.4.3", - "rand_core 0.10.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -1500,12 +1151,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - [[package]] name = "redox_syscall" version = "0.5.18" @@ -1517,9 +1162,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.13.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1529,9 +1174,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.18" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1540,9 +1185,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.11" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -1558,26 +1203,12 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" dependencies = [ - "log", "once_cell", "ring", "rustls-pki-types", @@ -1588,9 +1219,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] @@ -1608,9 +1239,18 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.23" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] [[package]] name = "scopeguard" @@ -1618,28 +1258,30 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "secp256k1" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.14.101", + "bitcoin_hashes", + "rand 0.8.6", "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" -dependencies = [ - "bitcoin_hashes 0.14.101", - "rand 0.8.7", - "secp256k1-sys", -] - [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -1650,10 +1292,16 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.229" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1661,29 +1309,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -1694,9 +1342,20 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1714,9 +1373,9 @@ dependencies = [ [[package]] name = "shlex" -version = "2.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" @@ -1726,15 +1385,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys 0.61.2", @@ -1754,32 +1413,15 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.119" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - [[package]] name = "synstructure" version = "0.13.2" @@ -1788,20 +1430,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", + "syn", ] [[package]] @@ -1815,11 +1444,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.18", ] [[package]] @@ -1830,25 +1459,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "thread_local" -version = "1.1.10" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] @@ -1865,9 +1494,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -1880,9 +1509,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.1" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -1893,24 +1522,15 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-happy-eyeballs" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8564c32dfb6f4257f8bc6edfc178a34af97520e0b7b9815500c55eb3d092f29f" -dependencies = [ - "tokio", -] - [[package]] name = "tokio-macros" -version = "2.7.2" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] @@ -1925,9 +1545,9 @@ dependencies = [ [[package]] name = "tokio-socks" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", @@ -1937,21 +1557,20 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.19" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", ] [[package]] name = "tokio-tungstenite" -version = "0.28.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", @@ -1965,117 +1584,17 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", - "libc", "pin-project-lite", "tokio", ] -[[package]] -name = "tonic" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" -dependencies = [ - "async-trait", - "base64", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "socket2", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-build" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" -dependencies = [ - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tonic-prost" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" -dependencies = [ - "bytes", - "prost", - "tonic", -] - -[[package]] -name = "tonic-prost-build" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "prost-types", - "quote", - "syn 2.0.119", - "tempfile", - "tonic-build", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project-lite", - "slab", - "sync_wrapper", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" version = "0.1.44" @@ -2096,7 +1615,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] @@ -2138,42 +1657,30 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "tungstenite" -version = "0.28.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.9.5", + "rand 0.9.2", "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.20", + "thiserror 2.0.18", "utf-8", ] [[package]] name = "typenum" -version = "1.20.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" @@ -2190,6 +1697,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2200,12 +1713,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "universal-time" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a939edecc3c5a7b83c02e5f6b3c31d2bc69eabcc9a87ab12c6d37ee6dbc856" - [[package]] name = "untrusted" version = "0.9.0" @@ -2239,11 +1746,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.4.3", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -2260,15 +1767,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2281,14 +1779,23 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -2299,9 +1806,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ "js-sys", "wasm-bindgen", @@ -2309,9 +1816,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2319,31 +1826,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.104" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -2355,14 +1896,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.9", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -2461,6 +2002,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.3" @@ -2469,9 +2098,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -2486,35 +2115,35 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "zerofrom" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] @@ -2527,15 +2156,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" @@ -2567,11 +2196,11 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn", ] [[package]] name = "zmij" -version = "1.0.23" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index a21e37a..554256b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cln-nip47" -version = "0.2.0" +version = "0.1.9" edition = "2024" rust-version = "1.85.0" @@ -12,22 +12,14 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["fs", "sync", "rt-multi-thread"] } -cln-rpc = "0.7" +cln-rpc = "0.6" # cln-rpc = { path = "../lightning/cln-rpc/", version = "^0.6" } -cln-plugin = "0.7" +cln-plugin = "0.6" # cln-plugin = { path = "../lightning/plugins/", version = "^0.6" } parking_lot = "0.12" -# # nostr-sdk = { git = "https://github.com/rust-nostr/nostr.git", rev = "ff5be7d1416ea201887a02f648795010a3cbdf49" } -# nostr = { git = "https://github.com/rust-nostr/nostr.git", rev = "ff5be7d1416ea201887a02f648795010a3cbdf49", features = [ -# "nip47", -# "nip04", -# "nip44", -# ] } -nostr = { version = "0.45.1", features = ["nip47", "nip04", "nip44"] } -nostr-sdk = { version = "0.45.1" } - -futures = "0.3" +# nostr-sdk = { git = "https://github.com/rust-nostr/nostr.git", rev = "f7122f5", features = ["nip47", "nip04", "nip44"]} +nostr-sdk = { version = "0.44", features = ["nip47", "nip04", "nip44"] } uuid = { version = "1", features = ["v4"] } @@ -35,18 +27,6 @@ hex = "0.4" regex = "1" -tonic = { version = "0.14", default-features = false, features = [ - "codegen", - "transport", - "tls-ring", -] } -prost = "0.14" -tonic-prost = "0.14" - -[build-dependencies] -tonic-prost-build = "0.14" -protoc-bin-vendored = "3" - [profile.optimized] inherits = "release" diff --git a/README.md b/README.md index 5556fd0..6d78898 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ + + + + - - - -
+ + + + + + + +
@@ -35,23 +47,11 @@
- - - - - - - -
# cln-nip47 -A core lightning plugin to connect wallets via Nostr Wallet Connect (NWC) as specified in [NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md). It is intended to be used by a single user, since all notifications got to all NWC's. +A core lightning plugin to connect wallets via Nostr Wallet Connect (NWC) as specified in [NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md). * [Installation](#installation) * [Building](#building) @@ -64,10 +64,11 @@ Release binaries for * x86_64-linux * armv7-linux (Raspberry Pi 32bit) * aarch64-linux (Raspberry Pi 64bit) -* universal-apple-darwin (macOS) can be found on the [release](https://github.com/daywalker90/cln-nip47/releases) page. If you are unsure about your architecture you can run ``uname -m``. +They require ``glibc>=2.31``, which you can check with ``ldd --version``. + # Building You can build the plugin yourself instead of using the release binaries. First clone the repo: @@ -84,7 +85,7 @@ cargo build --release After that the binary will be here: ``target/release/cln-nip47`` -Note: Release binaries are built with the ``optimized`` profile. +Note: Release binaries are built using ``cross`` and the ``optimized`` profile. # Documentation @@ -97,12 +98,7 @@ It is highly recommended to use your own private relay since public relays may l For a private relay you can for example use [nostr-rs-relay](https://github.com/scsibug/nostr-rs-relay) with ``pubkey_whitelist`` set to both ``clientkey_public`` and ``walletkey_public`` (returned from ``nip47-create``/``nip47-list``). ## Options -* ``nip47-relays``: Specify the relays that you want to use with your NWC. Can be set multiple times to use multiple relays. NWC's you create will save these and even if you add or remove relays keep the relays from the moment you created that NWC. If you don't set this yourself these default relays will be used: - * ``wss://nos.lol`` - * ``wss://relay.primal.net`` - * ``wss://relay.getalby.com/v1`` - * ``wss://relay.nostr.net`` - * ``wss://relay.snort.social`` +* ``nip47-relays``: Specify the relays that you want to use with your NWC. Can be set multiple times to use multiple relays. NWC's you create will save these and even if you add or remove relays keep the relays from the moment you created that NWC. You must set this atleast one time. * ``nip47-notifications``: Enable/disable nip47 notifications. Default is enabled (``true``) ## Methods @@ -131,25 +127,20 @@ For a private relay you can for example use [nostr-rs-relay](https://github.com/ * list all NWC configurations or just the one with ``label`` * ***label***: optional. The label the NWC was created with -## Holdinvoice support -For methods or notifications related to holdinvoices you need v0.3.2+ of [hold](https://github.com/BoltzExchange/hold) with enabled grpc (which is the default just make sure the port is free) - ## Supported NWC methods * ``pay_invoice`` +* ``multi_pay_invoice`` * ``pay_keysend`` (no ``preimage`` in request allowed since CLN only supports generating it itself) +* ``multi_pay_keysend`` (no ``preimage`` in request allowed since CLN only supports generating it itself) * ``make_invoice`` * ``lookup_invoice`` * ``list_transactions`` * ``get_balance`` * ``get_info`` (no ``block_hash``) -* ``make_hold_invoice`` (requires [Holdinvoice support](#holdinvoice_support)) -* ``cancel_hold_invoice`` (requires [Holdinvoice support](#holdinvoice_support)) -* ``settle_hold_invoice`` (requires [Holdinvoice support](#holdinvoice_support)) ## Supported NWC notifications * ``payment_received`` * ``payment_sent`` -* ``hold_invoice_accepted`` (requires [Holdinvoice support](#holdinvoice_support)) ## Supported content encryption: * [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 9b5fbd8..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Supported Versions - -Only the latest version is supported. - -## Reporting a Vulnerability - -To report security vulnerabilities, please send an email to: -- `daywalker990@gmx.de` - -Note: This email address is exclusively for vulnerability reporting. - -For all other inquiries/communication, please use the Github issues. - -## Signatures For Releases - -The following keys may be used to communicate sensitive information to -developers: - -| Name | Email | Fingerprint | -|------|-------|-------------| -| daywalker90 | `daywalker990@gmx.de` | 8A07 9421 A871 D0B1 0835 1193 7AB4 802E D5A6 39F3 | - -You can import a key by running the following command with that individual’s fingerprint: -`gpg --keyserver hkps://keys.openpgp.org --recv-keys ""`. -Ensure that you put quotes around fingerprints containing spaces. diff --git a/build.rs b/build.rs deleted file mode 100644 index 04c8cef..0000000 --- a/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -fn main() { - let protoc = protoc_bin_vendored::protoc_bin_path().unwrap(); - unsafe { std::env::set_var("PROTOC", protoc) }; - tonic_prost_build::configure() - .protoc_arg("--experimental_allow_proto3_optional") - .compile_protos(&["protos/hold.proto"], &["protos"]) - .unwrap_or_else(|e| panic!("Could not build protos: {e}")); -} diff --git a/coffee.yml b/coffee.yml index fa75d76..980549a 100644 --- a/coffee.yml +++ b/coffee.yml @@ -1,6 +1,6 @@ plugin: name: cln-nip47 - version: 0.2.0 + version: 0.1.9 lang: rust install: | cargo build --release && cp target/release/cln-nip47 . && cargo clean diff --git a/protos/hold.proto b/protos/hold.proto deleted file mode 100644 index e7ad240..0000000 --- a/protos/hold.proto +++ /dev/null @@ -1,185 +0,0 @@ -syntax = "proto3"; - -package hold; - -service Hold { - rpc GetInfo (GetInfoRequest) returns (GetInfoResponse); - - rpc Invoice (InvoiceRequest) returns (InvoiceResponse) {} - rpc Inject (InjectRequest) returns (InjectResponse) {} - - rpc List (ListRequest) returns (ListResponse) {} - - rpc Settle (SettleRequest) returns (SettleResponse) {} - rpc Cancel (CancelRequest) returns (CancelResponse) {} - - // Cleans cancelled invoices - rpc Clean (CleanRequest) returns (CleanResponse) {} - - rpc Track (TrackRequest) returns (stream TrackResponse) {} - rpc TrackAll (TrackAllRequest) returns (stream TrackAllResponse) {} - - rpc OnionMessages (stream OnionMessageResponse) returns (stream OnionMessage) {} -} - -enum HookAction { - Continue = 0; - Resolve = 1; -} - -message GetInfoRequest {} -message GetInfoResponse { - string version = 1; -} - -message Hop { - bytes public_key = 1; - uint64 short_channel_id = 2; - uint64 base_fee = 3; - uint64 ppm_fee = 4; - uint64 cltv_expiry_delta = 5; -} - -message RoutingHint { - repeated Hop hops = 1; -} - -message InvoiceRequest { - bytes payment_hash = 1; - uint64 amount_msat = 2; - - oneof description { - string memo = 3; - bytes hash = 4; - } - - optional uint64 expiry = 5; - optional uint64 min_final_cltv_expiry = 6; - repeated RoutingHint routing_hints = 7; -} -message InvoiceResponse { - string bolt11 = 1; -} - -message InjectRequest { - string invoice = 1; - optional uint64 min_cltv_expiry = 2; -} -message InjectResponse {} - -message ListRequest { - message Pagination { - // Inclusive - int64 index_start = 1; - uint64 limit = 2; - } - - oneof constraint { - bytes payment_hash = 1; - Pagination pagination = 2; - } -} - -enum InvoiceState { - UNPAID = 0; - ACCEPTED = 1; - PAID = 2; - CANCELLED = 3; -} - -message Htlc { - int64 id = 1; - InvoiceState state = 2; - string scid = 3; - uint64 channel_id = 4; - uint64 msat = 5; - uint64 created_at = 6; - optional uint64 cltv_expiry = 7; -} - -message Invoice { - int64 id = 1; - bytes payment_hash = 2; - optional bytes preimage = 3; - string invoice = 4; - InvoiceState state = 5; - uint64 created_at = 6; - optional uint64 settled_at = 8; - - repeated Htlc htlcs = 7; - - optional uint64 min_cltv_expiry = 9; -} - -message ListResponse { - repeated Invoice invoices = 1; -} - -message SettleRequest { - bytes payment_preimage = 1; -} -message SettleResponse {} - -message CancelRequest { - bytes payment_hash = 1; -} -message CancelResponse {} - -message CleanRequest { - // Clean everything older than age seconds - optional uint64 age = 1; -} -message CleanResponse { - uint64 cleaned = 1; -} - -message TrackRequest { - bytes payment_hash = 1; -} - -message TrackResponse { - InvoiceState state = 1; -} - -message TrackAllRequest { - repeated bytes payment_hashes = 1; -} - -message TrackAllResponse { - bytes payment_hash = 1; - string bolt11 = 2; - InvoiceState state = 3; -} - -message OnionMessage { - message ReplyBlindedPath { - message Hop { - optional bytes blinded_node_id = 1; - optional bytes encrypted_recipient_data = 2; - } - - optional bytes first_node_id = 1; - optional string first_scid = 2; - optional uint64 first_scid_dir = 3; - optional bytes first_path_key = 4; - repeated Hop hops = 5; - } - - message UnknownField { - uint64 number = 1; - bytes value = 2; - } - - uint64 id = 1; - optional bytes pathsecret = 2; - optional ReplyBlindedPath reply_blindedpath = 3; - optional bytes invoice_request = 4; - optional bytes invoice = 5; - optional bytes invoice_error = 6; - repeated UnknownField unknown_fields = 7; -} - -message OnionMessageResponse { - uint64 id = 1; - HookAction action = 2; -} diff --git a/src/main.rs b/src/main.rs index 1c3323a..778d840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,4 @@ -use std::{ - path::{Path, PathBuf}, - str::FromStr, - time::Duration, -}; +use std::{path::Path, time::Duration}; use anyhow::anyhow; use cln_plugin::{ @@ -10,29 +6,16 @@ use cln_plugin::{ Plugin, options::{ConfigOption, DefaultBooleanConfigOption, StringArrayConfigOption}, }; -use cln_rpc::{ - ClnRpc, - model::{ - requests::{DecodeRequest, ListdatastoreRequest}, - responses::DecodeType, - }, -}; -use nostr::nips::nip47; +use cln_rpc::{ClnRpc, model::requests::ListdatastoreRequest}; +use nostr_sdk::nips::nip47; use nwc::run_nwc; use nwc_notifications::{payment_received_handler, payment_sent_handler}; use parse::read_startup_options; use rpc::{nwc_budget, nwc_create, nwc_list, nwc_revoke}; -use serde_json::json; use structs::PluginState; use tokio::time; -use tonic::transport::{Certificate, ClientTlsConfig, Endpoint, Identity}; use util::{load_nwc_store, update_nwc_store}; -use crate::{ - hold::{InvoiceState, ListRequest, hold_client::HoldClient}, - nwc_notifications::holdinvoice_accepted_handler, -}; - mod nwc; mod nwc_balance; mod nwc_hold; @@ -49,9 +32,6 @@ mod tasks; mod util; pub const STARTUP_DELAY: u64 = 1; -pub mod hold { - tonic::include_proto!("hold"); -} const OPT_RELAYS: StringArrayConfigOption = ConfigOption::new_str_arr_no_default( "nip47-relays", @@ -63,33 +43,30 @@ const OPT_NOTIFICATIONS: DefaultBooleanConfigOption = ConfigOption::new_bool_wit "Enable/disable nip47-notifications. Default is `true`", ); pub const PLUGIN_NAME: &str = "cln-nip47"; -pub const WALLET_READ_OR_RECEIVE_METHODS: [nip47::Method; 5] = [ +pub const WALLET_READ_METHODS: [nip47::Method; 5] = [ nip47::Method::MakeInvoice, nip47::Method::LookupInvoice, nip47::Method::ListTransactions, nip47::Method::GetBalance, nip47::Method::GetInfo, ]; -pub const WALLET_PAY_METHODS: [nip47::Method; 2] = - [nip47::Method::PayInvoice, nip47::Method::PayKeysend]; -pub const WALLET_HOLD_METHODS: [nip47::Method; 3] = [ - nip47::Method::MakeHoldInvoice, - nip47::Method::CancelHoldInvoice, - nip47::Method::SettleHoldInvoice, +pub const WALLET_PAY_METHODS: [nip47::Method; 4] = [ + nip47::Method::PayInvoice, + nip47::Method::MultiPayInvoice, + nip47::Method::PayKeysend, + nip47::Method::MultiPayKeysend, ]; pub const WALLET_NOTIFICATIONS: [nip47::NotificationType; 2] = [ nip47::NotificationType::PaymentReceived, nip47::NotificationType::PaymentSent, ]; -pub const WALLET_HOLD_NOTIFICATIONS: [nip47::NotificationType; 1] = - [nip47::NotificationType::HoldInvoiceAccepted]; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { unsafe { std::env::set_var( "CLN_PLUGIN_LOG", - "cln_plugin=info,cln_rpc=info,cln_nip47=trace,info", + "cln_plugin=info,cln_rpc=info,cln_nip47=debug,info", ); }; log_panics::init(); @@ -132,16 +109,6 @@ async fn main() -> Result<(), anyhow::Error> { }; let plugin = confplugin.start(state).await?; - match check_hold_support(plugin.clone()).await { - Ok(()) => { - log::info!("Hold support activated, loading pending invoices..."); - if let Err(e) = load_pending_hold_invoices(plugin.clone()).await { - log::error!("Error loading pending hold invoices: {e}"); - } - } - Err(e) => log::info!("Hold support not activated: {e}"), - } - { let mut rpc = plugin.state().rpc_lock.lock().await; @@ -163,16 +130,6 @@ async fn main() -> Result<(), anyhow::Error> { } } - let plugin_clone_cleanup = plugin.clone(); - tokio::spawn(async move { - loop { - if let Err(e) = tasks::cleanup_event_ids(plugin_clone_cleanup.clone()).await { - log::warn!("Error in cleanup_event_ids thread: {e}"); - } - time::sleep(Duration::from_secs(10)).await; - } - }); - plugin.join().await } @@ -181,8 +138,8 @@ async fn shutdown_handler( _args: serde_json::Value, ) -> Result<(), anyhow::Error> { let mut locked_handles = plugin.state().handles.lock().await; - for (_x, wallet_service) in locked_handles.drain() { - wallet_service.client.shutdown().await; + for (_x, (client, _client_pubkey)) in locked_handles.drain() { + client.shutdown().await; } std::process::exit(0) } @@ -206,186 +163,7 @@ async fn load_nwcs(plugin: Plugin, rpc: &mut ClnRpc) -> Result<(), } } - // We don't keep track of inflight payments succeeding - // while the plugin is dynamically restarted - if nwc_store.reserved_msat != 0 { - log::warn!( - "Releasing {} msat leftover budget reservation for {label}", - nwc_store.reserved_msat - ); - nwc_store.reserved_msat = 0; - update_nwc_store(rpc, label, nwc_store.clone()).await?; - } - run_nwc(plugin.clone(), label.clone(), nwc_store.clone()).await?; } Ok(()) } - -async fn load_pending_hold_invoices(plugin: Plugin) -> Result<(), anyhow::Error> { - let mut hold_client = plugin.state().hold_client.lock().clone().unwrap(); - let invoices_request = ListRequest { constraint: None }; - let invoices = hold_client - .list(invoices_request) - .await? - .into_inner() - .invoices; - - let mut rpc = plugin.state().rpc_lock.lock().await; - - for invoice in invoices { - if invoice.state() == InvoiceState::Accepted || invoice.state() == InvoiceState::Unpaid { - // Bound the accepted handler by the invoice's expiry. The hold - // plugin has no expired state, so without this it would wait - // forever on an invoice that expires while Unpaid. - let invoice_decoded = rpc - .call_typed(&DecodeRequest { - string: invoice.invoice.clone(), - }) - .await?; - - if !invoice_decoded.valid { - log::warn!( - "Skipping hold invoice {}, could not decode it", - hex::encode(&invoice.payment_hash) - ); - continue; - } - - let (created_at, expiry) = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => ( - invoice_decoded.invoice_created_at, - invoice_decoded.invoice_relative_expiry.map(u64::from), - ), - DecodeType::BOLT11_INVOICE => (invoice_decoded.created_at, invoice_decoded.expiry), - _ => continue, - }; - - let (Some(created_at), Some(expiry)) = (created_at, expiry) else { - log::warn!( - "Skipping hold invoice {}: missing creation time or expiry", - hex::encode(&invoice.payment_hash) - ); - continue; - }; - let expires_at = created_at.saturating_add(expiry); - - log::debug!( - "Starting holdinvoice accepted handler for {}", - hex::encode(&invoice.payment_hash) - ); - tokio::spawn(holdinvoice_accepted_handler( - plugin.clone(), - invoice.payment_hash, - expires_at, - )); - } - } - - Ok(()) -} - -async fn check_hold_support(plugin: Plugin) -> Result<(), anyhow::Error> { - let mut rpc = plugin.state().rpc_lock.lock().await; - let hold_grpc_host_response: serde_json::Value = rpc - .call_raw("listconfigs", &json!({"config": "hold-grpc-host"})) - .await?; - - let Some(hold_grpc_host_configs) = hold_grpc_host_response.get("configs") else { - return Err(anyhow!("Unsopprted listconfigs response!")); - }; - let Some(hold_grpc_host_config) = hold_grpc_host_configs.get("hold-grpc-host") else { - return Err(anyhow!("hold-grpc-host config not found")); - }; - let Some(hold_grpc_host_value) = hold_grpc_host_config.get("value_str") else { - return Err(anyhow!("hold-grpc-host config not a string")); - }; - let Some(hold_grpc_host) = hold_grpc_host_value.as_str() else { - return Err(anyhow!("hold-grpc-host config not convertable to string")); - }; - - let hold_grpc_port_response: serde_json::Value = rpc - .call_raw("listconfigs", &json!({"config": "hold-grpc-port"})) - .await?; - let Some(hold_grpc_port_configs) = hold_grpc_port_response.get("configs") else { - return Err(anyhow!("Unsopprted listconfigs response!")); - }; - let Some(hold_grpc_port_config) = hold_grpc_port_configs.get("hold-grpc-port") else { - return Err(anyhow!("hold-grpc-port config not found")); - }; - let Some(hold_grpc_port_value) = hold_grpc_port_config.get("value_int") else { - return Err(anyhow!("hold-grpc-port config not a number")); - }; - let hold_grpc_port = if let Some(hgh) = hold_grpc_port_value.as_u64() { - u16::try_from(hgh)? - } else { - return Err(anyhow!("hold-grpc-port config not convertable to integer")); - }; - - let cert_dir = PathBuf::from_str(&plugin.configuration().lightning_dir)?.join("hold"); - - log::debug!( - "Searching {} for hold plugin certs", - cert_dir.to_str().unwrap() - ); - - let cert_max_retries = 10; - let mut cert_retries = 0; - while cert_retries < cert_max_retries && !do_certificates_exist(&cert_dir) { - log::debug!("Hold certificates incomplete. Waiting..."); - time::sleep(Duration::from_millis(500)).await; - cert_retries += 1; - } - - let ca_cert = tokio::fs::read(cert_dir.join("ca.pem")).await?; - let client_cert = tokio::fs::read(cert_dir.join("client.pem")).await?; - let client_key = tokio::fs::read(cert_dir.join("client-key.pem")).await?; - - let identity = Identity::from_pem(client_cert, client_key); - - let ca = Certificate::from_pem(ca_cert); - - let tls_config = ClientTlsConfig::new() - .ca_certificate(ca) - .identity(identity) - .domain_name("hold"); - - let endpoint = Endpoint::from_shared(format!("https://{hold_grpc_host}:{hold_grpc_port}"))? - .tls_config(tls_config)? - .keep_alive_while_idle(true) - .connect_timeout(Duration::from_secs(5)); - - let chan_max_retries = 20; - let mut chan_retries = 0; - - let channel = loop { - match endpoint.connect().await { - Ok(channel) => break channel, - Err(e) if chan_retries < chan_max_retries => { - chan_retries += 1; - - log::debug!( - "Hold gRPC server not ready, retrying ({chan_retries}/{chan_max_retries}): {e}" - ); - - time::sleep(Duration::from_millis(500)).await; - } - Err(e) => { - return Err(anyhow!("Hold gRPC server did not become ready: {e}")); - } - } - }; - - *plugin.state().hold_client.lock() = Some(HoldClient::new(channel)); - - Ok(()) -} - -fn do_certificates_exist(cert_dir: &Path) -> bool { - let required_files = ["client.pem", "client-key.pem", "ca.pem"]; - - required_files.iter().all(|file| { - let path = cert_dir.join(file); - path.exists() && path.metadata().is_ok_and(|m| m.len() > 0) - }) -} diff --git a/src/nwc.rs b/src/nwc.rs index dfd0e2c..448334b 100644 --- a/src/nwc.rs +++ b/src/nwc.rs @@ -1,24 +1,31 @@ -use std::time::Duration; +use std::{borrow::Cow, time::Duration}; use anyhow::anyhow; use cln_plugin::Plugin; -use futures::StreamExt; -use nostr::{ - event::{Event, EventBuilder, EventId, FinalizeEventAsync, Kind, Tag}, - filter::Filter, - key::{Keys, PublicKey, SecretKey}, - nips::{nip04, nip44, nip47}, - types::Timestamp, -}; use nostr_sdk::{ - client::{self, Client, ClientNotification}, - error::Error, - relay::RelayStatus, + Alphabet, + Client, + Event, + EventBuilder, + EventId, + Keys, + PublicKey, + RelayPoolNotification, + RelayStatus, + SecretKey, + SignerError, + SingleLetterTag, + TagKind, + Timestamp, + client, + nips::{nip04, nip44, nip47}, + nostr::{Filter, Kind, Tag}, }; -use tokio::time; +use tokio::{sync::oneshot, time}; use crate::{ OPT_NOTIFICATIONS, + STARTUP_DELAY, nwc_balance::get_balance_response, nwc_hold::{ cancel_hold_invoice_response, @@ -27,49 +34,50 @@ use crate::{ }, nwc_info::get_info_response, nwc_invoice::make_invoice_response, - nwc_keysend::pay_keysend_response, + nwc_keysend::{multi_pay_keysend, pay_keysend_response}, nwc_lookups::{list_transactions_response, lookup_invoice_response}, - nwc_pay::pay_invoice_response, - structs::{ID_MAX_AGE, NwcStore, PluginState, WalletService}, - util::{build_capabilities, build_notifications_vec, is_read_only_nwc, save_event_id}, + nwc_pay::{multi_pay_invoice, pay_invoice_response}, + structs::{NwcStore, PluginState}, + tasks::budget_task, + util::{build_capabilities, build_notifications_vec, is_read_only_nwc}, }; -#[allow(clippy::too_many_lines)] pub async fn run_nwc( plugin: Plugin, label: String, nwc_store: NwcStore, -) -> Result<(), Error> { +) -> Result<(), client::Error> { let (method_capabilities, _) = build_capabilities(is_read_only_nwc(&nwc_store), &plugin); - let wallet_keys = Keys::new(SecretKey::from_hex(&nwc_store.walletkey)?); - let client_keys = Keys::new(nwc_store.uri.secret.clone()); + let wallet_keys = Keys::new( + SecretKey::from_hex(&nwc_store.walletkey) + .map_err(|e| client::Error::Signer(SignerError::backend(e)))?, + ); + let client_pubkey = Keys::new(nwc_store.uri.secret.clone()).public_key(); - let nostr_client = Client::new(); + let client = Client::new(wallet_keys.clone()); log::debug!("relay_count:{}", nwc_store.uri.relays.len()); for relay in &nwc_store.uri.relays { log::debug!("Adding relay: {relay}"); - nostr_client.add_relay(relay).await?; + client.add_relay(relay).await?; } if nwc_store.interval_config.is_some() { - log::debug!("NWC {label} uses an interval budget"); + start_nwc_budget_job(&plugin, label.clone()); } - let nostr_client_clone = nostr_client.clone(); + let client_clone = client.clone(); let plugin_clone = plugin.clone(); let label_clone = label.clone(); - let client_keys_clone = client_keys.clone(); - let wallet_keys_clone = wallet_keys.clone(); tokio::spawn(async move { loop { - nostr_client_clone - .connect() - .and_wait(Duration::from_secs(30)) + client_clone.connect().await; + client_clone + .wait_for_connection(Duration::from_secs(30)) .await; - let relays = nostr_client_clone.relays().await; + let relays = client_clone.relays().await; if relays.is_empty() { log::info!("No more relays left, we probably shut down. Exiting..."); break; @@ -90,67 +98,62 @@ pub async fn run_nwc( if let Err(e) = send_nwc_info_event( plugin_clone.clone(), - nostr_client_clone.clone(), + client_clone.clone(), method_capabilities.clone(), - wallet_keys_clone.clone(), + wallet_keys.clone(), ) .await { log::warn!("{e}"); - nostr_client_clone.disconnect().await; + client_clone.disconnect().await; time::sleep(Duration::from_secs(5)).await; continue; } let filter = Filter::new() .kind(Kind::WalletConnectRequest) - .author(client_keys_clone.public_key()); + .author(client_pubkey) + .since(Timestamp::now() - STARTUP_DELAY - 1); - let mut notifications = nostr_client_clone.notifications(); - - match nostr_client_clone.subscribe(filter).await { - Ok(o) => { - if o.success.is_empty() { - log::warn!("Could not subscribe to any relay!"); - nostr_client_clone.disconnect().await; - time::sleep(Duration::from_secs(5)).await; - continue; - } - } - Err(e) => { - log::warn!("Error subscribing to relays: {e}"); - nostr_client_clone.disconnect().await; - time::sleep(Duration::from_secs(5)).await; - continue; - } + if let Err(e) = client_clone.subscribe(filter, None).await { + log::warn!("Could not subscribe to nwc events! {e}"); + client_clone.disconnect().await; + time::sleep(Duration::from_secs(5)).await; + continue; } - while let Some(notification) = notifications.next().await { - if let Err(e) = nwc_request_handler( - notification, - &nostr_client_clone, - &plugin_clone, - &label_clone, - &wallet_keys_clone, - client_keys_clone.public_key(), - ) + let client_clone_handler = client_clone.clone(); + match client_clone + .handle_notifications(|notification| { + let client_clone_handler = client_clone_handler.clone(); + let plugin_clone = plugin_clone.clone(); + let label_clone = label_clone.clone(); + let wallet_keys_clone = wallet_keys.clone(); + nwc_request_handler( + notification, + client_clone_handler, + plugin_clone, + label_clone, + wallet_keys_clone, + client_pubkey, + ) + }) .await - { - log::warn!("NWC handler for `{label_clone}` had an error: {e}"); + { + Ok(()) => { + log::info!("NWC handler for `{label_clone}` stopped"); + break; } + Err(e) => log::warn!("NWC handler for `{label_clone}` had an error: {e}"), } - - {}; } }); let mut locked_handles = plugin.state().handles.lock().await; - let wallet_service = WalletService { - client: nostr_client, - client_pubkey: client_keys.public_key(), - wallet_secret: wallet_keys, - }; - locked_handles.insert(label.clone(), wallet_service); + locked_handles.insert( + label.clone(), + (client, Keys::new(nwc_store.uri.secret).public_key()), + ); Ok(()) } @@ -173,7 +176,7 @@ pub async fn send_nwc_info_event( .tag(Tag::parse(vec!["notifications", ¬ification_capabilities]).unwrap()); } - let info_event = match info_event_builder.finalize_async(&wallet_keys).await { + let info_event = match info_event_builder.sign_with_keys(&wallet_keys) { Ok(o) => o, Err(e) => { return Err(anyhow!("Could not sign info_event! {e}")); @@ -201,103 +204,95 @@ pub async fn send_nwc_info_event( pub async fn stop_nwc(plugin: Plugin, label: &String) { let mut locked_handles = plugin.state().handles.lock().await; - if let Some(wallet_service) = locked_handles.remove(label) { - wallet_service.client.shutdown().await; + if let Some((client, _client_pubkey)) = locked_handles.remove(label) { + client.shutdown().await; + } + + stop_nwc_budget_job(&plugin, label); +} + +pub fn start_nwc_budget_job(plugin: &Plugin, label: String) { + let (tx, rx) = oneshot::channel::<()>(); + tokio::spawn(budget_task(rx, plugin.clone(), label.clone())); + plugin.state().budget_jobs.lock().insert(label, tx); +} + +pub fn stop_nwc_budget_job(plugin: &Plugin, label: &String) { + let mut budget_jobs = plugin.state().budget_jobs.lock(); + let job = budget_jobs.remove(label); + if let Some(j) = job { + let _ = j.send(()); } } -#[allow(clippy::too_many_lines)] async fn nwc_request_handler( - notification: ClientNotification, - nostr_client: &client::Client, - plugin: &Plugin, - label: &str, - wallet_keys: &Keys, + notification: RelayPoolNotification, + client: client::Client, + plugin: Plugin, + label: String, + wallet_keys: Keys, client_pubkey: PublicKey, -) -> Result<(), Box> { +) -> Result> { let (relay_url, subscription_id, event) = match notification { - ClientNotification::Event { + RelayPoolNotification::Event { relay_url, subscription_id, event, } => (relay_url, subscription_id, event), - ClientNotification::Message { + RelayPoolNotification::Message { relay_url: _, message: _, - } - | ClientNotification::Shutdown => return Ok(()), + } => return Ok(false), + RelayPoolNotification::Shutdown => return Ok(true), }; - log::trace!("relay_url:{relay_url} subscription_id:{subscription_id} {event:?}"); + if let Some(expi) = event.tags.expiration() { + if *expi < Timestamp::now() { + return Ok(false); + } + } + log::debug!("relay_url:{relay_url} subscription_id:{subscription_id} {event:?}"); let mut use_nip44 = check_nip44_support(&event); - let request = decrypt_request(&event.content, wallet_keys, &client_pubkey, &mut use_nip44)?; + let request = decrypt_request(&event.content, &wallet_keys, &client_pubkey, &mut use_nip44)?; - let responses = if event.tags.expiration().is_some() - && event.tags.expiration().unwrap() < Timestamp::now() - { - vec![( - nip47::Response { - result_type: request.method, - error: Some(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Event expired".to_owned(), - }), - result: None, - }, - None, - )] - } else if event.created_at.as_secs() < (Timestamp::now().as_secs() - ID_MAX_AGE) { - vec![( - nip47::Response { - result_type: request.method, - error: Some(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Event created too far in the past".to_owned(), - }), - result: None, - }, - None, - )] - } else { - { - let mut rpc = plugin.state().rpc_lock.lock().await; - save_event_id(&mut rpc, event.id.to_hex(), event.created_at).await?; + let responses = match request.params { + nip47::RequestParams::PayInvoice(pay_invoice_request) => { + pay_invoice_response(plugin.clone(), pay_invoice_request, &label).await } - - match request.params { - nip47::RequestParams::PayInvoice(pay_invoice_request) => { - pay_invoice_response(plugin.clone(), pay_invoice_request, label).await - } - nip47::RequestParams::PayKeysend(pay_keysend_request) => { - pay_keysend_response(plugin.clone(), pay_keysend_request, label).await - } - nip47::RequestParams::MakeInvoice(make_invoice_request) => { - make_invoice_response(plugin.clone(), make_invoice_request).await - } - nip47::RequestParams::LookupInvoice(lookup_invoice_request) => { - lookup_invoice_response(plugin.clone(), lookup_invoice_request).await - } - nip47::RequestParams::ListTransactions(list_transactions_request) => { - list_transactions_response(plugin.clone(), list_transactions_request).await - } - nip47::RequestParams::GetBalance => get_balance_response(plugin.clone(), label).await, - nip47::RequestParams::GetInfo => get_info_response(plugin.clone(), label).await, - nip47::RequestParams::MakeHoldInvoice(make_hold_invoice_request) => { - make_hold_invoice_response(plugin.clone(), make_hold_invoice_request).await - } - nip47::RequestParams::CancelHoldInvoice(cancel_hold_invoice_request) => { - cancel_hold_invoice_response(plugin.clone(), cancel_hold_invoice_request).await - } - nip47::RequestParams::SettleHoldInvoice(settle_hold_invoice_request) => { - settle_hold_invoice_response(plugin.clone(), settle_hold_invoice_request).await - } + nip47::RequestParams::MultiPayInvoice(multi_pay_invoice_request) => { + multi_pay_invoice(plugin.clone(), multi_pay_invoice_request, &label).await + } + nip47::RequestParams::PayKeysend(pay_keysend_request) => { + pay_keysend_response(plugin, pay_keysend_request, &label).await + } + nip47::RequestParams::MultiPayKeysend(multi_pay_keysend_request) => { + multi_pay_keysend(plugin.clone(), multi_pay_keysend_request, &label).await + } + nip47::RequestParams::MakeInvoice(make_invoice_request) => { + make_invoice_response(plugin.clone(), make_invoice_request).await + } + nip47::RequestParams::LookupInvoice(lookup_invoice_request) => { + lookup_invoice_response(plugin.clone(), lookup_invoice_request).await + } + nip47::RequestParams::ListTransactions(list_transactions_request) => { + list_transactions_response(plugin.clone(), list_transactions_request).await + } + nip47::RequestParams::GetBalance => get_balance_response(plugin.clone(), &label).await, + nip47::RequestParams::GetInfo => get_info_response(plugin.clone(), &label).await, + nip47::RequestParams::MakeHoldInvoice(make_hold_invoice_request) => { + make_hold_invoice_response(plugin.clone(), make_hold_invoice_request, &label).await + } + nip47::RequestParams::CancelHoldInvoice(cancel_hold_invoice_request) => { + cancel_hold_invoice_response(plugin.clone(), cancel_hold_invoice_request, &label).await + } + nip47::RequestParams::SettleHoldInvoice(settle_hold_invoice_request) => { + settle_hold_invoice_response(plugin.clone(), settle_hold_invoice_request, &label).await } }; - for (response, id) in responses { let content = - match encrypt_response_content(&response, wallet_keys, &client_pubkey, use_nip44) { + match encrypt_response_content(&response, &wallet_keys, &client_pubkey, use_nip44) { Ok(o) => o, Err(e) => { log::warn!("{e}"); @@ -306,7 +301,7 @@ async fn nwc_request_handler( }; let response_event = - match build_response_event(event.id, content, wallet_keys, client_pubkey, id).await { + match build_response_event(event.id, content, &wallet_keys, client_pubkey, id) { Ok(o) => o, Err(e) => { log::warn!("Error signing reponse event! {e}"); @@ -314,7 +309,7 @@ async fn nwc_request_handler( } }; - let send_result = match nostr_client.send_event(&response_event).await { + let send_result = match client.send_event(&response_event).await { Ok(o) => o, Err(e) => { log::warn!("Error sending response event! {e}"); @@ -332,25 +327,20 @@ async fn nwc_request_handler( ); continue; } - for success in send_result.success { - log::trace!("Sent to {}", success.0); - } - for failure in send_result.failed { - log::trace!("Failed to send to {}: {}", failure.0, failure.1); - } - log::trace!("SENT RESPONSE {response_event:?}"); + log::debug!("SENT RESPONSE {response_event:?}"); } - Ok(()) + Ok(false) } fn check_nip44_support(event: &Event) -> bool { - for tag in event.tags.iter() { - if tag.kind() == "encryption" { - if let Some(enc_tag_content) = tag.content() { - if enc_tag_content.contains("nip44_v2") { - return true; - } + if let Some(encryption_tag) = event + .tags + .find(TagKind::Custom(Cow::Borrowed("encryption"))) + { + if let Some(enc_tag_content) = encryption_tag.content() { + if enc_tag_content.contains("nip44_v2") { + return true; } } } @@ -398,7 +388,7 @@ fn decrypt_request( } } }; - log::trace!("Decrypted (nip44_v2:{use_nip44}):{content}"); + log::debug!("Decrypted (nip44_v2:{use_nip44}):{content}"); let request: nip47::Request = match serde_json::from_str(&content) { Ok(o) => o, Err(e) => { @@ -421,7 +411,7 @@ fn encrypt_response_content( return Err(anyhow!("Error serializing response! {e}")); } }; - log::trace!("RESPONSE:{response_str}"); + log::debug!("RESPONSE:{response_str}"); if use_nip44 { match nip44::encrypt( wallet_keys.secret_key(), @@ -440,7 +430,7 @@ fn encrypt_response_content( } } -async fn build_response_event( +fn build_response_event( event_id: EventId, content: String, wallet_keys: &Keys, @@ -451,9 +441,15 @@ async fn build_response_event( .tag(Tag::event(event_id)) .tag(Tag::public_key(client_pubkey)); if let Some(i) = id { - response_builder = response_builder.tag(Tag::identifier(i)); + response_builder = response_builder.tag(Tag::custom( + TagKind::SingleLetter(SingleLetterTag { + character: Alphabet::D, + uppercase: false, + }), + vec![i], + )); } - match response_builder.finalize_async(wallet_keys).await { + match response_builder.sign_with_keys(wallet_keys) { Ok(o) => Ok(o), Err(e) => Err(e.into()), } diff --git a/src/nwc_balance.rs b/src/nwc_balance.rs index 2fde546..c29b343 100644 --- a/src/nwc_balance.rs +++ b/src/nwc_balance.rs @@ -1,11 +1,8 @@ use cln_plugin::Plugin; use cln_rpc::{model::requests::ListpeerchannelsRequest, primitives::ChannelState}; -use nostr::nips::nip47; +use nostr_sdk::nips::nip47; -use crate::{ - structs::PluginState, - util::{get_budget_msat, load_nwc_store}, -}; +use crate::{structs::PluginState, util::load_nwc_store}; pub async fn get_balance_response( plugin: Plugin, @@ -43,7 +40,7 @@ async fn get_balance( message: e.to_string(), })?; - let balance = if let Some(bdgt_amt) = get_budget_msat(&nwc_store) { + let balance = if let Some(bdgt_amt) = nwc_store.budget_msat { bdgt_amt } else { let listpeerchannels = rpc diff --git a/src/nwc_hold.rs b/src/nwc_hold.rs index 4ce23a3..e048d0c 100644 --- a/src/nwc_hold.rs +++ b/src/nwc_hold.rs @@ -1,248 +1,58 @@ -use std::str::FromStr; - use cln_plugin::Plugin; -use cln_rpc::primitives::Sha256; -use nostr::{nips::nip47, types::Timestamp}; +use nostr_sdk::nips::nip47; -use crate::{ - hold::{CancelRequest, InvoiceRequest, SettleRequest, invoice_request::Description}, - nwc_notifications::holdinvoice_accepted_handler, - structs::PluginState, -}; +use crate::structs::PluginState; pub async fn make_hold_invoice_response( - plugin: Plugin, - params: nip47::MakeHoldInvoiceRequest, + _plugin: Plugin, + _params: nip47::MakeHoldInvoiceRequest, + _label: &str, ) -> Vec<(nip47::Response, Option)> { - vec![match make_hold_invoice(plugin, params).await { - Ok(o) => ( - nip47::Response { - result_type: nip47::Method::MakeHoldInvoice, - error: None, - result: Some(nip47::ResponseResult::MakeHoldInvoice(o)), - }, - None, - ), - Err(e) => ( - nip47::Response { - result_type: nip47::Method::MakeHoldInvoice, - error: Some(e), - result: None, - }, - None, - ), - }] -} - -async fn make_hold_invoice( - plugin: Plugin, - params: nip47::MakeHoldInvoiceRequest, -) -> Result { - let Some(mut hold_client) = plugin.state().hold_client.lock().clone() else { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::NotImplemented, - message: "No hold plugin found".to_owned(), - }); - }; - - let description: Option = if let Some(d_hash) = ¶ms.description_hash { - if let Some(description) = ¶ms.description { - let my_description_hash = Sha256::const_hash(description.as_bytes()); - let description_hash = Sha256::from_str(d_hash).map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - if my_description_hash != description_hash { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "description_hash not matching description".to_owned(), - }); - } - } - - let desc_hash_bytes = match hex::decode(d_hash) { - Ok(p) => p, - Err(_e) => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Could not convert description hash to bytes".to_owned(), - }); - } - }; - Some(Description::Hash(desc_hash_bytes)) - } else { - params - .description - .as_ref() - .map(|desc| Description::Memo(desc.clone())) - }; - - let payment_hash = match hex::decode(¶ms.payment_hash) { - Ok(p) => p, - Err(_e) => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid payment hash".to_owned(), - }); - } - }; - - let expiry = params.expiry.unwrap_or(60 * 60); - - let holdinvoice_request = InvoiceRequest { - payment_hash: payment_hash.clone(), - amount_msat: params.amount, - expiry: Some(expiry), - min_final_cltv_expiry: params.min_cltv_expiry_delta.map(u64::from), - routing_hints: Vec::new(), - description, - }; - - let holdinvoice = hold_client - .invoice(holdinvoice_request) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: format!("Error creating hold invoice: {e}"), - })? - .into_inner(); - - let expires_at = Timestamp::now() + expiry; - - let response = nip47::MakeHoldInvoiceResponse { - invoice: Some(holdinvoice.bolt11), - transaction_type: nip47::TransactionType::Incoming, - description: params.description, - description_hash: params.description_hash, - amount: params.amount, - created_at: Timestamp::now(), - expires_at, - metadata: None, - payment_hash: params.payment_hash, - }; - - tokio::spawn(holdinvoice_accepted_handler( - plugin, - payment_hash, - expires_at.as_secs(), - )); - Ok(response) + vec![( + nip47::Response { + result_type: nip47::Method::MakeHoldInvoice, + error: Some(nip47::NIP47Error { + code: nip47::ErrorCode::NotImplemented, + message: "Not implemented".to_owned(), + }), + result: None, + }, + None, + )] } pub async fn cancel_hold_invoice_response( - plugin: Plugin, - params: nip47::CancelHoldInvoiceRequest, + _plugin: Plugin, + _params: nip47::CancelHoldInvoiceRequest, + _label: &str, ) -> Vec<(nip47::Response, Option)> { - vec![match cancel_hold_invoice(plugin, params).await { - Ok(o) => ( - nip47::Response { - result_type: nip47::Method::CancelHoldInvoice, - error: None, - result: Some(nip47::ResponseResult::CancelHoldInvoice(o)), - }, - None, - ), - Err(e) => ( - nip47::Response { - result_type: nip47::Method::CancelHoldInvoice, - error: Some(e), - result: None, - }, - None, - ), - }] -} - -async fn cancel_hold_invoice( - plugin: Plugin, - params: nip47::CancelHoldInvoiceRequest, -) -> Result { - let Some(mut hold_client) = plugin.state().hold_client.lock().clone() else { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::NotImplemented, - message: "No hold plugin found".to_owned(), - }); - }; - - let payment_hash = match hex::decode(¶ms.payment_hash) { - Ok(p) => p, - Err(_e) => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid payment hash".to_owned(), - }); - } - }; - - let hold_cancel_request = CancelRequest { payment_hash }; - - let hold_cancel_response = hold_client.cancel(hold_cancel_request).await; - - match hold_cancel_response { - Ok(_o) => Ok(nip47::CancelHoldInvoiceResponse {}), - Err(e) => Err(nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - }), - } + vec![( + nip47::Response { + result_type: nip47::Method::MakeHoldInvoice, + error: Some(nip47::NIP47Error { + code: nip47::ErrorCode::NotImplemented, + message: "Not implemented".to_owned(), + }), + result: None, + }, + None, + )] } pub async fn settle_hold_invoice_response( - plugin: Plugin, - params: nip47::SettleHoldInvoiceRequest, + _plugin: Plugin, + _params: nip47::SettleHoldInvoiceRequest, + _label: &str, ) -> Vec<(nip47::Response, Option)> { - vec![match settle_hold_invoice(plugin, params).await { - Ok(o) => ( - nip47::Response { - result_type: nip47::Method::SettleHoldInvoice, - error: None, - result: Some(nip47::ResponseResult::SettleHoldInvoice(o)), - }, - None, - ), - Err(e) => ( - nip47::Response { - result_type: nip47::Method::SettleHoldInvoice, - error: Some(e), - result: None, - }, - None, - ), - }] -} - -async fn settle_hold_invoice( - plugin: Plugin, - params: nip47::SettleHoldInvoiceRequest, -) -> Result { - let Some(mut hold_client) = plugin.state().hold_client.lock().clone() else { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::NotImplemented, - message: "No hold plugin found".to_owned(), - }); - }; - - let preimage = match hex::decode(¶ms.preimage) { - Ok(p) => p, - Err(_e) => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid preimage".to_owned(), - }); - } - }; - - let hold_settle_request = SettleRequest { - payment_preimage: preimage, - }; - - let hold_settle_response = hold_client.settle(hold_settle_request).await; - - match hold_settle_response { - Ok(_o) => Ok(nip47::SettleHoldInvoiceResponse {}), - Err(e) => Err(nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - }), - } + vec![( + nip47::Response { + result_type: nip47::Method::MakeHoldInvoice, + error: Some(nip47::NIP47Error { + code: nip47::ErrorCode::NotImplemented, + message: "Not implemented".to_owned(), + }), + result: None, + }, + None, + )] } diff --git a/src/nwc_info.rs b/src/nwc_info.rs index 27e196a..d3930e2 100644 --- a/src/nwc_info.rs +++ b/src/nwc_info.rs @@ -1,6 +1,6 @@ use cln_plugin::Plugin; use cln_rpc::model::requests::GetinfoRequest; -use nostr::nips::nip47; +use nostr_sdk::nips::nip47; use crate::{ structs::PluginState, diff --git a/src/nwc_invoice.rs b/src/nwc_invoice.rs index f521673..654a0b0 100644 --- a/src/nwc_invoice.rs +++ b/src/nwc_invoice.rs @@ -5,7 +5,7 @@ use cln_rpc::{ model::requests::InvoiceRequest, primitives::{Amount, AmountOrAny, Sha256}, }; -use nostr::{nips::nip47, types::Timestamp}; +use nostr_sdk::nips::nip47; use uuid::Uuid; use crate::structs::PluginState; @@ -94,8 +94,8 @@ async fn make_invoice( description_hash: params.description_hash, preimage: None, amount: Some(params.amount), - created_at: Some(Timestamp::now()), - expires_at: Some(Timestamp::from_secs(o.expires_at)), + created_at: Some(nostr_sdk::nostr::Timestamp::now()), + expires_at: Some(nostr_sdk::nostr::Timestamp::from_secs(o.expires_at)), }), Err(e) => Err(nip47::NIP47Error { code: nip47::ErrorCode::Internal, diff --git a/src/nwc_keysend.rs b/src/nwc_keysend.rs index aec724d..477db88 100644 --- a/src/nwc_keysend.rs +++ b/src/nwc_keysend.rs @@ -1,30 +1,18 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{str::FromStr, time::Duration}; use cln_plugin::Plugin; use cln_rpc::{ - ClnRpc, - RpcError, - model::requests::{KeysendRequest, XkeysendRequest}, - primitives::{Amount, PublicKey, Secret, TlvEntry, TlvStream}, + model::requests::KeysendRequest, + primitives::{Amount, PublicKey, TlvEntry, TlvStream}, }; -use nostr::nips::nip47::{self}; +use nostr_sdk::nips::nip47; +use tokio::time; use crate::{ structs::PluginState, - util::{ - budget_amount_check, - get_budget_msat, - load_nwc_store, - payment_fee_reserve_msat, - refund_budget, - reserve_budget, - rpc_socket_path, - settle_budget, - }, + util::{budget_amount_check, load_nwc_store, update_nwc_store}, }; -pub const XKEYSEND_COMMAND: &str = "xkeysend"; - pub async fn pay_keysend_response( plugin: Plugin, params: nip47::PayKeysendRequest, @@ -61,6 +49,8 @@ async fn pay_keysend( params: nip47::PayKeysendRequest, label: &str, ) -> Result { + let mut rpc = plugin.state().rpc_lock.lock().await; + if params.preimage.is_some() { return Err(nip47::NIP47Error { code: nip47::ErrorCode::Other, @@ -68,142 +58,32 @@ async fn pay_keysend( }); } + let mut nwc_store = load_nwc_store(&mut rpc, label) + .await + .map_err(|e| nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + })?; + + budget_amount_check(Some(params.amount), None, nwc_store.budget_msat).map_err(|e| { + nip47::NIP47Error { + code: nip47::ErrorCode::QuotaExceeded, + message: e.to_string(), + } + })?; + let pubkey = PublicKey::from_str(¶ms.pubkey).map_err(|e| nip47::NIP47Error { code: nip47::ErrorCode::Other, message: e.to_string(), })?; - let reservation = { - let mut rpc = plugin.state().rpc_lock.lock().await; - - let nwc_store = load_nwc_store(&mut rpc, label) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - - budget_amount_check(Some(params.amount), None, get_budget_msat(&nwc_store)).map_err( - |e| nip47::NIP47Error { - code: nip47::ErrorCode::QuotaExceeded, - message: e.to_string(), - }, - )?; - - // Reserve amount plus worst case fee so concurrent payments can never - // exceed the budget. - if get_budget_msat(&nwc_store).unwrap_or(u64::MAX) - < params - .amount - .saturating_add(payment_fee_reserve_msat(params.amount)) - { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::QuotaExceeded, - message: "Payment and estimated fees exceed the available budget".to_owned(), - }); - } - - reserve_budget(&mut rpc, label, &nwc_store, params.amount) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })? - }; - - let has_xkeysend = plugin.state().config.lock().has_xkeysend; - let mut pay_rpc = - ClnRpc::new(rpc_socket_path(&plugin)) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: format!("Could not connect to lightningd: {e}"), - })?; - - let pay_result = if has_xkeysend { - xkeysend(&mut pay_rpc, ¶ms, pubkey).await - } else { - keysend(&mut pay_rpc, ¶ms, pubkey).await - }; - - match pay_result { - Ok((amount_sent_msat, amount_msat, preimage)) => { - let mut rpc = plugin.state().rpc_lock.lock().await; - if let Err(e) = settle_budget(&mut rpc, label, reservation, amount_sent_msat).await { - log::error!("Error updating budget after successful keysend: {e}"); - } - - let preimage = hex::encode(preimage.to_vec()); - let fees_paid = amount_sent_msat.saturating_sub(amount_msat); - - Ok(nip47::PayKeysendResponse { - preimage, - fees_paid: Some(fees_paid), - }) - } - Err(e) => { - let mut rpc = plugin.state().rpc_lock.lock().await; - if let Err(refund_err) = refund_budget(&mut rpc, label, reservation).await { - log::error!( - "Error refunding budget reservation after failed keysend: {refund_err}" - ); - } - Err(map_keysend_error(&e, has_xkeysend)) - } - } -} - -async fn xkeysend( - pay_rpc: &mut ClnRpc, - params: &nip47::PayKeysendRequest, - pubkey: PublicKey, -) -> Result<(u64, u64, Secret), RpcError> { - let mut extratlvs = HashMap::with_capacity(params.tlv_records.len()); - for tlv in ¶ms.tlv_records { - extratlvs.insert(tlv.tlv_type.to_string(), tlv.value.clone()); - } - let extratlvs = if extratlvs.is_empty() { - None - } else { - Some(extratlvs) - }; - - let o = pay_rpc - .call_typed(&XkeysendRequest { - extratlvs, - label: None, - maxdelay: None, - maxfee: None, - retry_for: None, - layers: None, - amount_msat: Amount::from_msat(params.amount), - destination: pubkey, - }) - .await?; - - Ok(( - o.amount_sent_msat.msat(), - o.amount_msat.msat(), - o.payment_preimage, - )) -} - -async fn keysend( - pay_rpc: &mut ClnRpc, - params: &nip47::PayKeysendRequest, - pubkey: PublicKey, -) -> Result<(u64, u64, Secret), RpcError> { let mut extratlvs = TlvStream { entries: Vec::new(), }; - for tlv in ¶ms.tlv_records { + for tlv in params.tlv_records { extratlvs.entries.push(TlvEntry { typ: tlv.tlv_type, - value: hex::decode(&tlv.value).map_err(|e| RpcError { - code: Some(-32700), - message: format!("Could not decode tlv bytes: {e}"), - data: None, - })?, + value: tlv.value.as_bytes().to_owned(), }); } let extratlvs = if extratlvs.entries.is_empty() { @@ -212,7 +92,7 @@ async fn keysend( Some(extratlvs) }; - let o = pay_rpc + match rpc .call_typed(&KeysendRequest { exemptfee: None, extratlvs, @@ -225,48 +105,80 @@ async fn keysend( amount_msat: Amount::from_msat(params.amount), destination: pubkey, }) - .await?; + .await + { + Ok(o) => { + if let Some(ref mut bdg) = nwc_store.budget_msat { + *bdg = bdg.saturating_sub(o.amount_sent_msat.msat()); + update_nwc_store(&mut rpc, label, nwc_store) + .await + .map_err(|e| nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + })?; + } - Ok(( - o.amount_sent_msat.msat(), - o.amount_msat.msat(), - o.payment_preimage, - )) -} + let preimage = hex::encode(o.payment_preimage.to_vec()); -fn map_keysend_error(e: &RpcError, is_xkeysend: bool) -> nip47::NIP47Error { - let Some(c) = e.code else { - return nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - }; - }; + let fees_paid = o.amount_sent_msat.msat() - o.amount_msat.msat(); - let failed_codes = if is_xkeysend { - vec![203, 205, 207, 219] - } else { - vec![203, 205, 210] - }; - - if failed_codes.contains(&c) { - nip47::NIP47Error { - code: nip47::ErrorCode::PaymentFailed, - message: e.to_string(), - } - } else if is_xkeysend && c == 209 { - nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: e.to_string(), - } - } else if !is_xkeysend && c == 206 { - nip47::NIP47Error { - code: nip47::ErrorCode::InsufficientBalance, - message: e.to_string(), - } - } else { - nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), + Ok(nip47::PayKeysendResponse { + preimage, + fees_paid: Some(fees_paid), + }) } + Err(e) => match e.code { + Some(c) => match c { + 203 | 205 | 210 => Err(nip47::NIP47Error { + code: nip47::ErrorCode::PaymentFailed, + message: e.to_string(), + }), + 206 => Err(nip47::NIP47Error { + code: nip47::ErrorCode::InsufficientBalance, + message: e.to_string(), + }), + _ => Err(nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + }), + }, + None => Err(nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + }), + }, } } + +pub async fn multi_pay_keysend( + plugin: Plugin, + params: nip47::MultiPayKeysendRequest, + label: &str, +) -> Vec<(nip47::Response, Option)> { + let mut responses = Vec::new(); + for pay in params.keysends { + let result = pay_keysend(plugin.clone(), pay.clone(), label).await; + let id = if let Some(i) = pay.id { i } else { pay.pubkey }; + let response_res = match result { + Ok(resp) => ( + nip47::Response { + result_type: nip47::Method::MultiPayKeysend, + error: None, + result: Some(nip47::ResponseResult::MultiPayKeysend(resp)), + }, + Some(id), + ), + Err(e) => ( + nip47::Response { + result_type: nip47::Method::MultiPayKeysend, + error: Some(e), + result: None, + }, + Some(id), + ), + }; + responses.push(response_res); + time::sleep(Duration::from_millis(100)).await; + } + responses +} diff --git a/src/nwc_lookups.rs b/src/nwc_lookups.rs index 42ce1f6..33bea1f 100644 --- a/src/nwc_lookups.rs +++ b/src/nwc_lookups.rs @@ -2,20 +2,9 @@ use std::{cmp::Reverse, str::FromStr}; use cln_plugin::Plugin; use cln_rpc::{ - ClnRpc, model::{ - requests::{ - DecodeRequest, - ListinvoicesIndex, - ListinvoicesRequest, - ListpaysRequest, - WaitIndexname, - WaitRequest, - WaitSubsystem, - }, + requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest}, responses::{ - DecodeResponse, - DecodeType, ListinvoicesInvoices, ListinvoicesInvoicesStatus, ListpaysPays, @@ -23,22 +12,11 @@ use cln_rpc::{ }, }, primitives::Sha256, + ClnRpc, }; -use nostr::{nips::nip47, types::Timestamp}; -use tonic::transport::Channel; +use nostr_sdk::{nips::nip47, Timestamp}; -use crate::{ - hold::{self, ListRequest, hold_client::HoldClient, list_request::Constraint}, - structs::{NOT_INV_ERR, PluginState}, - util::rpc_socket_path, -}; - -#[allow(clippy::doc_markdown)] -/// Hard bound on the number of transactions a single `list_transactions` -/// request will fetch, decode and return (DoS protection). -const MAX_TRANSACTIONS: usize = 500; -/// Bound on the size of a `list_transactions` response in bytes. -const RESPONSE_LIMIT_BYTES: usize = 127 * 1024; +use crate::structs::{PluginState, NOT_INV_ERR}; pub async fn lookup_invoice_response( plugin: Plugin, @@ -80,7 +58,7 @@ async fn lookup_invoice( let invoice = if params.payment_hash.is_some() && params.invoice.is_some() { None } else { - params.invoice.clone() + params.invoice }; let invoices = rpc @@ -103,275 +81,46 @@ async fn lookup_invoice( if invoices.len() == 1 { let invoice_response = invoices.into_iter().next().unwrap(); - return make_lookup_response_from_listinvoices(&mut rpc, invoice_response).await; - } - - let payment_hash_hash = if let Some(hash) = ¶ms.payment_hash { - if let Ok(res) = Sha256::from_str(hash) { - Some(res) + make_lookup_response_from_listinvoices(&mut rpc, invoice_response).await + } else { + let payment_hash_hash = if let Some(hash) = params.payment_hash { + if let Ok(res) = Sha256::from_str(&hash) { + Some(res) + } else { + return Err(nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: "Could not convert payment hash".to_owned(), + }); + } } else { - return Err(nip47::NIP47Error { + None + }; + + let pays = rpc + .call_typed(&ListpaysRequest { + bolt11: invoice, + index: None, + limit: None, + payment_hash: payment_hash_hash, + start: None, + status: None, + }) + .await + .map_err(|e| nip47::NIP47Error { code: nip47::ErrorCode::Internal, - message: "Could not convert payment hash".to_owned(), + message: e.to_string(), + })? + .pays; + + if pays.len() != 1 { + return Err(nip47::NIP47Error { + code: nip47::ErrorCode::NotFound, + message: "Transaction not found".to_owned(), }); } - } else { - None - }; - - let pays = rpc - .call_typed(&ListpaysRequest { - bolt11: invoice, - index: None, - limit: None, - payment_hash: payment_hash_hash, - start: None, - status: None, - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })? - .pays; - - if pays.len() == 1 { let list_pay = pays.into_iter().next().unwrap(); - return make_lookup_response_from_listpays(&mut rpc, list_pay).await; - } - - let holdinvoice_support = plugin.state().hold_client.lock().is_some(); - - if holdinvoice_support { - let mut hold_client = plugin.state().hold_client.lock().clone().unwrap(); - return lookup_holdinvoice(&mut hold_client, &mut rpc, params).await; - } - - Err(nip47::NIP47Error { - code: nip47::ErrorCode::NotFound, - message: "Transaction not found".to_owned(), - }) -} - -async fn lookup_holdinvoice( - hold_client: &mut HoldClient, - rpc: &mut ClnRpc, - params: nip47::LookupInvoiceRequest, -) -> Result { - log::debug!("Looking up hold invoice for params {params:#?}"); - let (hold_invoice, invoice_decoded) = - get_and_decode_holdinvoice(rpc, hold_client, params).await?; - let not_invoice_err = Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: NOT_INV_ERR.to_owned(), - }); - - let description = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description, - DecodeType::BOLT11_INVOICE => invoice_decoded.description, - _ => return not_invoice_err, - }; - let description_hash = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => None, - DecodeType::BOLT11_INVOICE => invoice_decoded.description_hash.map(|h| h.to_string()), - _ => return not_invoice_err, - }; - - let created_at = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => { - Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap()) - } - DecodeType::BOLT11_INVOICE => Timestamp::from_secs(invoice_decoded.created_at.unwrap()), - _ => return not_invoice_err, - }; - - let amount = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_amount_msat.unwrap().msat(), - DecodeType::BOLT11_INVOICE => { - if let Some(amt) = invoice_decoded.amount_msat { - amt.msat() - } else { - // amount: `any` but have to put a value... - 0 - } - } - _ => return not_invoice_err, - }; - - let expires_at = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded - .invoice_relative_expiry - .map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))), - DecodeType::BOLT11_INVOICE => invoice_decoded - .expiry - .map(|e_at| created_at + Timestamp::from_secs(e_at)), - _ => return not_invoice_err, - }; - - let state = match hold_invoice.state() { - hold::InvoiceState::Unpaid => nip47::TransactionState::Pending, - hold::InvoiceState::Accepted => nip47::TransactionState::Accepted, - hold::InvoiceState::Paid => nip47::TransactionState::Settled, - hold::InvoiceState::Cancelled => nip47::TransactionState::Expired, - }; - - let settled_at = if hold_invoice.settled_at() != 0 { - Some(Timestamp::from_secs(hold_invoice.settled_at())) - } else { - None - }; - - let preimage = if hold_invoice.state() == hold::InvoiceState::Paid { - Some(hex::encode(hold_invoice.preimage())) - } else { - None - }; - - Ok(nip47::LookupInvoiceResponse { - transaction_type: Some(nip47::TransactionType::Incoming), - invoice: Some(hold_invoice.invoice.clone()), - description, - description_hash, - preimage, - payment_hash: hex::encode(hold_invoice.payment_hash), - amount, - fees_paid: 0, - created_at, - expires_at, - settled_at, - metadata: None, - state: Some(state), - }) -} - -#[allow(clippy::too_many_lines)] -async fn get_and_decode_holdinvoice( - rpc: &mut ClnRpc, - hold_client: &mut HoldClient, - params: nip47::LookupInvoiceRequest, -) -> Result<(hold::Invoice, DecodeResponse), nip47::NIP47Error> { - if let Some(ph) = ¶ms.payment_hash { - let payment_hash_hash = match hex::decode(ph) { - Ok(p) => p, - Err(_e) => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid payment hash".to_owned(), - }); - } - }; - let list_request = ListRequest { - constraint: Some(Constraint::PaymentHash(payment_hash_hash)), - }; - let hold_lookup = hold_client - .list(list_request) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: format!("Could not fetch hold invoice: {e}"), - })? - .into_inner(); - - if hold_lookup.invoices.len() != 1 { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Transaction not found".to_owned(), - }); - } - - let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap(); - let invoice_decoded = rpc - .call_typed(&DecodeRequest { - string: hold_invoice.invoice.clone(), - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - if !invoice_decoded.valid { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid invoice decoded".to_owned(), - }); - } - - Ok((hold_invoice, invoice_decoded)) - } else { - let invoice_decoded = rpc - .call_typed(&DecodeRequest { - string: params.invoice.unwrap(), - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - if !invoice_decoded.valid { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid invoice decoded".to_owned(), - }); - } - - let ph = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_payment_hash.unwrap(), - DecodeType::BOLT11_INVOICE => invoice_decoded.payment_hash.unwrap().to_string(), - _ => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Not a supported invoice type".to_owned(), - }); - } - }; - let payment_hash_hash = match hex::decode(&ph) { - Ok(p) => p, - Err(_e) => { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid payment hash in invoice".to_owned(), - }); - } - }; - - let list_request = ListRequest { - constraint: Some(Constraint::PaymentHash(payment_hash_hash)), - }; - - let hold_lookup = hold_client - .list(list_request) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: format!("Could not fetch hold invoice: {e}"), - })? - .into_inner(); - - if hold_lookup.invoices.len() != 1 { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Transaction not found".to_owned(), - }); - } - - let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap(); - let invoice_decoded = rpc - .call_typed(&DecodeRequest { - string: hold_invoice.invoice.clone(), - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - if !invoice_decoded.valid { - return Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: "Invalid invoice decoded".to_owned(), - }); - } - Ok((hold_invoice, invoice_decoded)) + make_lookup_response_from_listpays(&mut rpc, list_pay).await } } @@ -403,37 +152,7 @@ async fn list_transactions( plugin: Plugin, params: nip47::ListTransactionsRequest, ) -> Result, nip47::NIP47Error> { - if params.limit == Some(0) { - return Ok(Vec::new()); - } - - let mut rpc = ClnRpc::new(rpc_socket_path(&plugin)) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: format!("Could not connect to lightningd: {e}"), - })?; - - let limit = usize::try_from(params.limit.unwrap_or(MAX_TRANSACTIONS as u64)).map_err(|e| { - nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: format!("32-bit usize limit exceeded: {e}"), - } - })?; - let offset = usize::try_from(params.offset.unwrap_or(0)).map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: format!("32-bit usize limit exceeded: {e}"), - })?; - let want = limit.saturating_add(offset); - // Enough candidates from each source to satisfy the requested window even - // if some get filtered out again by `from`/`until`/`unpaid`. - let per_source = want - .saturating_mul(2) - .saturating_add(16) - .min(MAX_TRANSACTIONS); - let from = params.from.map(|t| t.as_secs()); - let until = params.until.map(|t| t.as_secs()); - let unpaid = params.unpaid.unwrap_or(false); + let mut rpc = plugin.state().rpc_lock.lock().await; let (query_invoices, query_payments) = match params.transaction_type { Some(t) => match t { @@ -443,29 +162,62 @@ async fn list_transactions( None => (true, true), }; + let unpaid = params.unpaid.unwrap_or(false); + let mut transactions: Vec = Vec::new(); - if query_invoices { - let invoices = collect_invoices(&mut rpc, per_source, from, until, unpaid).await?; - transactions.extend(invoices); + let list_invoices = rpc + .call_typed(&ListinvoicesRequest { + index: None, + invstring: None, + label: None, + limit: None, + offer_id: None, + payment_hash: None, + start: None, + }) + .await + .map_err(|e| nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + })? + .invoices; - let hold_client = plugin.state().hold_client.lock().clone(); - if let Some(mut hold_client) = hold_client { - list_holdinvoices_to_transactions( - &mut hold_client, - &mut rpc, - per_source, - from, - until, - &mut transactions, - ) - .await?; + for list_invoice in list_invoices { + if !unpaid && list_invoice.status == ListinvoicesInvoicesStatus::UNPAID { + continue; + } + + match make_lookup_response_from_listinvoices(&mut rpc, list_invoice).await { + Ok(t) => transactions.push(t), + Err(_e) => (), + } } } if query_payments { - let pays = collect_pays(&mut rpc, per_source, from, until).await?; - transactions.extend(pays); + let list_pays = rpc + .call_typed(&ListpaysRequest { + bolt11: None, + index: None, + limit: None, + payment_hash: None, + start: None, + status: None, + }) + .await + .map_err(|e| nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + })? + .pays; + + for list_pay in list_pays { + match make_lookup_response_from_listpays(&mut rpc, list_pay).await { + Ok(t) => transactions.push(t), + Err(_e) => (), + } + } } transactions.sort_by_key(|t| Reverse(t.created_at)); @@ -488,354 +240,11 @@ async fn list_transactions( } } - transactions = trim_to_size(transactions, RESPONSE_LIMIT_BYTES); + transactions = trim_to_size(transactions, 127 * 1024); Ok(transactions) } -async fn max_invoice_created_index(rpc: &mut ClnRpc) -> Result, cln_rpc::RpcError> { - let current_index = rpc - .call_typed(&WaitRequest { - indexname: WaitIndexname::CREATED, - subsystem: WaitSubsystem::INVOICES, - nextvalue: 0, - }) - .await? - .created - .ok_or_else(|| cln_rpc::RpcError { - code: Some(-32700), - message: "Missing created field in wait response".to_owned(), - data: None, - })?; - - if current_index == 0 { - return Ok(None); - } - Ok(Some(current_index)) -} - -async fn first_invoice_created_index(rpc: &mut ClnRpc) -> Result { - let first_index = rpc - .call_typed(&ListinvoicesRequest { - label: None, - invstring: None, - payment_hash: None, - offer_id: None, - index: Some(ListinvoicesIndex::CREATED), - start: Some(0), - limit: Some(1), - }) - .await? - .invoices - .first() - .map_or(0, |i| i.created_index); - Ok(first_index) -} - -/// Collect up to `per_source` most recent paid/public invoices within -/// `[from, until]` by paging backwards through `created_index`, decoding only -/// what is needed. Bounded by `MAX_TRANSACTIONS` regardless of node history. -async fn collect_invoices( - rpc: &mut ClnRpc, - per_source: usize, - from: Option, - until: Option, - unpaid: bool, -) -> Result, nip47::NIP47Error> { - const PAGE: u32 = 200; - const MAX_RAW_ROWS: usize = MAX_TRANSACTIONS * 8; - - let Some(max_idx) = max_invoice_created_index(rpc) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })? - else { - return Ok(Vec::new()); - }; - - let first_index = first_invoice_created_index(rpc) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - - let mut out = Vec::new(); - let mut raw_seen = 0usize; - let mut start_excl = max_idx.saturating_add(1); - - loop { - let page_start = start_excl.saturating_sub(u64::from(PAGE)); - let rows = rpc - .call_typed(&ListinvoicesRequest { - index: Some(ListinvoicesIndex::CREATED), - invstring: None, - label: None, - limit: Some(PAGE), - offer_id: None, - payment_hash: None, - start: Some(page_start), - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })? - .invoices; - - // Process newest first within the page. - for row in rows.into_iter().rev() { - raw_seen += 1; - if raw_seen > MAX_RAW_ROWS { - return Ok(out); - } - - if row.created_index >= start_excl { - continue; - } - - if !unpaid && row.status == ListinvoicesInvoicesStatus::UNPAID { - continue; - } - - match make_lookup_response_from_listinvoices(rpc, row).await { - Ok(tx) => { - let created = tx.created_at.as_secs(); - if let Some(f) = from { - // Newest first, so everything older is also out of range. - if created < f { - return Ok(out); - } - } - if let Some(u) = until { - if created > u { - continue; - } - } - out.push(tx); - if out.len() >= per_source { - return Ok(out); - } - } - Err(_e) => (), - } - } - - if page_start == 0 || page_start < first_index { - break; - } - start_excl = page_start; - } - - Ok(out) -} - -/// Collect up to `per_source` most recent payments within `[from, until]`, -/// decoding only the kept subset. `listpays` returns `created_at` directly, so -/// the whole payment history can be filtered before any decode. -async fn collect_pays( - rpc: &mut ClnRpc, - per_source: usize, - from: Option, - until: Option, -) -> Result, nip47::NIP47Error> { - let pays = rpc - .call_typed(&ListpaysRequest { - bolt11: None, - index: None, - limit: None, - payment_hash: None, - start: None, - status: None, - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })? - .pays; - - // Newest first. - let mut pays = pays; - pays.sort_by_key(|p| Reverse((p.created_at, p.created_index.unwrap_or(0)))); - - let mut out = Vec::new(); - for pay in pays { - if out.len() >= per_source { - break; - } - if let Some(u) = until { - if pay.created_at > u { - continue; - } - } - if let Some(f) = from { - // Newest first, so the rest are also out of range. - if pay.created_at < f { - break; - } - } - - match make_lookup_response_from_listpays(rpc, pay).await { - Ok(tx) => out.push(tx), - Err(_e) => (), - } - } - - Ok(out) -} - -async fn list_holdinvoices_to_transactions( - hold_client: &mut HoldClient, - rpc: &mut ClnRpc, - per_source: usize, - from: Option, - until: Option, - transactions: &mut Vec, -) -> Result<(), nip47::NIP47Error> { - let lookup_request = ListRequest { constraint: None }; - - let hold_lookup = hold_client - .list(lookup_request) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: format!("Could not fetch hold invoices: {e}"), - })? - .into_inner(); - - // Decode only the most recent `per_source` hold invoices (newest first). - let mut hold_invoices = hold_lookup.invoices; - hold_invoices.sort_by_key(|inv| Reverse(inv.created_at)); - - let mut kept = 0usize; - for hold_invoice in hold_invoices { - if kept >= per_source { - break; - } - if let Some(u) = until { - if hold_invoice.created_at > u { - continue; - } - } - if let Some(f) = from { - // Newest first, so the rest are also out of range. - if hold_invoice.created_at < f { - break; - } - } - - match make_lookup_response_from_holdinvoice(rpc, &hold_invoice).await { - Ok(tx) => { - transactions.push(tx); - kept += 1; - } - Err(_e) => (), - } - } - - Ok(()) -} - -async fn make_lookup_response_from_holdinvoice( - rpc: &mut ClnRpc, - hold_invoice: &hold::Invoice, -) -> Result { - let not_invoice_err = Err(nip47::NIP47Error { - code: nip47::ErrorCode::Other, - message: NOT_INV_ERR.to_owned(), - }); - - let invoice_decoded = rpc - .call_typed(&DecodeRequest { - string: hold_invoice.invoice.clone(), - }) - .await - .map_err(|e| nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - })?; - - let description = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description, - DecodeType::BOLT11_INVOICE => invoice_decoded.description, - _ => return not_invoice_err, - }; - let description_hash = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => None, - DecodeType::BOLT11_INVOICE => invoice_decoded.description_hash.map(|h| h.to_string()), - _ => return not_invoice_err, - }; - - let created_at = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => { - Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap()) - } - DecodeType::BOLT11_INVOICE => Timestamp::from_secs(invoice_decoded.created_at.unwrap()), - _ => return not_invoice_err, - }; - - let amount = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_amount_msat.unwrap().msat(), - DecodeType::BOLT11_INVOICE => { - if let Some(amt) = invoice_decoded.amount_msat { - amt.msat() - } else { - // amount: `any` but have to put a value... - 0 - } - } - _ => return not_invoice_err, - }; - - let expires_at = match invoice_decoded.item_type { - DecodeType::BOLT12_INVOICE => invoice_decoded - .invoice_relative_expiry - .map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))), - DecodeType::BOLT11_INVOICE => invoice_decoded - .expiry - .map(|e_at| created_at + Timestamp::from_secs(e_at)), - _ => return not_invoice_err, - }; - - let state = match hold_invoice.state() { - hold::InvoiceState::Unpaid => nip47::TransactionState::Pending, - hold::InvoiceState::Accepted => nip47::TransactionState::Accepted, - hold::InvoiceState::Paid => nip47::TransactionState::Settled, - hold::InvoiceState::Cancelled => nip47::TransactionState::Expired, - }; - - let settled_at = if hold_invoice.settled_at() != 0 { - Some(Timestamp::from_secs(hold_invoice.settled_at())) - } else { - None - }; - - let preimage = if hold_invoice.state() == hold::InvoiceState::Paid { - Some(hex::encode(hold_invoice.preimage())) - } else { - None - }; - - Ok(nip47::LookupInvoiceResponse { - transaction_type: Some(nip47::TransactionType::Incoming), - invoice: Some(hold_invoice.invoice.clone()), - description, - description_hash, - preimage, - payment_hash: hex::encode(hold_invoice.payment_hash.clone()), - amount, - fees_paid: 0, - created_at, - expires_at, - settled_at, - metadata: None, - state: Some(state), - }) -} - async fn make_lookup_response_from_listinvoices( rpc: &mut ClnRpc, list_invoice: ListinvoicesInvoices, @@ -845,12 +254,10 @@ async fn make_lookup_response_from_listinvoices( message: NOT_INV_ERR.to_owned(), }); - let invstring = if let Some(bolt11) = list_invoice.bolt11 { - bolt11 - } else if let Some(bolt12) = list_invoice.bolt12 { - bolt12 + let invstring = if list_invoice.bolt11.is_some() { + list_invoice.bolt11.as_ref().unwrap() } else { - return not_invoice_err; + list_invoice.bolt12.as_ref().unwrap() }; let invoice_decoded = rpc .call_typed(&DecodeRequest { @@ -918,7 +325,7 @@ async fn make_lookup_response_from_listinvoices( Ok(nip47::LookupInvoiceResponse { transaction_type: Some(nip47::TransactionType::Incoming), - invoice: Some(invstring), + invoice: Some(invstring.to_owned()), description, description_hash, preimage, @@ -1039,36 +446,30 @@ async fn make_lookup_response_from_listpays( }) } -/// Keep only the newest transactions that fit within `max_size` bytes. -/// Transactions must be sorted newest first. Serializes each transaction only -/// once and chunks the remaining (O(n), not O(n^2)). fn trim_to_size( mut transactions: Vec, max_size: usize, ) -> Vec { - let mut total_size = 0usize; - let mut keep = 0usize; - for tx in &transactions { - match serde_json::to_vec(tx) { + let length_before = transactions.len(); + while !transactions.is_empty() { + match serde_json::to_vec(&transactions) { Ok(serialized) => { - total_size += serialized.len(); - if total_size > max_size { - break; + if serialized.len() <= max_size { + log::info!( + "Trimmed {} transactions to stay under {}bytes", + length_before - transactions.len(), + max_size + ); + return transactions; } + transactions.pop(); } Err(e) => { - log::warn!("Failed to serialize transaction: {e}"); - break; + log::warn!("Failed to serialize transactions: {e}"); + return transactions; } } - keep += 1; } - let trimmed = transactions.len() - keep; - if trimmed > 0 { - log::info!("Trimmed {trimmed} transactions to stay under {max_size} bytes"); - } - transactions.truncate(keep); - - transactions + Vec::new() } diff --git a/src/nwc_notifications.rs b/src/nwc_notifications.rs index 302ba96..8f02b6a 100644 --- a/src/nwc_notifications.rs +++ b/src/nwc_notifications.rs @@ -3,10 +3,8 @@ use std::str::FromStr; use anyhow::anyhow; use cln_plugin::Plugin; use cln_rpc::{ - ClnRpc, - Notification, model::{ - requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest, ListpeerchannelsRequest}, + requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest}, responses::{ DecodeResponse, ListinvoicesInvoices, @@ -15,26 +13,21 @@ use cln_rpc::{ ListpaysPaysStatus, }, }, - notifications::{InvoicePaymentNotification, SendPaySuccessNotification}, primitives::Sha256, + ClnRpc, }; -use nostr::{ - event::{EventBuilder, FinalizeEventAsync, Kind, Tag}, - nips::{nip04, nip44, nip47}, - types::Timestamp, +use nostr_sdk::{ + nips::nip47, + nostr::{key::PublicKey, EventBuilder, Kind, Tag}, + Client, + Timestamp, }; use crate::{ + structs::{PluginState, NOT_INV_ERR}, OPT_NOTIFICATIONS, - hold::{InvoiceState, ListRequest, TrackRequest, list_request::Constraint}, - structs::{NOT_INV_ERR, PluginState, WalletService}, }; -/// Upper bound on how long a `holdinvoice_accepted_handler` waits for an -/// invoice to be accepted, so a client cannot pin a task and a gRPC stream -/// open forever with an absurd expiry. -const MAX_HOLD_WAIT_SECS: u64 = 24 * 60 * 60; - pub async fn payment_received_handler( plugin: Plugin, args: serde_json::Value, @@ -42,12 +35,13 @@ pub async fn payment_received_handler( if !plugin.option(&OPT_NOTIFICATIONS).unwrap() { return Ok(()); } - - let notif: Notification = serde_json::from_value(args)?; - let inv_pay_notif: InvoicePaymentNotification = match notif { - Notification::InvoicePayment(invoice_payment_notification) => invoice_payment_notification, - _ => return Err(anyhow!("Wrong notification type, expected invoice_payment")), - }; + let label = args + .get("invoice_payment") + .ok_or_else(|| anyhow!("Malformed invoice_payment notification: missing invoice_payment"))? + .get("label") + .ok_or_else(|| anyhow!("Malformed invoice_payment notification: missing label"))? + .as_str() + .ok_or_else(|| anyhow!("label not a string"))?; let mut rpc = plugin.state().rpc_lock.lock().await; @@ -55,7 +49,7 @@ pub async fn payment_received_handler( .call_typed(&ListinvoicesRequest { index: None, invstring: None, - label: Some(inv_pay_notif.label), + label: Some(label.to_owned()), limit: None, offer_id: None, payment_hash: None, @@ -67,41 +61,25 @@ pub async fn payment_received_handler( let invoice = invoice_resp .first() .ok_or_else(|| anyhow!("invoice not found"))?; - let invstring = if let Some(bolt11) = &invoice.bolt11 { - bolt11.clone() - } else if let Some(bolt12) = &invoice.bolt12 { - bolt12.clone() + let invstring = if invoice.bolt11.is_some() { + invoice.bolt11.as_ref().unwrap() } else { - return Err(anyhow!( - "Listinvoices has neither returned bolt11 or bolt12 field" - )); + invoice.bolt12.as_ref().unwrap() }; - let payment_hash_str = hex::encode(invoice.payment_hash); - let invoice_decoded = rpc .call_typed(&DecodeRequest { string: invstring.clone(), }) .await?; - if !invoice_decoded.valid { - return Err(anyhow!("Invalid invoice decoded for {payment_hash_str}")); - } - let notification = make_payment_received_from_listinvoices(invoice, invstring, invoice_decoded)?; let clients = plugin.state().handles.lock().await; - for wallet_service in clients.values() { - if let Err(e) = send_notification(¬ification, wallet_service).await { - log::warn!( - "Failed sending payment_received notification for {payment_hash_str} \ - to client {}: {e}", - wallet_service.client_pubkey - ); - } + for (client, client_pubkey) in clients.values() { + send_notification(¬ification, client, client_pubkey).await?; } Ok(()) @@ -109,7 +87,7 @@ pub async fn payment_received_handler( fn make_payment_received_from_listinvoices( invoice: &ListinvoicesInvoices, - invstring: String, + invstring: &str, invoice_decoded: DecodeResponse, ) -> Result { let not_invoice_err = Err(anyhow!(NOT_INV_ERR.to_owned())); @@ -177,7 +155,7 @@ fn make_payment_received_from_listinvoices( notification_type: nip47::NotificationType::PaymentReceived, notification: nip47::NotificationResult::PaymentReceived(nip47::PaymentNotification { transaction_type: Some(nip47::TransactionType::Incoming), - invoice: invstring, + invoice: invstring.to_owned(), description: description.clone(), description_hash: description_hash.clone(), preimage: preimage.clone(), @@ -204,15 +182,14 @@ pub async fn payment_sent_handler( if !plugin.option(&OPT_NOTIFICATIONS).unwrap() { return Ok(()); } - - let notif: Notification = serde_json::from_value(args)?; - let send_pay_notif: SendPaySuccessNotification = match notif { - Notification::SendPaySuccess(send_pay_success_notification) => { - send_pay_success_notification - } - _ => return Err(anyhow!("Wrong notification type, expected sendpay_success")), - }; - let payment_hash = hex::encode(send_pay_notif.payment_hash); + let payment_hash = args + .get("sendpay_success") + .ok_or_else(|| anyhow!("Malformed sendpay_success notification: missing sendpay_success"))? + .get("payment_hash") + .ok_or_else(|| anyhow!("Malformed sendpay_success notification: missing payment_hash"))? + .as_str() + .ok_or_else(|| anyhow!("payment_hash not a string"))? + .to_owned(); let mut rpc = plugin.state().rpc_lock.lock().await; @@ -238,14 +215,8 @@ pub async fn payment_sent_handler( let clients = plugin.state().handles.lock().await; - for wallet_service in clients.values() { - if let Err(e) = send_notification(¬ification, wallet_service).await { - log::warn!( - "Failed sending payment_sent notification for {payment_hash} to\ - client {}: {e}", - wallet_service.client_pubkey - ); - } + for (client, client_pubkey) in clients.values() { + send_notification(¬ification, client, client_pubkey).await?; } Ok(()) @@ -366,19 +337,17 @@ async fn make_payment_sent_from_listpays( async fn send_notification( notification: &String, - wallet_service: &WalletService, + client: &Client, + client_pubkey: &PublicKey, ) -> Result<(), anyhow::Error> { - log::trace!("NOTIFICATION: {notification}"); - let content_encrypted_nip04 = nip04::encrypt( - wallet_service.wallet_secret.secret_key(), - &wallet_service.client_pubkey, - notification, - )?; + let signer = client.signer().await?; + log::debug!("NOTIFICATION: {notification}"); + let content_encrypted_nip04 = signer.nip04_encrypt(client_pubkey, notification).await?; let event_nip04 = EventBuilder::new(Kind::from_u16(23196), content_encrypted_nip04) - .tag(Tag::public_key(wallet_service.client_pubkey)) - .finalize_async(&wallet_service.wallet_secret) + .tag(Tag::public_key(*client_pubkey)) + .sign(&signer) .await?; - let nip04_result = wallet_service.client.send_event(&event_nip04).await?; + let nip04_result = client.send_event(&event_nip04).await?; if nip04_result.success.is_empty() { log::warn!( "None of the relays accepted our nip04 notification: {}", @@ -389,19 +358,14 @@ async fn send_notification( .join(", ") ); } - log::trace!("NIP04 NOTIFICATION SENT: {event_nip04:?}"); + log::debug!("NIP04 NOTIFICATION SENT: {event_nip04:?}"); - let content_encrypted_nip44 = nip44::encrypt( - wallet_service.wallet_secret.secret_key(), - &wallet_service.client_pubkey, - notification, - nip44::Version::V2, - )?; + let content_encrypted_nip44 = signer.nip44_encrypt(client_pubkey, notification).await?; let event_nip44 = EventBuilder::new(Kind::from_u16(23197), content_encrypted_nip44) - .tag(Tag::public_key(wallet_service.client_pubkey)) - .finalize_async(&wallet_service.wallet_secret) + .tag(Tag::public_key(*client_pubkey)) + .sign(&signer) .await?; - let nip44_result = wallet_service.client.send_event(&event_nip44).await?; + let nip44_result = client.send_event(&event_nip44).await?; if nip44_result.success.is_empty() { log::warn!( "None of the relays accepted our nip44 notification: {}", @@ -412,177 +376,7 @@ async fn send_notification( .join(", ") ); } - log::trace!("NIP44 NOTIFICATION SENT: {event_nip44:?}"); + log::debug!("NIP44 NOTIFICATION SENT: {event_nip44:?}"); Ok(()) } - -#[allow(clippy::too_many_lines)] -pub async fn holdinvoice_accepted_handler( - plugin: Plugin, - payment_hash: Vec, - expires_at: u64, -) -> Result<(), anyhow::Error> { - let payment_hash_str = hex::encode(&payment_hash); - - let mut hold_client = plugin.state().hold_client.lock().clone().unwrap(); - - let track_request = TrackRequest { - payment_hash: payment_hash.clone(), - }; - let mut track_stream = hold_client.track(track_request).await?.into_inner(); - - // The hold plugin has no expired state: an invoice that expires without - // being accepted stays in the Unpaid state and the track stream never ends - // on its own. Bound the wait by the invoice's expiry (capped) so we do not - // hold this task and gRPC stream open forever. - let wait_duration = expires_at - .saturating_sub(Timestamp::now().as_secs()) - .min(MAX_HOLD_WAIT_SECS); - let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(wait_duration); - - let mut accepted = false; - loop { - match tokio::time::timeout_at(deadline, track_stream.message()).await { - Err(_elapsed) => { - log::debug!("Hold invoice {payment_hash_str} expired before being accepted"); - break; - } - Ok(Err(e)) => return Err(e.into()), - // The stream ended without an Accepted state: the invoice was - // cancelled before it ever got accepted. - Ok(Ok(None)) => break, - Ok(Ok(Some(response))) => { - log::debug!("Invoice status: {}", response.state().as_str_name()); - match response.state() { - InvoiceState::Accepted => { - accepted = true; - break; - } - InvoiceState::Paid | InvoiceState::Cancelled => break, - InvoiceState::Unpaid => (), - } - } - } - } - - if !accepted { - log::debug!("Hold invoice {payment_hash_str} was not accepted, skipping notification"); - return Ok(()); - } - - let list_request = ListRequest { - constraint: Some(Constraint::PaymentHash(payment_hash.clone())), - }; - - let hold_lookup = hold_client.list(list_request).await?.into_inner(); - - if hold_lookup.invoices.len() != 1 { - return Err(anyhow!("hold plugin did not return exactly one invoice")); - } - - let hold_invoice = hold_lookup.invoices.first().unwrap(); - - let mut rpc = plugin.state().rpc_lock.lock().await; - - let invoice_decoded = rpc - .call_typed(&DecodeRequest { - string: hold_invoice.invoice.clone(), - }) - .await?; - - let amount = match invoice_decoded.item_type { - cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => { - invoice_decoded.invoice_amount_msat.unwrap().msat() - } - cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => { - if let Some(amt) = invoice_decoded.amount_msat { - amt.msat() - } else { - // amount: `any` but have to put a value... - 0 - } - } - _ => return Err(anyhow!("hold plugin did not return an invoice string")), - }; - - let created_at = match invoice_decoded.item_type { - cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => { - Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap()) - } - cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => { - Timestamp::from_secs(invoice_decoded.created_at.unwrap()) - } - _ => return Err(anyhow!("hold plugin did not return an invoice string")), - }; - - let expires_at = match invoice_decoded.item_type { - cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => { - created_at - + Timestamp::from_secs(u64::from(invoice_decoded.invoice_relative_expiry.unwrap())) - } - cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => { - created_at + Timestamp::from_secs(invoice_decoded.expiry.unwrap()) - } - _ => return Err(anyhow!("hold plugin did not return an invoice string")), - }; - - let list_peer_channels = rpc - .call_typed(&ListpeerchannelsRequest { - id: None, - short_channel_id: None, - channel_id: None, - }) - .await? - .channels; - - let payment_hash_hash = Sha256::from_str(&payment_hash_str)?; - - let mut lowest_htlc_expiry = u32::MAX; - - for peer in list_peer_channels { - if let Some(htlcs) = peer.htlcs { - for htlc in htlcs { - if htlc.payment_hash != payment_hash_hash { - continue; - } - if htlc.expiry < lowest_htlc_expiry { - lowest_htlc_expiry = htlc.expiry; - } - } - } - } - - let clients = plugin.state().handles.lock().await; - - let content = nip47::Notification { - notification_type: nip47::NotificationType::HoldInvoiceAccepted, - notification: nip47::NotificationResult::HoldInvoiceAccepted( - nip47::HoldInvoiceAcceptedNotification { - transaction_type: nip47::TransactionType::Incoming, - invoice: hold_invoice.invoice.clone(), - description: None, - description_hash: None, - payment_hash: hex::encode(&hold_invoice.payment_hash), - amount, - created_at, - expires_at, - settle_deadline: lowest_htlc_expiry, - metadata: None, - state: Some(nip47::TransactionState::Accepted), - }, - ), - }; - let notification = serde_json::to_string(&content).unwrap(); - - for wallet_service in clients.values() { - if let Err(e) = send_notification(¬ification, wallet_service).await { - log::warn!( - "Failed sending hold_invoice_accepted {payment_hash_str} notification\ - to client {}: {e}", - wallet_service.client_pubkey - ); - } - } - Ok(()) -} diff --git a/src/nwc_pay.rs b/src/nwc_pay.rs index 4799465..73b64a0 100644 --- a/src/nwc_pay.rs +++ b/src/nwc_pay.rs @@ -1,31 +1,23 @@ +use std::time::Duration; + use cln_plugin::Plugin; use cln_rpc::{ - ClnRpc, - RpcError, model::{ requests::{DecodeRequest, PayRequest, XpayRequest}, responses::DecodeResponse, }, primitives::{Amount, Secret}, + ClnRpc, + RpcError, }; -use nostr::nips::nip47; +use nostr_sdk::nips::nip47; +use tokio::time; use crate::{ - structs::{NOT_INV_ERR, NwcStore, PluginState}, - util::{ - budget_amount_check, - get_budget_msat, - load_nwc_store, - payment_fee_reserve_msat, - refund_budget, - reserve_budget, - rpc_socket_path, - settle_budget, - }, + structs::{NwcStore, PluginState, NOT_INV_ERR}, + util::{at_or_above_version, budget_amount_check, load_nwc_store, update_nwc_store}, }; -pub const XPAY_COMMAND: &str = "xpay"; - pub async fn pay_invoice_response( plugin: Plugin, params: nip47::PayInvoiceRequest, @@ -56,118 +48,24 @@ async fn pay_invoice( params: nip47::PayInvoiceRequest, label: &str, ) -> Result<(nip47::PayInvoiceResponse, Option), (nip47::NIP47Error, Option)> { - let (id, reservation) = { - let mut rpc = plugin.state().rpc_lock.lock().await; + let mut rpc = plugin.state().rpc_lock.lock().await; - let decoded_invoice = decode_and_validate_invoice(&mut rpc, ¶ms).await?; + let decoded_invoice = decode_and_validate_invoice(&mut rpc, ¶ms).await?; - let id = get_payment_id(¶ms, &decoded_invoice)?; + let id = get_payment_id(¶ms, &decoded_invoice)?; - let invoice_amt_msat = get_invoice_amount_msat(&decoded_invoice); + let invoice_amt_msat = get_invoice_amount_msat(&decoded_invoice, &id)?; - let nwc_store = - load_nwc_and_check_budget(&mut rpc, label, ¶ms, invoice_amt_msat, &id).await?; + let nwc_store = + load_nwc_and_check_budget(&mut rpc, label, ¶ms, invoice_amt_msat, &id).await?; - let amt_msat = match (params.amount, invoice_amt_msat) { - (None, None) => { - return Err(( - nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: "No amount found in request or invoice".to_owned(), - }, - Some(id.clone()), - )); - } - (None, Some(b)) => b, - (Some(a), None) => a, - (Some(a), Some(b)) => { - if a != b { - return Err(( - nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: "request amount does not match invoice amount".to_owned(), - }, - Some(id.clone()), - )); - } - a - } - }; + let my_version = plugin.state().config.lock().clone().my_cln_version; + let use_xpay = check_cln_version(&my_version, &id)?; - // Reserve the invoice amount plus the worst case fee so that no - // combination of concurrent payments can exceed the budget and so that - // balance queries during the payment reflect the reserved amount. - if get_budget_msat(&nwc_store).unwrap_or(u64::MAX) - < amt_msat.saturating_add(payment_fee_reserve_msat(amt_msat)) - { - return Err(( - nip47::NIP47Error { - code: nip47::ErrorCode::QuotaExceeded, - message: "Payment and estimated fees exceed the available budget".to_owned(), - }, - Some(id), - )); - } - - let reservation = reserve_budget(&mut rpc, label, &nwc_store, amt_msat) - .await - .map_err(|e| { - ( - nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: e.to_string(), - }, - Some(id.clone()), - ) - })?; - - (id, reservation) - }; - - let has_xpay = plugin.state().config.lock().has_xpay; - - let mut pay_rpc = ClnRpc::new(rpc_socket_path(&plugin)).await.map_err(|e| { - ( - nip47::NIP47Error { - code: nip47::ErrorCode::Internal, - message: format!("Could not connect to lightningd: {e}"), - }, - Some(id.clone()), - ) - })?; - - let pay_result = if has_xpay { - pay_with_xpay(&mut pay_rpc, ¶ms).await + if use_xpay { + pay_with_xpay_full(&mut rpc, params, label, nwc_store, &id).await } else { - pay_with_legacy(&mut pay_rpc, ¶ms).await - }; - - match pay_result { - Ok((amount_sent_msat, amount_msat, preimage)) => { - let mut rpc = plugin.state().rpc_lock.lock().await; - if let Err(e) = settle_budget(&mut rpc, label, reservation, amount_sent_msat).await { - log::error!("Error updating budget after successful payment: {e}"); - } - - let preimage_str = hex::encode(preimage.to_vec()); - let fees_paid = amount_sent_msat.saturating_sub(amount_msat); - Ok(( - nip47::PayInvoiceResponse { - preimage: preimage_str, - fees_paid: Some(fees_paid), - }, - Some(id), - )) - } - Err(e) => { - let mut rpc = plugin.state().rpc_lock.lock().await; - if let Err(refund_err) = refund_budget(&mut rpc, label, reservation).await { - log::error!( - "Error refunding budget reservation after failed payment: {refund_err}" - ); - } - Err(map_cln_error_to_nip47(&e, &id, has_xpay)) - } + pay_with_legacy_full(&mut rpc, params, label, nwc_store, &id).await } } @@ -241,15 +139,30 @@ fn get_payment_id( Ok(id) } -fn get_invoice_amount_msat(decoded_invoice: &DecodeResponse) -> Option { - decoded_invoice.amount_msat.as_ref().map(Amount::msat) +fn get_invoice_amount_msat( + decoded_invoice: &DecodeResponse, + id: &str, +) -> Result)> { + decoded_invoice + .amount_msat + .as_ref() + .ok_or_else(|| { + ( + nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: "Missing amount_msat in decoded invoice".to_owned(), + }, + Some(id.to_owned()), + ) + }) + .map(Amount::msat) } async fn load_nwc_and_check_budget( rpc: &mut ClnRpc, label: &str, params: &nip47::PayInvoiceRequest, - invoice_amt_msat: Option, + invoice_amt_msat: u64, id: &str, ) -> Result)> { let nwc_store = load_nwc_store(rpc, label).await.map_err(|e| { @@ -262,7 +175,7 @@ async fn load_nwc_and_check_budget( ) })?; - budget_amount_check(params.amount, invoice_amt_msat, get_budget_msat(&nwc_store)).map_err( + budget_amount_check(params.amount, Some(invoice_amt_msat), nwc_store.budget_msat).map_err( |e| { ( nip47::NIP47Error { @@ -277,6 +190,56 @@ async fn load_nwc_and_check_budget( Ok(nwc_store) } +fn check_cln_version( + my_version: &str, + id: &str, +) -> Result)> { + at_or_above_version(my_version, "24.11").map_err(|e| { + ( + nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + }, + Some(id.to_owned()), + ) + }) +} + +async fn update_budget_and_create_response( + rpc: &mut ClnRpc, + label: &str, + nwc_store: &mut NwcStore, + amount_sent_msat: u64, + amount_msat: u64, + preimage: Secret, + id: &str, +) -> Result<(nip47::PayInvoiceResponse, Option), (nip47::NIP47Error, Option)> { + if let Some(ref mut bdg) = nwc_store.budget_msat { + *bdg = bdg.saturating_sub(amount_sent_msat); + update_nwc_store(rpc, label, nwc_store.clone()) + .await + .map_err(|e| { + ( + nip47::NIP47Error { + code: nip47::ErrorCode::Internal, + message: e.to_string(), + }, + Some(id.to_owned()), + ) + })?; + } + + let preimage_str = hex::encode(preimage.to_vec()); + let fees_paid = amount_sent_msat - amount_msat; + Ok(( + nip47::PayInvoiceResponse { + preimage: preimage_str, + fees_paid: Some(fees_paid), + }, + Some(id.to_owned()), + )) +} + fn map_cln_error_to_nip47( e: &RpcError, id: &str, @@ -314,8 +277,8 @@ fn map_cln_error_to_nip47( } else if !is_xpay && c == 206 { ( nip47::NIP47Error { - code: nip47::ErrorCode::PaymentFailed, - message: format!("Route too expensive: {e}"), + code: nip47::ErrorCode::InsufficientBalance, + message: e.to_string(), }, Some(id.to_owned()), ) @@ -339,11 +302,14 @@ fn map_cln_error_to_nip47( } } -async fn pay_with_xpay( - pay_rpc: &mut ClnRpc, - params: &nip47::PayInvoiceRequest, -) -> Result<(u64, u64, Secret), RpcError> { - let payment_result = pay_rpc +async fn pay_with_xpay_full( + rpc: &mut ClnRpc, + params: nip47::PayInvoiceRequest, + label: &str, + mut nwc_store: NwcStore, + id: &str, +) -> Result<(nip47::PayInvoiceResponse, Option), (nip47::NIP47Error, Option)> { + let payment_result = rpc .call_typed(&XpayRequest { amount_msat: params.amount.map(Amount::from_msat), maxdelay: None, @@ -351,26 +317,36 @@ async fn pay_with_xpay( partial_msat: None, retry_for: None, layers: None, - invstring: params.invoice.clone(), + invstring: params.invoice, payer_note: None, - dev_use_shadow: None, - label: None, - localinvreqid: None, }) - .await?; + .await + .map_err(|e| map_cln_error_to_nip47(&e, id, true))?; - Ok(( - payment_result.amount_sent_msat.msat(), - payment_result.amount_msat.msat(), - payment_result.payment_preimage, - )) + let amount_sent_msat = payment_result.amount_sent_msat.msat(); + let amount_msat = payment_result.amount_msat.msat(); + let preimage = payment_result.payment_preimage; + + update_budget_and_create_response( + rpc, + label, + &mut nwc_store, + amount_sent_msat, + amount_msat, + preimage, + id, + ) + .await } -async fn pay_with_legacy( - pay_rpc: &mut ClnRpc, - params: &nip47::PayInvoiceRequest, -) -> Result<(u64, u64, Secret), RpcError> { - let payment_result = pay_rpc +async fn pay_with_legacy_full( + rpc: &mut ClnRpc, + params: nip47::PayInvoiceRequest, + label: &str, + mut nwc_store: NwcStore, + id: &str, +) -> Result<(nip47::PayInvoiceResponse, Option), (nip47::NIP47Error, Option)> { + let payment_result = rpc .call_typed(&PayRequest { amount_msat: params.amount.map(Amount::from_msat), description: None, @@ -384,13 +360,55 @@ async fn pay_with_legacy( retry_for: None, riskfactor: None, exclude: None, - bolt11: params.invoice.clone(), + bolt11: params.invoice, }) - .await?; + .await + .map_err(|e| map_cln_error_to_nip47(&e, id, false))?; - Ok(( - payment_result.amount_sent_msat.msat(), - payment_result.amount_msat.msat(), - payment_result.payment_preimage, - )) + let amount_sent_msat = payment_result.amount_sent_msat.msat(); + let amount_msat = payment_result.amount_msat.msat(); + let preimage = payment_result.payment_preimage; + + update_budget_and_create_response( + rpc, + label, + &mut nwc_store, + amount_sent_msat, + amount_msat, + preimage, + id, + ) + .await +} + +pub async fn multi_pay_invoice( + plugin: Plugin, + params: nip47::MultiPayInvoiceRequest, + label: &str, +) -> Vec<(nip47::Response, Option)> { + let mut responses = Vec::new(); + for pay in params.invoices { + let result = pay_invoice(plugin.clone(), pay, label).await; + let response_res = match result { + Ok((resp, id)) => ( + nip47::Response { + result_type: nip47::Method::MultiPayInvoice, + error: None, + result: Some(nip47::ResponseResult::MultiPayInvoice(resp)), + }, + id, + ), + Err((e, id)) => ( + nip47::Response { + result_type: nip47::Method::MultiPayInvoice, + error: Some(e), + result: None, + }, + id, + ), + }; + responses.push(response_res); + time::sleep(Duration::from_millis(100)).await; + } + responses } diff --git a/src/parse.rs b/src/parse.rs index a8f9768..7c32966 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,72 +1,43 @@ -use std::{collections::HashSet, path::Path}; +use std::path::Path; use anyhow::anyhow; use cln_plugin::ConfiguredPlugin; -use cln_rpc::{ClnRpc, model::requests::HelpRequest}; -use nostr::types::RelayUrl; +use cln_rpc::{model::requests::GetinfoRequest, ClnRpc}; use crate::{ - OPT_RELAYS, - nwc_keysend::XKEYSEND_COMMAND, - nwc_pay::XPAY_COMMAND, structs::{PluginState, TimeUnit}, + OPT_RELAYS, }; pub async fn read_startup_options( plugin: &ConfiguredPlugin, state: &PluginState, ) -> Result<(), anyhow::Error> { - let relays_str = if plugin - .option(&OPT_RELAYS) - .unwrap() - .is_none_or(|v| v.is_empty()) - { - vec![ - "wss://nos.lol".to_owned(), - "wss://relay.primal.net".to_owned(), - "wss://relay.getalby.com/v1".to_owned(), - "wss://relay.nostr.net".to_owned(), - "wss://relay.snort.social".to_owned(), - ] + let relays_str = if let Some(relays) = plugin.option(&OPT_RELAYS).unwrap() { + if relays.is_empty() { + return Err(anyhow!( + "Empty `{}` option, must specify atleast one relay url!", + OPT_RELAYS.name() + )); + } + relays } else { - plugin.option(&OPT_RELAYS).unwrap().unwrap() + return Err(anyhow!( + "`{}` not set, must specify atleast one relay url!", + OPT_RELAYS.name() + )); }; let mut rpc = ClnRpc::new( Path::new(&plugin.configuration().lightning_dir).join(&plugin.configuration().rpc_file), ) .await?; - let help = rpc.call_typed(&HelpRequest { command: None }).await?; - + let version = rpc.call_typed(&GetinfoRequest {}).await?.version; let mut config = state.config.lock(); - - let mut available_commands = HashSet::new(); - for command in &help.help { - if let Some(method) = command.command.split_ascii_whitespace().next() { - available_commands.insert(method); - } - } - - log::debug!( - "Found {} commands available: {}", - available_commands.len(), - available_commands - .iter() - .copied() - .collect::>() - .join(" ") - ); - - config.has_xkeysend = available_commands.contains(XKEYSEND_COMMAND); - config.has_xpay = available_commands.contains(XPAY_COMMAND); - log::debug!( - "Using xpay:{} xkeysend:{}", - config.has_xpay, - config.has_xkeysend - ); + config.my_cln_version = version; for relay in relays_str { log::debug!("RELAY:{relay}"); - config.relays.push(RelayUrl::parse(&relay)?); + config.relays.push(nostr_sdk::RelayUrl::parse(&relay)?); } Ok(()) } @@ -80,10 +51,10 @@ pub fn parse_time_period(input: &str) -> Result { if let Ok(time_unit) = unit.parse() { match time_unit { TimeUnit::Second => Ok(value), - TimeUnit::Minute => Ok(value.saturating_mul(60)), - TimeUnit::Hour => Ok(value.saturating_mul(60 * 60)), - TimeUnit::Day => Ok(value.saturating_mul(60 * 60 * 24)), - TimeUnit::Week => Ok(value.saturating_mul(60 * 60 * 24 * 7)), + TimeUnit::Minute => Ok(value * 60), + TimeUnit::Hour => Ok(value * 60 * 60), + TimeUnit::Day => Ok(value * 60 * 60 * 24), + TimeUnit::Week => Ok(value * 60 * 60 * 24 * 7), } } else { Err(anyhow!(format!("Unsupported time unit: {unit}"))) diff --git a/src/rpc.rs b/src/rpc.rs index a2c2c38..272d483 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -6,25 +6,15 @@ use cln_rpc::model::requests::{ DeldatastoreRequest, ListdatastoreRequest, }; -use nostr::{ - key::{Keys, SecretKey}, - nips::nip47::NostrWalletConnectUri, - types::Timestamp, -}; +use nostr_sdk::{Keys, SecretKey, Timestamp, nips::nip47::NostrWalletConnectURI}; use serde_json::json; use crate::{ PLUGIN_NAME, - nwc::{run_nwc, send_nwc_info_event, stop_nwc}, + nwc::{run_nwc, send_nwc_info_event, start_nwc_budget_job, stop_nwc, stop_nwc_budget_job}, parse::parse_time_period, structs::{BudgetIntervalConfig, NwcStore, PluginState}, - util::{ - build_capabilities, - get_budget_msat, - is_read_only_nwc, - load_nwc_store, - update_nwc_store, - }, + util::{build_capabilities, is_read_only_nwc, load_nwc_store, update_nwc_store}, }; pub async fn nwc_create( @@ -39,7 +29,7 @@ pub async fn nwc_create( let wallet_keys = Keys::generate(); let client_keys = Keys::generate(); - let uri = NostrWalletConnectUri::new( + let uri = NostrWalletConnectURI::new( wallet_keys.public_key(), config.relays.clone(), client_keys.secret_key().clone(), @@ -68,7 +58,6 @@ pub async fn nwc_create( interval_secs: interval, reset_budget_msat: bgt_msat, last_reset: Timestamp::now().as_secs(), - spend_since_last_reset: 0, }; result.insert("interval_config".to_owned(), serde_json::to_value(&conf)?); Some(conf) @@ -83,7 +72,6 @@ pub async fn nwc_create( walletkey: wallet_keys.secret_key().to_secret_hex(), budget_msat, interval_config, - reserved_msat: 0, }; rpc.call_typed(&DatastoreRequest { @@ -95,9 +83,7 @@ pub async fn nwc_create( }) .await?; - if let Err(e) = run_nwc(plugin.clone(), label.clone(), nwc_store.clone()).await { - log::warn!("Failed running nwc: {e}"); - } + run_nwc(plugin.clone(), label.clone(), nwc_store.clone()).await?; Ok(serde_json::Value::Object(result)) } @@ -129,6 +115,8 @@ pub async fn nwc_budget( let (label, budget_msat, interval_secs) = parse_full_args(args)?; + stop_nwc_budget_job(&plugin, &label); + let mut nwc_store = load_nwc_store(&mut rpc, &label).await?; let is_old_nwc_read_only = is_read_only_nwc(&nwc_store); @@ -140,7 +128,6 @@ pub async fn nwc_budget( interval_secs: interval, reset_budget_msat: budget, last_reset: Timestamp::now().as_secs(), - spend_since_last_reset: 0, }; nwc_store.interval_config = Some(interval_config.clone()); } else { @@ -155,6 +142,10 @@ pub async fn nwc_budget( update_nwc_store(&mut rpc, &label, nwc_store.clone()).await?; + if nwc_store.interval_config.is_some() { + start_nwc_budget_job(&plugin, label.clone()); + } + if is_old_nwc_read_only != is_new_nwc_read_only { let wallet_keys = Keys::new(SecretKey::from_hex(&nwc_store.walletkey)?); let (method_capabilities, _) = build_capabilities(is_new_nwc_read_only, &plugin); @@ -164,7 +155,7 @@ pub async fn nwc_budget( clients .get(&label) .ok_or_else(|| anyhow!("No client found for label: {label}"))? - .client + .0 .clone(), method_capabilities, wallet_keys, @@ -186,8 +177,7 @@ pub async fn nwc_list( let mut nwcs = Vec::new(); if let Some(lbl) = label { - let mut nwc_store = load_nwc_store(&mut rpc, &lbl).await?; - nwc_store.budget_msat = get_budget_msat(&nwc_store); + let nwc_store = load_nwc_store(&mut rpc, &lbl).await?; let wallet_key = Keys::new(SecretKey::from_hex(&nwc_store.walletkey)?); let client_key = Keys::new(nwc_store.uri.secret.clone()); let mut nwc_json = json!(nwc_store); @@ -212,8 +202,7 @@ pub async fn nwc_list( for datastore in all_stored_nwcs { let label = datastore.key.last().unwrap().to_owned(); - let mut nwc_store = load_nwc_store(&mut rpc, &label).await?; - nwc_store.budget_msat = get_budget_msat(&nwc_store); + let nwc_store = load_nwc_store(&mut rpc, &label).await?; let wallet_key = Keys::new(SecretKey::from_hex(&nwc_store.walletkey)?); let client_key = Keys::new(nwc_store.uri.secret.clone()); let mut nwc_json = json!(nwc_store); diff --git a/src/structs.rs b/src/structs.rs index c66de4e..c96db5a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,28 +1,19 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr, sync::Arc}; use cln_rpc::ClnRpc; -use nostr::{ - key::{Keys, PublicKey}, - nips::nip47::NostrWalletConnectUri, - types::RelayUrl, -}; -use nostr_sdk::client::Client; +use nostr_sdk::{client, nips::nip47, nostr}; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use tonic::transport::Channel; - -use crate::hold::hold_client::HoldClient; +use tokio::sync::oneshot; pub const NOT_INV_ERR: &str = "Not an invoice or invalid invoice"; -pub const ID_STORE: &str = "eventids"; -pub const ID_MAX_AGE: u64 = 7_200; #[derive(Clone)] pub struct PluginState { pub config: Arc>, - pub handles: Arc>>, + pub handles: Arc>>, pub rpc_lock: Arc>, - pub hold_client: Arc>>>, + pub budget_jobs: Arc>>>, } impl PluginState { pub async fn new(path: PathBuf) -> Result { @@ -30,29 +21,21 @@ impl PluginState { config: Arc::new(Mutex::new(Config::default())), handles: Arc::new(tokio::sync::Mutex::new(HashMap::new())), rpc_lock: Arc::new(tokio::sync::Mutex::new(ClnRpc::new(path).await?)), - hold_client: Arc::new(Mutex::new(None)), + budget_jobs: Arc::new(Mutex::new(HashMap::new())), }) } } -pub struct WalletService { - pub client: Client, - pub client_pubkey: PublicKey, - pub wallet_secret: Keys, -} - #[derive(Clone, Debug)] pub struct Config { - pub relays: Vec, - pub has_xkeysend: bool, - pub has_xpay: bool, + pub relays: Vec, + pub my_cln_version: String, } impl Config { pub fn default() -> Config { Config { relays: Vec::new(), - has_xkeysend: false, - has_xpay: false, + my_cln_version: String::new(), } } } @@ -85,18 +68,14 @@ pub struct BudgetIntervalConfig { pub interval_secs: u64, pub reset_budget_msat: u64, pub last_reset: u64, - #[serde(default)] - pub spend_since_last_reset: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NwcStore { - pub uri: NostrWalletConnectUri, + pub uri: nip47::NostrWalletConnectURI, pub walletkey: String, #[serde(skip_serializing_if = "Option::is_none")] pub budget_msat: Option, #[serde(skip_serializing_if = "Option::is_none")] pub interval_config: Option, - #[serde(default)] - pub reserved_msat: u64, } diff --git a/src/tasks.rs b/src/tasks.rs index 7a53195..2437e4a 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,49 +1,59 @@ -use std::{path::Path, str::FromStr, time::Duration}; +use std::{path::Path, time::Duration}; +use anyhow::anyhow; use cln_plugin::Plugin; -use cln_rpc::{ - ClnRpc, - model::requests::{DeldatastoreRequest, ListdatastoreRequest}, -}; -use nostr::types::Timestamp; +use cln_rpc::ClnRpc; +use nostr_sdk::Timestamp; +use tokio::{sync::oneshot, time}; use crate::{ - PLUGIN_NAME, - structs::{ID_MAX_AGE, ID_STORE, PluginState}, + structs::PluginState, + util::{load_nwc_store, update_nwc_store}, }; -pub async fn cleanup_event_ids(plugin: Plugin) -> Result<(), anyhow::Error> { +pub async fn budget_task( + mut rx: oneshot::Receiver<()>, + plugin: Plugin, + label: String, +) -> Result<(), anyhow::Error> { let mut rpc = ClnRpc::new( Path::new(&plugin.configuration().lightning_dir).join(&plugin.configuration().rpc_file), ) .await?; - loop { - { - let ids = rpc - .call_typed(&ListdatastoreRequest { - key: Some(vec![format!("{}-{}", PLUGIN_NAME, ID_STORE)]), - }) - .await? - .datastore; - - let now = Timestamp::now(); - for id in ids { - if id.string.is_none() { - continue; - } - let timestamp = Timestamp::from_str(&id.string.unwrap())?; - if now.as_secs().saturating_sub(timestamp.as_secs()) > ID_MAX_AGE { - rpc.call_typed(&DeldatastoreRequest { - generation: None, - key: id.key.clone(), - }) - .await?; - log::debug!("Cleaned up event id: {}", id.key.last().unwrap()); - } + let mut nwc_store = load_nwc_store(&mut rpc, &label).await?; + let interval_config = nwc_store + .interval_config + .as_mut() + .ok_or_else(|| anyhow!("interval_config disappeared!"))?; + let now = Timestamp::now().as_secs(); + log::debug!( + "interval:{} now:{} prev:{}", + interval_config.interval_secs, + now, + interval_config.last_reset + ); + let next_reset = std::cmp::max( + interval_config + .interval_secs + .saturating_sub(now.saturating_sub(interval_config.last_reset)), + 1, + ); + tokio::select! { + _ = &mut rx => { + log::info!("Stopping budget task for {label}"); + break; + } + () = time::sleep(Duration::from_secs(next_reset)) => { + log::info!("Refreshing budget for {label}"); + *nwc_store.budget_msat + .as_mut() + .ok_or_else(||anyhow!("budget_msat missing"))? = interval_config.reset_budget_msat; + interval_config.last_reset = Timestamp::now().as_secs(); + update_nwc_store(&mut rpc, &label, nwc_store).await?; + log::info!("Done refreshing budget for {label}"); } } - - tokio::time::sleep(Duration::from_secs(120)).await; } + Ok(()) } diff --git a/src/util.rs b/src/util.rs index 2543e55..36cc484 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,76 +1,20 @@ -use std::{ - cmp::max, - path::{Path, PathBuf}, -}; - use anyhow::anyhow; use cln_plugin::Plugin; use cln_rpc::{ - ClnRpc, model::requests::{DatastoreMode, DatastoreRequest, ListdatastoreRequest}, + ClnRpc, }; -use nostr::{nips::nip47, types::Timestamp}; +use nostr_sdk::nips::nip47; use crate::{ + structs::{NwcStore, PluginState}, OPT_NOTIFICATIONS, PLUGIN_NAME, - WALLET_HOLD_METHODS, - WALLET_HOLD_NOTIFICATIONS, WALLET_NOTIFICATIONS, WALLET_PAY_METHODS, - WALLET_READ_OR_RECEIVE_METHODS, - structs::{ID_STORE, NwcStore, PluginState}, + WALLET_READ_METHODS, }; -/// CLN's default maximum fee for a payment is `max(5000msat, 1% of amount)`, -/// so reserving this much guarantees a settled payment never exceeds the budget. -pub const MIN_FEE_RESERVE_MSAT: u64 = 5_000; - -pub fn payment_fee_reserve_msat(amount_msat: u64) -> u64 { - max(MIN_FEE_RESERVE_MSAT, amount_msat.saturating_div(100)) -} - -pub fn get_budget_msat(nwc_store: &NwcStore) -> Option { - match nwc_store.budget_msat { - Some(b) => { - let available = if let Some(conf) = &nwc_store.interval_config { - let now = Timestamp::now().as_secs(); - let spend = if now.saturating_sub(conf.last_reset) >= conf.interval_secs { - 0 - } else { - conf.spend_since_last_reset - }; - conf.reset_budget_msat.saturating_sub(spend) - } else { - b - }; - Some(available.saturating_sub(nwc_store.reserved_msat)) - } - None => None, - } -} - -pub fn update_budget_msat(nwc_store: &mut NwcStore, amount_spent_msat: u64) { - if let Some(bdg) = nwc_store.budget_msat.as_mut() { - if let Some(conf) = nwc_store.interval_config.as_mut() { - let now = Timestamp::now().as_secs(); - if now.saturating_sub(conf.last_reset) >= conf.interval_secs { - conf.last_reset = now; - conf.spend_since_last_reset = amount_spent_msat; - } else { - conf.spend_since_last_reset = conf - .spend_since_last_reset - .saturating_add(amount_spent_msat); - } - *bdg = conf - .reset_budget_msat - .saturating_sub(conf.spend_since_last_reset); - } else { - *bdg = bdg.saturating_sub(amount_spent_msat); - } - } -} - pub fn budget_amount_check( request_amt_msat: Option, invoice_amt_msat: Option, @@ -142,72 +86,6 @@ pub async fn update_nwc_store( Ok(()) } -pub fn rpc_socket_path(plugin: &Plugin) -> PathBuf { - Path::new(&plugin.configuration().lightning_dir).join(&plugin.configuration().rpc_file) -} - -/// Reserve the payment amount plus the worst case fee so that no combination -/// of concurrent payments can exceed the budget. Must be called while holding -/// the global rpc lock. Returns the reserved amount (0 if there is no budget). -pub async fn reserve_budget( - rpc: &mut ClnRpc, - label: &str, - nwc_store: &NwcStore, - amount_msat: u64, -) -> Result { - if nwc_store.budget_msat.is_none() { - return Ok(0); - } - - let reservation = amount_msat.saturating_add(payment_fee_reserve_msat(amount_msat)); - let mut new_store = nwc_store.clone(); - new_store.reserved_msat = new_store.reserved_msat.saturating_add(reservation); - update_nwc_store(rpc, label, new_store).await?; - - Ok(reservation) -} - -/// Release the reservation of a payment that did not succeed. Must be called -/// while holding the global rpc lock. -pub async fn refund_budget( - rpc: &mut ClnRpc, - label: &str, - reservation: u64, -) -> Result<(), anyhow::Error> { - if reservation == 0 { - return Ok(()); - } - - let mut nwc_store = load_nwc_store(rpc, label).await?; - nwc_store.reserved_msat = nwc_store.reserved_msat.saturating_sub(reservation); - update_nwc_store(rpc, label, nwc_store).await?; - - Ok(()) -} - -/// Record the actual amount spent by a successful payment and release the -/// remainder of the reservation. Must be called while holding the global rpc -/// lock. Charges the real spend first so that an error leaves the budget -/// conservatively low rather than too high. -pub async fn settle_budget( - rpc: &mut ClnRpc, - label: &str, - reservation: u64, - amount_spent_msat: u64, -) -> Result<(), anyhow::Error> { - // A zero reservation only happens when the nwc has no budget at all. - if reservation == 0 { - return Ok(()); - } - - let mut nwc_store = load_nwc_store(rpc, label).await?; - update_budget_msat(&mut nwc_store, amount_spent_msat); - nwc_store.reserved_msat = nwc_store.reserved_msat.saturating_sub(reservation); - update_nwc_store(rpc, label, nwc_store).await?; - - Ok(()) -} - pub fn is_read_only_nwc(nwc_store: &NwcStore) -> bool { if let Some(budget_msat) = nwc_store.budget_msat { if budget_msat == 0 && nwc_store.interval_config.is_none() { @@ -217,42 +95,40 @@ pub fn is_read_only_nwc(nwc_store: &NwcStore) -> bool { false } -pub async fn save_event_id( - rpc: &mut ClnRpc, - id: String, - timestamp: Timestamp, -) -> Result<(), anyhow::Error> { - rpc.call_typed(&DatastoreRequest { - key: vec![format!("{}-{}", PLUGIN_NAME, ID_STORE), id.clone()], - generation: None, - hex: None, - mode: Some(DatastoreMode::MUST_CREATE), - string: Some(timestamp.to_string()), - }) - .await?; - log::debug!("stored event id:{id}"); - Ok(()) +pub fn at_or_above_version(my_version: &str, min_version: &str) -> Result { + let clean_start_my_version = my_version + .split_once('v') + .ok_or_else(|| anyhow!("Could not find v in version string"))? + .1; + let full_clean_my_version: String = clean_start_my_version + .chars() + .take_while(|x| x.is_ascii_digit() || *x == '.') + .collect(); + + let my_version_parts: Vec<&str> = full_clean_my_version.split('.').collect(); + let min_version_parts: Vec<&str> = min_version.split('.').collect(); + + if my_version_parts.len() <= 1 || my_version_parts.len() > 3 { + return Err(anyhow!("Version string parse error: {my_version}")); + } + for (my, min) in my_version_parts.iter().zip(min_version_parts.iter()) { + let my_num: u32 = my.parse()?; + let min_num: u32 = min.parse()?; + + if my_num != min_num { + return Ok(my_num > min_num); + } + } + + Ok(my_version_parts.len() >= min_version_parts.len()) } -pub fn build_capabilities(is_receive_only: bool, plugin: &Plugin) -> (String, String) { - let holdinvoice_support = plugin.state().hold_client.lock().is_some(); - - let mut methods = WALLET_READ_OR_RECEIVE_METHODS - .map(|m| m.to_string()) - .join(" "); - if !is_receive_only { +pub fn build_capabilities(is_read_only: bool, plugin: &Plugin) -> (String, String) { + let mut methods = WALLET_READ_METHODS.map(|m| m.to_string()).join(" "); + if !is_read_only { methods.push(' '); methods.push_str(WALLET_PAY_METHODS.map(|m| m.to_string()).join(" ").as_str()); } - if holdinvoice_support { - methods.push(' '); - methods.push_str( - WALLET_HOLD_METHODS - .map(|m| m.to_string()) - .join(" ") - .as_str(), - ); - } let mut notifications = String::new(); if plugin.option(&OPT_NOTIFICATIONS).unwrap() { @@ -262,44 +138,23 @@ pub fn build_capabilities(is_receive_only: bool, plugin: &Plugin) - .join(" ") .as_str(), ); - if holdinvoice_support { - notifications.push(' '); - notifications.push_str( - WALLET_HOLD_NOTIFICATIONS - .map(|m| m.to_string()) - .join(" ") - .as_str(), - ); - } } (methods, notifications) } -pub fn build_methods_vec( - is_receive_only: bool, - plugin: &Plugin, -) -> Vec { - let holdinvoice_support = plugin.state().hold_client.lock().is_some(); - let mut methods = WALLET_READ_OR_RECEIVE_METHODS.to_vec(); - if !is_receive_only { +pub fn build_methods_vec(is_read_only: bool, _plugin: &Plugin) -> Vec { + let mut methods = WALLET_READ_METHODS.to_vec(); + if !is_read_only { methods.extend_from_slice(&WALLET_PAY_METHODS); } - if holdinvoice_support { - methods.extend_from_slice(&WALLET_HOLD_METHODS); - } methods } pub fn build_notifications_vec(plugin: &Plugin) -> Vec { - let holdinvoice_support = plugin.state().hold_client.lock().is_some(); - let mut notifications = Vec::new(); if plugin.option(&OPT_NOTIFICATIONS).unwrap() { notifications.extend_from_slice(&WALLET_NOTIFICATIONS.map(|m| m.to_string())); - if holdinvoice_support { - notifications.extend_from_slice(&WALLET_HOLD_NOTIFICATIONS.map(|m| m.to_string())); - } } notifications } @@ -321,105 +176,3 @@ fn test_budget_check() { assert!(budget_amount_check(Some(0), Some(0), Some(1)).is_ok()); assert!(budget_amount_check(Some(0), Some(0), Some(0)).is_ok()); } - -#[test] -fn test_budget_interval_helpers() { - use crate::structs::BudgetIntervalConfig; - - let now = Timestamp::now().as_secs(); - let conf = BudgetIntervalConfig { - interval_secs: 10, - reset_budget_msat: 1000, - last_reset: now, - spend_since_last_reset: 0, - }; - let mut store = NwcStore { - uri: nostr::nips::nip47::NostrWalletConnectUri::new( - nostr::key::Keys::generate().public_key(), - vec![], - nostr::key::Keys::generate().secret_key().clone(), - None, - ), - walletkey: "test".to_owned(), - budget_msat: Some(1000), - interval_config: Some(conf), - reserved_msat: 0, - }; - - assert_eq!(get_budget_msat(&store), Some(1000)); - - update_budget_msat(&mut store, 300); - assert_eq!( - store - .interval_config - .as_ref() - .unwrap() - .spend_since_last_reset, - 300 - ); - assert_eq!(get_budget_msat(&store), Some(700)); - - update_budget_msat(&mut store, 500); - assert_eq!(get_budget_msat(&store), Some(200)); - - store.interval_config.as_mut().unwrap().last_reset = now.saturating_sub(20); - let old_last_reset = store.interval_config.as_ref().unwrap().last_reset; - assert_eq!(get_budget_msat(&store), Some(1000)); - - update_budget_msat(&mut store, 400); - assert!(store.interval_config.as_ref().unwrap().last_reset > old_last_reset); - assert_eq!( - store - .interval_config - .as_ref() - .unwrap() - .spend_since_last_reset, - 400 - ); - assert_eq!(get_budget_msat(&store), Some(600)); -} - -#[test] -fn test_payment_fee_reserve() { - // fee reserve is at least 5000msat - assert_eq!(payment_fee_reserve_msat(0), 5000); - assert_eq!(payment_fee_reserve_msat(1000), 5000); - assert_eq!(payment_fee_reserve_msat(499_999), 5000); - // and 1% of the amount above that - assert_eq!(payment_fee_reserve_msat(500_000), 5000); - assert_eq!(payment_fee_reserve_msat(1_000_000), 10_000); - assert_eq!(payment_fee_reserve_msat(123_456_789), 1_234_567); -} - -#[test] -fn test_budget_reservations() { - let store = NwcStore { - uri: nostr::nips::nip47::NostrWalletConnectUri::new( - nostr::key::Keys::generate().public_key(), - vec![], - nostr::key::Keys::generate().secret_key().clone(), - None, - ), - walletkey: "test".to_owned(), - budget_msat: Some(10_000), - interval_config: None, - reserved_msat: 0, - }; - - // reserving reduces the available budget - let mut stored = store.clone(); - stored.reserved_msat = 1_500; - assert_eq!(get_budget_msat(&stored), Some(8_500)); - - // a full spend plus reservation can never be under budget - let full_reservation = - stored.clone().budget_msat.unwrap() + payment_fee_reserve_msat(store.budget_msat.unwrap()); - stored.reserved_msat = full_reservation; - assert_eq!(get_budget_msat(&stored), Some(0)); - - // reservations do not affect a store without a budget - let mut no_budget = store; - no_budget.budget_msat = None; - no_budget.reserved_msat = 1_000; - assert_eq!(get_budget_msat(&no_budget), None); -} diff --git a/tests/config.toml b/tests/config.toml index 3afabc9..062124b 100644 --- a/tests/config.toml +++ b/tests/config.toml @@ -9,4 +9,4 @@ reject_future_seconds = 1800 [limits] limit_scrapers = false [network] -address = "127.0.0.1" +address = "0.0.0.0" diff --git a/tests/pyproject.toml b/tests/pyproject.toml index bcde559..874fadb 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -6,12 +6,12 @@ description = "python dependencies for running tests" [dependency-groups] dev = [ - "pytest>=8,<10", + "pytest>=7.4,<9", "pytest-asyncio>=0.23.8,<2", "pytest-xdist>=3.7,<4", "pytest-timeout>=2.4,<3", - "nostr-sdk>=0.45", - "pyln-testing>=25.9", - "pyln-client>=25.9", - "pyln-proto>=25.9", + "nostr-sdk>=0.44", + "pyln-testing>=25.5", + "pyln-client>=25.5", + "pyln-proto>=25.5", ] diff --git a/tests/setup.sh b/tests/setup.sh index 829a336..0fe9440 100755 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -81,17 +81,3 @@ if [ -d "$proto_path" ]; then exit 1 fi fi - -hold_url="https://github.com/BoltzExchange/hold/releases/download/v0.3.3/hold-linux-amd64.tar.gz" - -if ! curl -L "$hold_url" -o "$script_dir/hold-linux-amd64.tar.gz"; then - echo "Error downloading the file from $hold_url" >&2 - exit 1 -fi - -if ! tar -xzvf "$script_dir/hold-linux-amd64.tar.gz" -C "$script_dir"; then - echo "Error extracting the contents of hold-linux-amd64.tar.gz" >&2 - exit 1 -fi - -mv "$script_dir/build/hold-linux-amd64" "$script_dir/hold" diff --git a/tests/test_cln-nip47.py b/tests/test_cln-nip47.py old mode 100644 new mode 100755 index 36260fd..daf256c --- a/tests/test_cln-nip47.py +++ b/tests/test_cln-nip47.py @@ -1,49 +1,64 @@ -# ruff: noqa: DTZ005 - -import asyncio import hashlib import inspect import json import logging -import secrets import time -import uuid -from collections.abc import Awaitable, Callable +import asyncio from datetime import datetime, timedelta -from typing import Any, Union +from typing import Any, Awaitable, Callable, Union import pytest +from pyln.testing.fixtures import * # noqa: F403 +from pyln.testing.utils import RpcError, wait_for, TIMEOUT +from util import generate_random_label, get_plugin # noqa: F401 + from nostr_sdk import ( + Alphabet, Client, - Event, + RelayUrl, EventBuilder, Filter, + Event, Keys, KeysendTlvRecord, Kind, + HandleNotification, ListTransactionsRequest, LookupInvoiceRequest, MakeInvoiceRequest, - Method, NostrSdkError, - NostrWalletConnect, + NostrSigner, NostrWalletConnectUri, + Nwc, PayInvoiceRequest, PayKeysendRequest, - PublicKey, - RelayUrl, - ReqTarget, + SingleLetterTag, Tag, - TransactionType, + TagKind, + Method, + PublicKey, ) -from pyln.testing.fixtures import * -from pyln.testing.utils import TIMEOUT, RpcError, wait_for -from util import generate_random_label, get_hold, get_plugin # noqa: F401 LOGGER = logging.getLogger(__name__) -Action = Union[ # noqa: UP007 +class NotificationHandler(HandleNotification): + def __init__(self, events_list, stop_after): + self.events_list = events_list + self.stop_after = stop_after + self._done = asyncio.Event() + + async def handle(self, relay_url, subscription_id, event: Event): + LOGGER.info(f"Received new event from {relay_url}: {event.as_json()}") + self.events_list.append(event) + if len(self.events_list) >= self.stop_after: + self._done.set() + + async def handle_msg(self, relay_url, msg): + _var = None + + +Action = Union[ Callable[[], Awaitable[None]], Callable[[], None], Awaitable[None], @@ -60,31 +75,12 @@ async def fetch_event_responses( ) -> tuple[list[Event], Any]: events = [] response_filter = Filter().kind(Kind(event_kind)).pubkey(client_pubkey) - target = ReqTarget.auto([response_filter]) + await client.subscribe(response_filter) - subscription_id = uuid.uuid4().hex - LOGGER.info(f"Subscribing with id {subscription_id} to {response_filter}") - - await client.subscribe(target, subscription_id) - - async def collect_events(): - stream = client.notifications() - - while len(events) < stop_after: - notification = await stream.next() - - if notification.is_new_event(): - event = notification.event - relay_url = notification.relay_url - - LOGGER.info(f"Received new event from {relay_url}: {event.as_json()}") - - events.append(event) - - task = asyncio.create_task(collect_events()) - - await asyncio.sleep(1) + handler = NotificationHandler(events, stop_after) + task = asyncio.create_task(client.handle_notifications(handler)) + time.sleep(1) if inspect.iscoroutine(action): action_result = await action elif inspect.iscoroutinefunction(action): @@ -95,10 +91,10 @@ async def fetch_event_responses( raise TypeError("action must be a callable or an awaitable") try: - await asyncio.wait_for(task, timeout=timeout) + await asyncio.wait_for(handler._done.wait(), timeout=timeout) except asyncio.TimeoutError: print( - f"Timeout reached after {timeout} seconds, collected {len(events)} events", + f"Timeout reached after {timeout} seconds, collected {len(events)} events" ) finally: task.cancel() @@ -107,10 +103,9 @@ async def fetch_event_responses( except asyncio.CancelledError: pass - await client.unsubscribe_all() - + await client.unsubscribe_all() assert len(events) == stop_after - return events, action_result + return (events, action_result) async def fetch_info_event( @@ -118,27 +113,31 @@ async def fetch_info_event( uri: NostrWalletConnectUri, ) -> Event: response_filter = Filter().kind(Kind(13194)).author(uri.public_key()) - target = ReqTarget.auto([response_filter]) - events = await client.fetch_events(target, timeout=timedelta(seconds=TIMEOUT)) + events = await client.fetch_events( + response_filter, timeout=timedelta(seconds=TIMEOUT) + ) start_time = datetime.now() - while len(events) < 1 and (datetime.now() - start_time) < timedelta( + while events.len() < 1 and (datetime.now() - start_time) < timedelta( seconds=TIMEOUT ): - await asyncio.sleep(1) - events = await client.fetch_events(target, timeout=timedelta(seconds=1)) - assert len(events) == 1 + time.sleep(1) + events = await client.fetch_events( + response_filter, timeout=timedelta(seconds=1) + ) + assert events.len() == 1 - return events[0] + return events.first() @pytest.mark.asyncio async def test_get_balance(nostr_relay, node_factory, get_plugin): # noqa: F811 url = nostr_relay - l1, _l2 = node_factory.line_graph( + l1, l2 = node_factory.line_graph( 2, wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -150,35 +149,38 @@ async def test_get_balance(nostr_relay, node_factory, get_plugin): # noqa: F811 uri_str = l1.rpc.call("nip47-create", ["test1", 3000])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) balance = await nwc.get_balance() - assert balance.balance == 3000 + assert balance == 3000 uri_str = l1.rpc.call("nip47-create", ["test2"])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) balance = await nwc.get_balance() - assert balance.balance == node_balance + assert balance == node_balance uri_str = l1.rpc.call("nip47-create", ["test3", 0])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) balance = await nwc.get_balance() - assert balance.balance == 0 + assert balance == 0 with pytest.raises(RpcError, match="not an integer"): uri_str = l1.rpc.call("nip47-create", ["test3", -1])["uri"] @@ -189,6 +191,7 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 url = nostr_relay l1 = node_factory.get_node( options={ + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, }, @@ -198,23 +201,26 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 uri_str = l1.rpc.call("nip47-create", ["test1", 3000])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) get_info = await nwc.get_info() assert get_info.alias == node_get_info["alias"] assert get_info.block_height == node_get_info["blockheight"] assert get_info.color == node_get_info["color"] assert get_info.methods == [ - Method.MAKE_INVOICE(), - Method.LOOKUP_INVOICE(), - Method.LIST_TRANSACTIONS(), - Method.GET_BALANCE(), - Method.GET_INFO(), - Method.PAY_INVOICE(), - Method.PAY_KEYSEND(), + Method.MAKE_INVOICE, + Method.LOOKUP_INVOICE, + Method.LIST_TRANSACTIONS, + Method.GET_BALANCE, + Method.GET_INFO, + Method.PAY_INVOICE, + Method.MULTI_PAY_INVOICE, + Method.PAY_KEYSEND, + Method.MULTI_PAY_KEYSEND, ] assert get_info.network == "regtest" assert get_info.notifications == ["payment_received", "payment_sent"] @@ -230,7 +236,7 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 }, ) l1.daemon.wait_for_log("All NWC's loaded") - await asyncio.sleep(5) + time.sleep(5) await client.connect() info_event = await fetch_info_event(client, uri) get_info = await nwc.get_info() @@ -238,13 +244,15 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 assert get_info.block_height == node_get_info["blockheight"] assert get_info.color == node_get_info["color"] assert get_info.methods == [ - Method.MAKE_INVOICE(), - Method.LOOKUP_INVOICE(), - Method.LIST_TRANSACTIONS(), - Method.GET_BALANCE(), - Method.GET_INFO(), - Method.PAY_INVOICE(), - Method.PAY_KEYSEND(), + Method.MAKE_INVOICE, + Method.LOOKUP_INVOICE, + Method.LIST_TRANSACTIONS, + Method.GET_BALANCE, + Method.GET_INFO, + Method.PAY_INVOICE, + Method.MULTI_PAY_INVOICE, + Method.PAY_KEYSEND, + Method.MULTI_PAY_KEYSEND, ] assert get_info.network == "regtest" assert get_info.notifications == [] @@ -252,29 +260,30 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 assert ( info_event.content() - == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice pay_keysend" + == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice multi_pay_invoice pay_keysend multi_pay_keysend" ) - encryption_tag = next( - tag for tag in info_event.tags() if tag.kind() == "encryption" + assert ( + info_event.tags().find(TagKind.UNKNOWN("encryption")).content() + == "nip44_v2 nip04" ) - assert encryption_tag.content() == "nip44_v2 nip04" - assert not any(tag.kind() == "notifications" for tag in info_event.tags()) + assert info_event.tags().find(TagKind.UNKNOWN("notifications")) is None uri_str = l1.rpc.call("nip47-create", ["test2", 0])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) get_info = await nwc.get_info() assert get_info.methods == [ - Method.MAKE_INVOICE(), - Method.LOOKUP_INVOICE(), - Method.LIST_TRANSACTIONS(), - Method.GET_BALANCE(), - Method.GET_INFO(), + Method.MAKE_INVOICE, + Method.LOOKUP_INVOICE, + Method.LIST_TRANSACTIONS, + Method.GET_BALANCE, + Method.GET_INFO, ] info_event = await fetch_info_event(client, uri) @@ -282,11 +291,11 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 info_event.content() == "make_invoice lookup_invoice list_transactions get_balance get_info" ) - encryption_tag = next( - tag for tag in info_event.tags() if tag.kind() == "encryption" + assert ( + info_event.tags().find(TagKind.UNKNOWN("encryption")).content() + == "nip44_v2 nip04" ) - assert encryption_tag.content() == "nip44_v2 nip04" - assert not any(tag.kind() == "notifications" for tag in info_event.tags()) + assert info_event.tags().find(TagKind.UNKNOWN("notifications")) is None @pytest.mark.asyncio @@ -294,6 +303,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81 url = nostr_relay l1 = node_factory.get_node( options={ + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, }, @@ -302,11 +312,12 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81 uri_str = l1.rpc.call("nip47-create", ["test1", 3000])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) timestamp = int(time.time()) invoice = await nwc.make_invoice( MakeInvoiceRequest( @@ -331,7 +342,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81 MakeInvoiceRequest( amount=3001, description="test2", - description_hash=hashlib.sha256(b"test2").hexdigest(), + description_hash=hashlib.sha256("test2".encode()).hexdigest(), expiry=120, ) ) @@ -357,7 +368,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81 MakeInvoiceRequest( amount=3001, description=None, - description_hash=hashlib.sha256(b"test2").hexdigest(), + description_hash=hashlib.sha256("test2".encode()).hexdigest(), expiry=120, ) ) @@ -368,7 +379,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81 MakeInvoiceRequest( amount=3001, description="test1", - description_hash=hashlib.sha256(b"test2").hexdigest(), + description_hash=hashlib.sha256("test2".encode()).hexdigest(), expiry=120, ) ) @@ -382,6 +393,7 @@ async def test_pay_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -390,14 +402,15 @@ async def test_pay_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 {"log-level": "debug"}, ], ) - uri_str = l1.rpc.call("nip47-create", ["test1", 8000])["uri"] + uri_str = l1.rpc.call("nip47-create", ["test1", 3000])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) result = await nwc.pay_keysend( PayKeysendRequest( id="id123", amount=1000, pubkey=l3.info["id"], preimage=None, tlv_records=[] @@ -412,7 +425,7 @@ async def test_pay_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 await nwc.pay_keysend( PayKeysendRequest( id="id123", - amount=7500, + amount=2001, pubkey=l2.info["id"], preimage=None, tlv_records=[KeysendTlvRecord(tlv_type=1234, value="a5c7e3d9b")], @@ -424,7 +437,7 @@ async def test_pay_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 await nwc.pay_keysend( PayKeysendRequest( id="id123", - amount=7500, + amount=2001, pubkey=l2.info["id"], preimage="or3ijro3ijroi", tlv_records=[KeysendTlvRecord(tlv_type=1234, value="a5c7e3d9b")], @@ -432,6 +445,99 @@ async def test_pay_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 ) +@pytest.mark.asyncio +async def test_multi_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 + url = nostr_relay + l1, l2, l3 = node_factory.line_graph( + 3, + wait_for_announce=True, + opts=[ + { + "log-level": "debug", + "plugin": get_plugin, + "nip47-relays": url, + "broken_log": r"Relay receiver exited with error|Connection failed", + }, + {"log-level": "debug"}, + {"log-level": "debug"}, + ], + ) + uri_res = l1.rpc.call("nip47-create", ["test1", 3010]) + uri_str = uri_res["uri"] + client_pubkey = PublicKey.parse(uri_res["clientkey_public"]) + LOGGER.info(uri_str) + uri = NostrWalletConnectUri.parse(uri_str) + content = { + "method": "multi_pay_keysend", + "params": { + "keysends": [ + {"id": "4da52c32a1", "pubkey": l2.info["id"], "amount": 1000}, + {"id": "3da52c32a1", "pubkey": l3.info["id"], "amount": 2000}, + ], + }, + } + content = json.dumps(content) + signer = NostrSigner.keys(Keys(uri.secret())) + encrypted_content = await signer.nip04_encrypt(uri.public_key(), content) + event = ( + await EventBuilder(Kind(23194), encrypted_content) + .tags([Tag.public_key(uri.public_key())]) + .sign(signer) + ) + client = Client(signer) + await client.add_relay(RelayUrl.parse(url)) + await client.connect() + (responses1, _res) = await fetch_event_responses( + client, client_pubkey, 23195, client.send_event(event), 2 + ) + + content = { + "method": "multi_pay_keysend", + "params": { + "keysends": [ + {"id": "5da52c32a1", "pubkey": l2.info["id"], "amount": 5}, + {"id": "2da52c32a1", "pubkey": l3.info["id"], "amount": 5}, + ], + }, + } + content = json.dumps(content) + encrypted_content = await signer.nip04_encrypt(uri.public_key(), content) + event = ( + await EventBuilder(Kind(23194), encrypted_content) + .tags([Tag.public_key(uri.public_key())]) + .sign(signer) + ) + (responses2, _res) = await fetch_event_responses( + client, client_pubkey, 23195, client.send_event(event), 2 + ) + + reponses = responses1 + responses2 + + error_events = [] + success_events = [] + for event in reponses: + LOGGER.info(event) + assert event.tags().find( + TagKind.SINGLE_LETTER(SingleLetterTag.lowercase(Alphabet.D)) + ) + content = await signer.nip04_decrypt(uri.public_key(), event.content()) + content = json.loads(content) + if "result" in content and content["result"] is not None: + success_events.append(content) + if "error" in content and content["error"] is not None: + error_events.append(content) + + assert len(success_events) == 3 + assert len(error_events) == 1 + for content in success_events: + assert content["result_type"] == "multi_pay_keysend" + assert content["result"]["preimage"] is not None + for content in error_events: + assert content["result_type"] == "multi_pay_keysend" + assert content["error"]["message"] == "Payment exceeds budget!" + assert content["error"]["code"] == "QUOTA_EXCEEDED" + + @pytest.mark.asyncio async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 url = nostr_relay @@ -440,6 +546,7 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -472,11 +579,12 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F uri_str = l1.rpc.call("nip47-create", ["test1", 3000])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) invoice = await nwc.make_invoice( MakeInvoiceRequest( amount=3000, description="test1", description_hash=None, expiry=None @@ -548,7 +656,7 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F MakeInvoiceRequest( amount=3001, description="test2", - description_hash=hashlib.sha256(b"test2").hexdigest(), + description_hash=hashlib.sha256("test2".encode()).hexdigest(), expiry=1000, ) ) @@ -570,7 +678,9 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F assert invoice_lookup.created_at.as_secs() == pytest.approx( invoice_decode["created_at"], abs=3 ) - assert invoice_lookup.description_hash == hashlib.sha256(b"test2").hexdigest() + assert ( + invoice_lookup.description_hash == hashlib.sha256("test2".encode()).hexdigest() + ) assert invoice_lookup.expires_at.as_secs() == pytest.approx( listpays_rpc["expires_at"], abs=3 ) @@ -597,7 +707,9 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F assert invoice_lookup.created_at.as_secs() == pytest.approx( invoice_decode["created_at"], abs=3 ) - assert invoice_lookup.description_hash == hashlib.sha256(b"test2").hexdigest() + assert ( + invoice_lookup.description_hash == hashlib.sha256("test2".encode()).hexdigest() + ) assert invoice_lookup.expires_at.as_secs() == pytest.approx( listpays_rpc["expires_at"], abs=3 ) @@ -666,6 +778,7 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -674,9 +787,9 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa ], ) l1.rpc.call( - "xpay", + "pay", { - "invstring": l2.rpc.call( + "bolt11": l2.rpc.call( "invoice", { "amount_msat": 500000000, @@ -691,17 +804,18 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa l2.rpc.call("listpeerchannels", [l1.info["id"]])["channels"][0][ "spendable_msat" ] - > 400000000 + > 30001 ) ) uri_str = l1.rpc.call("nip47-create", ["test1"])["uri"] LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) for i in range(10): invoice = l2.rpc.call( "invoice", @@ -721,7 +835,7 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa amount=3000, description="test2", description_hash=None, expiry=None ) ) - result = l2.rpc.call("xpay", [invoice.invoice]) + result = l2.rpc.call("pay", [invoice.invoice]) invoice = await nwc.make_invoice( MakeInvoiceRequest( @@ -742,23 +856,19 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa ) assert len(result) == 22 for tx in result: - assert tx.description is not None - assert tx.invoice is not None - assert tx.amount is not None - assert tx.created_at is not None - assert tx.description_hash is None - assert tx.preimage is not None - assert tx.settled_at is not None - assert tx.metadata is None - assert tx.transaction_type is not None - assert tx.state is not None - assert tx.payment_hash is not None - assert tx.fees_paid is not None - - if tx.transaction_type == TransactionType.INCOMING: - assert tx.expires_at is not None - else: - assert tx.expires_at is None + tx.description is not None + tx.invoice is not None + tx.amount is not None + tx.created_at is not None + tx.description_hash is None + tx.expires_at is None + tx.preimage is not None + tx.settled_at is not None + tx.metadata is None + tx.transaction_type is not None + tx.state is not None + tx.payment_hash is not None + tx.fees_paid is not None @pytest.mark.asyncio @@ -769,12 +879,13 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8 wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", }, {"log-level": "debug"}, - {"plugin": get_plugin, "nip47-relays": url}, + {"log-level": "debug", "plugin": get_plugin, "nip47-relays": url}, ], ) uri_res = l1.rpc.call("nip47-create", ["test1"]) @@ -783,12 +894,12 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8 LOGGER.info(uri_str) uri = NostrWalletConnectUri.parse(uri_str) - keys = Keys(uri.secret()) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) invoice = l3.rpc.call( "invoice", @@ -850,10 +961,11 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8 responses = responses1 + responses2 LOGGER.info(f"response1: {responses1} response2: {responses2}") assert len(responses) == 2 + signer = NostrSigner.keys(Keys(uri.secret())) received_events = [] sent_events = [] for event in responses: - content = keys.nip04_decrypt(uri.public_key(), event.content()) + content = await signer.nip04_decrypt(uri.public_key(), event.content()) content = json.loads(content) LOGGER.info(content) if content["notification_type"] == "payment_received": @@ -909,7 +1021,7 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8 }, ) l1.daemon.wait_for_log("All NWC's loaded") - await asyncio.sleep(3) + time.sleep(3) await client.connect() await fetch_info_event(client, uri) @@ -922,7 +1034,7 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8 }, ) with pytest.raises(AssertionError, match="0 == 1"): - (_responses3, _pay3) = await fetch_event_responses( + (responses3, pay3) = await fetch_event_responses( client, client_pubkey, 23196, @@ -942,6 +1054,7 @@ async def test_pay_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -949,9 +1062,10 @@ async def test_pay_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 {"log-level": "debug"}, ], ) - uri_str = l1.rpc.call("nip47-create", ["test1", 9000])["uri"] + uri_str = l1.rpc.call("nip47-create", ["test1", 3001])["uri"] uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) @@ -960,7 +1074,7 @@ async def test_pay_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 "invoice", {"label": generate_random_label(), "description": "test1", "amount_msat": 3000}, ) - nwc = NostrWalletConnect(NostrWalletConnectUri.parse(uri_str)) + nwc = Nwc(NostrWalletConnectUri.parse(uri_str)) result = await nwc.pay_invoice( PayInvoiceRequest(id=None, amount=None, invoice=invoice["bolt11"]) ) @@ -977,27 +1091,97 @@ async def test_pay_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 ) invoice = l2.rpc.call( "invoice", - {"label": generate_random_label(), "description": "test3", "amount_msat": 6500}, + {"label": generate_random_label(), "description": "test3", "amount_msat": 2}, ) with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): await nwc.pay_invoice( PayInvoiceRequest(id=None, amount=None, invoice=invoice["bolt11"]) ) - invoice = l2.rpc.call( + +@pytest.mark.asyncio +async def test_multi_pay(nostr_relay, node_factory, get_plugin): # noqa: F811 + url = nostr_relay + l1, l2 = node_factory.line_graph( + 2, + wait_for_announce=True, + opts=[ + { + "log-level": "debug", + "plugin": get_plugin, + "nip47-relays": url, + "broken_log": r"Relay receiver exited with error|Connection failed", + }, + {"log-level": "debug"}, + ], + ) + uri_res = l1.rpc.call("nip47-create", ["test1", 30000]) + uri_str = uri_res["uri"] + client_pubkey = PublicKey.parse(uri_res["clientkey_public"]) + LOGGER.info(uri_str) + uri = NostrWalletConnectUri.parse(uri_str) + invoice1 = l2.rpc.call( + "invoice", + {"label": generate_random_label(), "description": "test1", "amount_msat": 3000}, + ) + invoice2 = l2.rpc.call( + "invoice", + {"label": generate_random_label(), "description": "test2", "amount_msat": 4000}, + ) + invoice3 = l2.rpc.call( "invoice", { "label": generate_random_label(), - "description": "test1", - "amount_msat": "any", + "description": "test3", + "amount_msat": 23001, }, ) - result = await nwc.pay_invoice( - PayInvoiceRequest(id=None, amount=2, invoice=invoice["bolt11"]) + content = { + "method": "multi_pay_invoice", + "params": { + "invoices": [ + {"id": "4da52c32a1", "invoice": invoice1["bolt11"]}, + {"id": "3da52c32a1", "invoice": invoice2["bolt11"]}, + {"id": "af3g2k2o11", "invoice": invoice3["bolt11"]}, + ], + }, + } + content = json.dumps(content) + signer = NostrSigner.keys(Keys(uri.secret())) + encrypted_content = await signer.nip44_encrypt(uri.public_key(), content) + request_event = ( + await EventBuilder(Kind(23194), encrypted_content) + .tags([Tag.public_key(uri.public_key())]) + .sign(signer) ) - pay = l1.rpc.call("listpays", {"payment_hash": invoice["payment_hash"]})["pays"][0] - assert result.preimage == pay["preimage"] - assert pay["amount_msat"] == 2 + client = Client(signer) + await client.add_relay(RelayUrl.parse(url)) + await client.connect() + + (responses, _res) = await fetch_event_responses( + client, client_pubkey, 23195, client.send_event(request_event), 3 + ) + + success_pays = [] + error_pays = [] + for response in responses: + d_tag = response.tags().find( + TagKind.SINGLE_LETTER(SingleLetterTag.lowercase(Alphabet.D)) + ) + content = await signer.nip44_decrypt(uri.public_key(), response.content()) + content = json.loads(content) + assert content["result_type"] == "multi_pay_invoice" + if "result" in content and content["result"] is not None: + assert d_tag is not None + assert content["result"]["preimage"] is not None + success_pays.append(content) + if "error" in content and content["error"] is not None: + assert d_tag.content() == "af3g2k2o11" + assert content["error"]["code"] == "QUOTA_EXCEEDED" + assert content["error"]["message"] == "Payment exceeds budget!" + error_pays.append(content) + assert len(success_pays) == 2 + assert len(error_pays) == 1 @pytest.mark.asyncio @@ -1008,6 +1192,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -1015,7 +1200,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 {"log-level": "debug"}, ], ) - uri_str = l1.rpc.call("nip47-create", ["test1", 8000])["uri"] + uri_str = l1.rpc.call("nip47-create", ["test1", 3000])["uri"] LOGGER.info(uri_str) invoice = l2.rpc.call( "invoice", @@ -1030,13 +1215,14 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 }, ) l1.daemon.wait_for_log("All NWC's loaded") - await asyncio.sleep(3) + time.sleep(3) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) result = await nwc.pay_invoice( PayInvoiceRequest(id=None, amount=None, invoice=invoice["bolt11"]) ) @@ -1044,7 +1230,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 invoice = l2.rpc.call( "invoice", - {"label": generate_random_label(), "description": "test1", "amount_msat": 5500}, + {"label": generate_random_label(), "description": "test1", "amount_msat": 1}, ) with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): await nwc.pay_invoice( @@ -1059,7 +1245,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 }, ) l1.daemon.wait_for_log("All NWC's loaded") - await asyncio.sleep(3) + time.sleep(3) await client.connect() await fetch_info_event(client, uri) with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): @@ -1070,13 +1256,14 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 revoke = l1.rpc.call("nip47-revoke", ["test1"]) assert revoke["revoked"] == "test1" - uri_str = l1.rpc.call("nip47-create", ["test1", 8000, "10sec"])["uri"] + uri_str = l1.rpc.call("nip47-create", ["test1", 3000, "10sec"])["uri"] uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) invoice = l2.rpc.call( "invoice", @@ -1084,11 +1271,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 ) invoice_exceeded = l2.rpc.call( "invoice", - {"label": generate_random_label(), "description": "test1", "amount_msat": 5500}, - ) - invoice_fee_exceeded = l2.rpc.call( - "invoice", - {"label": generate_random_label(), "description": "test1", "amount_msat": 1}, + {"label": generate_random_label(), "description": "test1", "amount_msat": 3000}, ) result = await nwc.pay_invoice( PayInvoiceRequest(id=None, amount=None, invoice=invoice["bolt11"]) @@ -1096,26 +1279,17 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 assert result.preimage is not None list = l1.rpc.call("nip47-list", ["test1"])[0] - assert list["test1"]["budget_msat"] == 5000 + assert list["test1"]["budget_msat"] == 0 with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): await nwc.pay_invoice( PayInvoiceRequest(id=None, amount=None, invoice=invoice_exceeded["bolt11"]) ) - with pytest.raises( - NostrSdkError.Generic, - match="Payment and estimated fees exceed the available budget", - ): - await nwc.pay_invoice( - PayInvoiceRequest( - id=None, amount=None, invoice=invoice_fee_exceeded["bolt11"] - ) - ) - await asyncio.sleep(11) + time.sleep(11) list = l1.rpc.call("nip47-list", ["test1"])[0] - assert list["test1"]["budget_msat"] == 8000 + assert list["test1"]["budget_msat"] == 3000 invoice = l2.rpc.call( "invoice", @@ -1127,7 +1301,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 assert result.preimage is not None list = l1.rpc.call("nip47-list", ["test1"])[0] - assert list["test1"]["budget_msat"] == 5000 + assert list["test1"]["budget_msat"] == 0 with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): await nwc.pay_invoice( @@ -1143,7 +1317,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 }, ) l1.daemon.wait_for_log("All NWC's loaded") - await asyncio.sleep(3) + time.sleep(3) await client.connect() await fetch_info_event(client, uri) @@ -1152,10 +1326,10 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 PayInvoiceRequest(id=None, amount=None, invoice=invoice_exceeded["bolt11"]) ) - await asyncio.sleep(11) + time.sleep(11) list = l1.rpc.call("nip47-list", ["test1"])[0] - assert list["test1"]["budget_msat"] == 8000 + assert list["test1"]["budget_msat"] == 3000 @pytest.mark.asyncio @@ -1166,6 +1340,7 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F wait_for_announce=True, opts=[ { + "log-level": "debug", "plugin": get_plugin, "nip47-relays": url, "broken_log": r"Relay receiver exited with error|Connection failed", @@ -1180,13 +1355,14 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F {"label": generate_random_label(), "description": "test1", "amount_msat": 5000}, ) uri = NostrWalletConnectUri.parse(uri_str) - client = Client() + signer = NostrSigner.keys(Keys(uri.secret())) + client = Client(signer) await client.add_relay(RelayUrl.parse(url)) await client.connect() await fetch_info_event(client, uri) - nwc = NostrWalletConnect(uri) + nwc = Nwc(uri) balance = await nwc.get_balance() - assert balance.balance == 3000 + assert balance == 3000 with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): await nwc.pay_invoice( @@ -1195,16 +1371,16 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F l1.rpc.call("nip47-budget", ["test1", 4000]) balance = await nwc.get_balance() - assert balance.balance == 4000 + assert balance == 4000 with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"): await nwc.pay_invoice( PayInvoiceRequest(id=None, amount=None, invoice=invoice["bolt11"]) ) - l1.rpc.call("nip47-budget", ["test1", 10000, "15s"]) + l1.rpc.call("nip47-budget", ["test1", 5000, "15s"]) balance = await nwc.get_balance() - assert balance.balance == 10000 + assert balance == 5000 with pytest.raises( RpcError, match="`budget_msat` must be greater than 0 if you use `interval`" @@ -1217,50 +1393,52 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F assert pay.preimage is not None balance = await nwc.get_balance() - assert balance.balance == 5000 + assert balance == 0 get_info = await nwc.get_info() assert get_info.methods == [ - Method.MAKE_INVOICE(), - Method.LOOKUP_INVOICE(), - Method.LIST_TRANSACTIONS(), - Method.GET_BALANCE(), - Method.GET_INFO(), - Method.PAY_INVOICE(), - Method.PAY_KEYSEND(), + Method.MAKE_INVOICE, + Method.LOOKUP_INVOICE, + Method.LIST_TRANSACTIONS, + Method.GET_BALANCE, + Method.GET_INFO, + Method.PAY_INVOICE, + Method.MULTI_PAY_INVOICE, + Method.PAY_KEYSEND, + Method.MULTI_PAY_KEYSEND, ] info_event = await fetch_info_event(client, uri) assert ( info_event.content() - == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice pay_keysend notifications" + == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice multi_pay_invoice pay_keysend multi_pay_keysend notifications" ) - encryption_tag = next( - tag for tag in info_event.tags() if tag.kind() == "encryption" + assert ( + info_event.tags().find(TagKind.UNKNOWN("encryption")).content() + == "nip44_v2 nip04" ) - assert encryption_tag.content() == "nip44_v2 nip04" - notification_tag = next( - tag for tag in info_event.tags() if tag.kind() == "notifications" + assert ( + info_event.tags().find(TagKind.UNKNOWN("notifications")).content() + == "payment_received payment_sent" ) - assert notification_tag.content() == "payment_received payment_sent" - await asyncio.sleep(18) + time.sleep(18) balance = await nwc.get_balance() - assert balance.balance == 10000 + assert balance == 5000 l1.rpc.call("nip47-budget", ["test1", 0]) balance = await nwc.get_balance() - assert balance.balance == 0 + assert balance == 0 get_info = await nwc.get_info() assert get_info.methods == [ - Method.MAKE_INVOICE(), - Method.LOOKUP_INVOICE(), - Method.LIST_TRANSACTIONS(), - Method.GET_BALANCE(), - Method.GET_INFO(), + Method.MAKE_INVOICE, + Method.LOOKUP_INVOICE, + Method.LIST_TRANSACTIONS, + Method.GET_BALANCE, + Method.GET_INFO, ] info_event = await fetch_info_event(client, uri) @@ -1268,646 +1446,11 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F info_event.content() == "make_invoice lookup_invoice list_transactions get_balance get_info notifications" ) - encryption_tag = next( - tag for tag in info_event.tags() if tag.kind() == "encryption" - ) - assert encryption_tag.content() == "nip44_v2 nip04" - notification_tag = next( - tag for tag in info_event.tags() if tag.kind() == "notifications" - ) - assert notification_tag.content() == "payment_received payment_sent" - - -@pytest.mark.asyncio -async def test_hold_invoice( - node_factory, - executor, - get_plugin, # noqa: F811 - get_hold, # noqa: F811 - nostr_relay, -): - url = nostr_relay - l1, l2 = node_factory.line_graph( - 2, - wait_for_announce=True, - opts=[ - { - "log-level": "debug", - "may_reconnect": True, - }, - { - "plugin": get_plugin, - "important-plugin": get_hold, - "hold-grpc-port": node_factory.get_unused_port(), - "nip47-relays": url, - "may_reconnect": True, - "broken_log": r"Relay receiver exited with error|Connection failed", - }, - ], - ) - uri_res = l2.rpc.call("nip47-create", ["test1", 3010]) - uri_str = uri_res["uri"] - client_pubkey = PublicKey.parse(uri_res["clientkey_public"]) - LOGGER.info(uri_str) - uri = NostrWalletConnectUri.parse(uri_str) - - nwc = NostrWalletConnect(uri) - - preimage = secrets.token_hex(32) - payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() - LOGGER.info(f"preimage: {preimage}") - LOGGER.info(f"payment_hash: {payment_hash}") - content = { - "method": "make_hold_invoice", - "params": { - "amount": 5000, - "payment_hash": payment_hash, - }, - } - content = json.dumps(content) - keys = Keys(uri.secret()) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - client = Client() - relay_url = RelayUrl.parse(url) - await client.add_relay(relay_url) - await client.connect() - - await fetch_info_event(client, uri) - - (responses1, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(event), 1 - ) - error_events = [] - success_events = [] - for event in responses1: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if "result" in content and content["result"] is not None: - success_events.append(content) - if "error" in content and content["error"] is not None: - error_events.append(content) - - assert len(success_events) == 1 - assert len(error_events) == 0 - - assert success_events[0]["result_type"] == "make_hold_invoice" - assert success_events[0]["result"]["payment_hash"] == payment_hash - assert success_events[0]["result"]["type"] == "incoming" - assert success_events[0]["result"]["invoice"] is not None - invoice1 = success_events[0]["result"]["invoice"] - assert "description" not in success_events[0]["result"] - assert "description_hash" not in success_events[0]["result"] - assert success_events[0]["result"]["amount"] == 5000 - invoice1_created_at = pytest.approx(int(time.time()), abs=1) - assert success_events[0]["result"]["created_at"] == invoice1_created_at - invoice1_expires_at = pytest.approx(int(time.time()) + 3600, abs=1) - assert success_events[0]["result"]["expires_at"] == invoice1_expires_at - assert "metadata" not in success_events[0]["result"] - - lookup_hold = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=None, - ) - ) - assert lookup_hold.invoice == success_events[0]["result"]["invoice"] - assert lookup_hold.amount == 5000 - assert lookup_hold.description is None - assert lookup_hold.created_at.as_secs() == success_events[0]["result"]["created_at"] - assert lookup_hold.description_hash is None - assert lookup_hold.expires_at.as_secs() == success_events[0]["result"]["expires_at"] - assert lookup_hold.fees_paid == 0 - assert lookup_hold.metadata is None - assert lookup_hold.preimage is None - assert lookup_hold.payment_hash == payment_hash - assert lookup_hold.transaction_type.name == "INCOMING" - assert lookup_hold.state.name == "PENDING" - assert lookup_hold.settled_at is None - - getinfo = l2.rpc.getinfo() - - (responses2, _res) = await fetch_event_responses( - client, - client_pubkey, - 23196, - lambda: executor.submit( - l1.rpc.call, "xpay", [success_events[0]["result"]["invoice"]] - ), - 1, - ) - hold_events = [] - for event in responses2: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if content["notification_type"] == "hold_invoice_accepted": - hold_events.append(content) - assert content["notification"]["payment_hash"] == payment_hash - assert len(hold_events) == 1 assert ( - hold_events[0]["notification"]["settle_deadline"] > getinfo["blockheight"] + 1 - ) - - lookup_hold = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=None, - invoice=invoice1, - ) - ) - assert lookup_hold.invoice == invoice1 - assert lookup_hold.amount == 5000 - assert lookup_hold.description is None - assert lookup_hold.created_at.as_secs() == invoice1_created_at - assert lookup_hold.description_hash is None - assert lookup_hold.expires_at.as_secs() == invoice1_expires_at - assert lookup_hold.fees_paid == 0 - assert lookup_hold.metadata is None - assert lookup_hold.preimage is None - assert lookup_hold.payment_hash == payment_hash - assert lookup_hold.transaction_type.name == "INCOMING" - assert lookup_hold.state.name == "ACCEPTED" - assert lookup_hold.settled_at is None - - content = { - "method": "settle_hold_invoice", - "params": { - "preimage": preimage, - }, - } - content = json.dumps(content) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - - (responses3, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(event), 1 - ) - error_events = [] - success_events = [] - for event in responses3: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if ( - "result" in content - and content["result"] is not None - and content["result_type"] == "settle_hold_invoice" - ): - success_events.append(content) - if "error" in content and content["error"] is not None: - error_events.append(content) - - assert len(success_events) == 1 - assert len(error_events) == 0 - for content in success_events: - assert content["result_type"] == "settle_hold_invoice" - lookup_hold = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=None, - invoice=invoice1, - ) - ) - assert lookup_hold.invoice == invoice1 - assert lookup_hold.amount == 5000 - assert lookup_hold.description is None - assert lookup_hold.created_at.as_secs() == invoice1_created_at - assert lookup_hold.description_hash is None - assert lookup_hold.expires_at.as_secs() == invoice1_expires_at - assert lookup_hold.fees_paid == 0 - assert lookup_hold.metadata is None - assert lookup_hold.preimage == preimage - assert lookup_hold.payment_hash == payment_hash - assert lookup_hold.transaction_type.name == "INCOMING" - assert lookup_hold.state.name == "SETTLED" - assert lookup_hold.settled_at.as_secs() == pytest.approx( - int(time.time()), abs=1 - ) - - wait_for( - lambda: ( - l1.rpc.call("listpays", {"payment_hash": payment_hash})["pays"][0]["status"] - == "complete" - ) - ) - - preimage = secrets.token_hex(32) - payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() - content = { - "method": "make_hold_invoice", - "params": { - "amount": 5000, - "payment_hash": payment_hash, - "description": "cancel_hold", - "expiry": 1000, - "min_cltv_expiry_delta": 200, - }, - } - content = json.dumps(content) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - - (responses4, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(event), 1 - ) - error_events = [] - success_events = [] - for event in responses4: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if ( - "result" in content - and content["result"] is not None - and content["result_type"] == "make_hold_invoice" - and content["result"]["payment_hash"] == payment_hash - ): - success_events.append(content) - if "error" in content and content["error"] is not None: - error_events.append(content) - - assert len(success_events) == 1 - assert len(error_events) == 0 - for content in success_events: - assert content["result_type"] == "make_hold_invoice" - assert content["result"]["payment_hash"] == payment_hash - invoice2 = content["result"]["invoice"] - invoice2_created_at = pytest.approx(int(time.time()), abs=1) - invoice2_expires_at = pytest.approx(int(time.time()) + 1000, abs=1) - - (responses5, _res) = await fetch_event_responses( - client, - client_pubkey, - 23196, - lambda: executor.submit( - l1.rpc.call, "xpay", [success_events[0]["result"]["invoice"]] - ), - 1, - ) - hold_events = [] - for event in responses5: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if ( - content["notification_type"] == "hold_invoice_accepted" - and content["notification"]["payment_hash"] == payment_hash - ): - hold_events.append(content) - assert len(hold_events) == 1 - - content = { - "method": "cancel_hold_invoice", - "params": { - "payment_hash": payment_hash, - }, - } - content = json.dumps(content) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - - (responses6, _res) = await fetch_event_responses( - client, - client_pubkey, - 23195, - client.send_event(event), - 1, - ) - error_events = [] - success_events = [] - for event in responses6: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if ( - "result" in content - and content["result"] is not None - and content["result_type"] == "cancel_hold_invoice" - ): - success_events.append(content) - if "error" in content and content["error"] is not None: - error_events.append(content) - - assert len(success_events) == 1 - assert len(error_events) == 0 - for content in success_events: - assert content["result_type"] == "cancel_hold_invoice" - lookup_hold = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=None, - invoice=invoice2, - ) - ) - assert lookup_hold.invoice == invoice2 - assert lookup_hold.amount == 5000 - assert lookup_hold.description == "cancel_hold" - assert lookup_hold.created_at.as_secs() == invoice2_created_at - assert lookup_hold.description_hash is None - assert lookup_hold.expires_at.as_secs() == invoice2_expires_at - assert lookup_hold.fees_paid == 0 - assert lookup_hold.metadata is None - assert lookup_hold.preimage is None - assert lookup_hold.payment_hash == payment_hash - assert lookup_hold.transaction_type.name == "INCOMING" - assert lookup_hold.state.name == "EXPIRED" - assert lookup_hold.settled_at is None - - invoice2_decoded = l1.rpc.call("decode", [invoice2]) - assert invoice2_decoded["min_final_cltv_expiry"] == 200 - - wait_for( - lambda: ( - l1.rpc.call("listpays", {"payment_hash": payment_hash})["pays"][0]["status"] - == "failed" - ) - ) - - nwc = NostrWalletConnect(uri) - - invoice_lookup1 = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=None, - ) - ) - invoice_lookup2 = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=None, - invoice=invoice2, - ) - ) - invoice_lookup3 = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=invoice2, - ) - ) - assert invoice_lookup1 == invoice_lookup2 - assert invoice_lookup1 == invoice_lookup3 - - invoice_lookup4 = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=None, - invoice=invoice1, - ) - ) - - result = await nwc.list_transactions( - ListTransactionsRequest( - _from=None, - until=None, - limit=None, - offset=None, - unpaid=True, - transaction_type=None, - ) - ) - assert len(result) == 2 - assert result == [invoice_lookup1, invoice_lookup4] or result == [ - invoice_lookup4, - invoice_lookup1, - ] - - description3 = "test3" - description_hash3 = hashlib.sha256(description3.encode()).hexdigest() - preimage = secrets.token_hex(32) - payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() - content = { - "method": "make_hold_invoice", - "params": { - "amount": 5001, - "payment_hash": payment_hash, - "description": description3, - "description_hash": description_hash3, - }, - } - content = json.dumps(content) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - await client.send_event(event) - - start_time = datetime.now() - while (datetime.now() - start_time) < timedelta(seconds=10): - await asyncio.sleep(1) - try: - await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=None, - ) - ) - break - except Exception as e: # noqa: BLE001 - LOGGER.error(e) - continue - - lookup_hold = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=None, - ) - ) - assert lookup_hold.amount == 5001 - assert lookup_hold.description is None - assert lookup_hold.description_hash == description_hash3 - assert lookup_hold.metadata is None - assert lookup_hold.preimage is None - assert lookup_hold.payment_hash == payment_hash - assert lookup_hold.transaction_type.name == "INCOMING" - assert lookup_hold.state.name == "PENDING" - assert lookup_hold.settled_at is None - - description4 = "test4" - description_hash4 = hashlib.sha256(description4.encode()).hexdigest() - preimage = secrets.token_hex(32) - payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() - content = { - "method": "make_hold_invoice", - "params": { - "amount": 5002, - "payment_hash": payment_hash, - "description_hash": description_hash4, - }, - } - content = json.dumps(content) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - await client.send_event(event) - - start_time = datetime.now() - while (datetime.now() - start_time) < timedelta(seconds=10): - await asyncio.sleep(1) - try: - await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=None, - ) - ) - break - except Exception as e: # noqa: BLE001 - LOGGER.error(e) - continue - - lookup_hold = await nwc.lookup_invoice( - LookupInvoiceRequest( - payment_hash=payment_hash, - invoice=None, - ) - ) - assert lookup_hold.amount == 5002 - assert lookup_hold.description is None - assert lookup_hold.description_hash == description_hash4 - assert lookup_hold.metadata is None - assert lookup_hold.preimage is None - assert lookup_hold.payment_hash == payment_hash - assert lookup_hold.transaction_type.name == "INCOMING" - assert lookup_hold.state.name == "PENDING" - assert lookup_hold.settled_at is None - - info_event = await fetch_info_event(client, uri) - assert ( - info_event.content() - == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice pay_keysend make_hold_invoice cancel_hold_invoice settle_hold_invoice notifications" - ) - encryption_tag = next( - tag for tag in info_event.tags() if tag.kind() == "encryption" - ) - assert encryption_tag.content() == "nip44_v2 nip04" - notification_tag = next( - tag for tag in info_event.tags() if tag.kind() == "notifications" + info_event.tags().find(TagKind.UNKNOWN("encryption")).content() + == "nip44_v2 nip04" ) assert ( - notification_tag.content() - == "payment_received payment_sent hold_invoice_accepted" + info_event.tags().find(TagKind.UNKNOWN("notifications")).content() + == "payment_received payment_sent" ) - - l2.restart() - - l1.rpc.connect(l2.info["id"], "localhost", l2.port) - - (responses7, _res) = await fetch_event_responses( - client, - client_pubkey, - 23196, - lambda: executor.submit(l1.rpc.call, "xpay", [lookup_hold.invoice]), - 1, - ) - hold_events = [] - for event in responses7: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if ( - content["notification_type"] == "hold_invoice_accepted" - and content["notification"]["payment_hash"] == payment_hash - ): - hold_events.append(content) - assert len(hold_events) == 1 - - -@pytest.mark.asyncio -async def test_hold_invoice_expiry( - node_factory, - get_plugin, # noqa: F811 - get_hold, # noqa: F811 - nostr_relay, -): - url = nostr_relay - l2 = node_factory.get_node( - options={ - "plugin": get_plugin, - "important-plugin": get_hold, - "hold-grpc-port": node_factory.get_unused_port(), - "nip47-relays": url, - }, - broken_log=r"Relay receiver exited with error|Connection failed", - ) - uri_res = l2.rpc.call("nip47-create", ["test1", 3010]) - uri_str = uri_res["uri"] - client_pubkey = PublicKey.parse(uri_res["clientkey_public"]) - LOGGER.info(uri_str) - uri = NostrWalletConnectUri.parse(uri_str) - - preimage = secrets.token_hex(32) - payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() - LOGGER.info(f"preimage: {preimage}") - LOGGER.info(f"payment_hash: {payment_hash}") - content = { - "method": "make_hold_invoice", - "params": { - "amount": 5000, - "payment_hash": payment_hash, - "expiry": 5, - }, - } - content = json.dumps(content) - keys = Keys(uri.secret()) - encrypted_content = keys.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .finalize_async(keys) - ) - client = Client() - relay_url = RelayUrl.parse(url) - await client.add_relay(relay_url) - await client.connect() - - await fetch_info_event(client, uri) - - (responses1, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(event), 1 - ) - error_events = [] - success_events = [] - for event in responses1: - LOGGER.info(event) - content = keys.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - LOGGER.info(content) - if "result" in content and content["result"] is not None: - success_events.append(content) - if "error" in content and content["error"] is not None: - error_events.append(content) - - assert len(success_events) == 1 - assert len(error_events) == 0 - - # The hold plugin has no expired state: an invoice that expires without - # ever being accepted (and without being cancelled) stays `Unpaid` and the - # track stream never ends on its own. The handler must give up at the - # invoice's expiry instead of hanging, and must not send a spurious - # accepted notification. - l2.daemon.wait_for_log("was not accepted, skipping notification", timeout=20) diff --git a/tests/util.py b/tests/util.py index 393a970..a3f69aa 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,8 +1,8 @@ +import logging import os import random import string from pathlib import Path -from hashlib import sha256 import pytest @@ -11,11 +11,9 @@ plugin_dir = Path(__file__).parent.parent.resolve() COMPILED_PATH = plugin_dir / "target" / RUST_PROFILE / "cln-nip47" DOWNLOAD_PATH = plugin_dir / "tests" / "cln-nip47" -DOWNLOAD_HOLD_PATH = plugin_dir / "tests" / "hold" - @pytest.fixture -def get_plugin(): +def get_plugin(directory): if COMPILED_PATH.is_file(): return COMPILED_PATH elif DOWNLOAD_PATH.is_file(): @@ -24,14 +22,6 @@ def get_plugin(): raise ValueError("No files were found.") -@pytest.fixture -def get_hold(): - if DOWNLOAD_HOLD_PATH.is_file(): - return DOWNLOAD_HOLD_PATH - else: - raise ValueError("No files were found.") - - def generate_random_label(): label_length = 8 random_label = "".join( @@ -44,6 +34,15 @@ def generate_random_number(): return random.randint(1, 20_000_000_000_000_00_000) +def pay_with_thread(rpc, bolt11): + LOGGER = logging.getLogger(__name__) + try: + rpc.dev_pay(bolt11, dev_use_shadow=False) + except Exception as e: + LOGGER.info(f"holdinvoice: Error paying payment hash:{e}") + pass + + def update_config_file_option(lightning_dir, option_name, option_value): with open(lightning_dir + "/config", "r") as file: lines = file.readlines() diff --git a/tests/uv.lock b/tests/uv.lock index c2c260e..6c3395a 100644 --- a/tests/uv.lock +++ b/tests/uv.lock @@ -1,9 +1,14 @@ version = 1 revision = 3 requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version < '3.11'", + +[[package]] +name = "asn1crypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, ] [[package]] @@ -35,88 +40,88 @@ wheels = [ [[package]] name = "bitarray" -version = "3.9.2" +version = "3.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/71/dd598d2d546d11d7aca6cd25f05875e2dad194df0a663f1892690d4fb90d/bitarray-3.9.2.tar.gz", hash = "sha256:37342f81c8f8e10ee5d1e23d5eda2e8dbd9d8d3a9d90e8285181fad57f21cdc1", size = 160932, upload-time = "2026-07-25T06:39:21.234Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/47/b5da717e7bbe97a6dc4c986f053ca55fd3276078d78f68f9e8b417d1425a/bitarray-3.8.1.tar.gz", hash = "sha256:f90bb3c680804ec9630bcf8c0965e54b4de84d33b17d7da57c87c30f0c64c6f5", size = 152471, upload-time = "2026-04-02T16:29:01.712Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/0b/8509a00dc5ab2945e9856582f8a4908a78f15a7a08b425dc067f733299d1/bitarray-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a334e162a24c4e85babffb78823d83aa04c882a7dd40c2106b668a8cde3da752", size = 156246, upload-time = "2026-07-25T06:37:14.799Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1e/40d32865af46ff2c8a4ee488c60f1dd3724f3ff2c8405cb6594e75685c7d/bitarray-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8855aab242b4865e7ac7e14b6ce80f4cdf213984f260f31d92505d6877c620f9", size = 152874, upload-time = "2026-07-25T06:37:16.141Z" }, - { url = "https://files.pythonhosted.org/packages/12/07/0a646e68fa1fdb73e260474fdca595b93a7ecae6da49ac1d4c34be2b3b08/bitarray-3.9.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf06a3a5263268a35ed0d5513309791040ec8e8d3089bb5af12f51979dbf0a2a", size = 350232, upload-time = "2026-07-25T06:37:17.071Z" }, - { url = "https://files.pythonhosted.org/packages/38/5c/c1926f34269fb112d4b8b2b4fb0b84fc47b02268c30b300d237cccf07060/bitarray-3.9.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b82130cfba659e6a9da81298f43be678e99c598e64d814cfa86f0df8c1e230aa", size = 372334, upload-time = "2026-07-25T06:37:18.165Z" }, - { url = "https://files.pythonhosted.org/packages/42/b8/9937340228a814d72194543a1d452d552e3ea45d1537e27914deff41bbe0/bitarray-3.9.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ebc32a4fd8659a848d2c92ca5d75a1029cff9711bedbf3a32c515a19d4b3868", size = 384431, upload-time = "2026-07-25T06:37:19.251Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f1/311991e38cae3aa2b0f48726578e98aa584157c1f5bf4c8df51ed5a273d0/bitarray-3.9.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047e6ad06ffe606cdc9d75b83effc5f14ab11f30bf6ca38fd98553410e23880b", size = 353328, upload-time = "2026-07-25T06:37:20.383Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e0/7087f78801ad89fedf4812bd9cf46c204e48c1cfbf83bd0d8eede7dbd076/bitarray-3.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:42e7735c20e3e8e8044bd566ffa8461ad4870a8459c5d3fe9d43984eaa2d6104", size = 348330, upload-time = "2026-07-25T06:37:21.579Z" }, - { url = "https://files.pythonhosted.org/packages/71/b9/42b7265266ceee164942e34c278e3982618ff022b40771004693c6a64055/bitarray-3.9.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:23aa70b05518ee20d965a8cbd1e168ba4085647f12d7bdb654412a253c476652", size = 370012, upload-time = "2026-07-25T06:37:22.584Z" }, - { url = "https://files.pythonhosted.org/packages/11/11/a80fde6c902cdef198a20e218a1005f09d9bc5266a8db310cfd2468d0e7b/bitarray-3.9.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0197a8970233d65e04357308d52e5310ea8219e07587f8db8a98d7ac21b7de50", size = 365747, upload-time = "2026-07-25T06:37:23.791Z" }, - { url = "https://files.pythonhosted.org/packages/30/6b/01ae2bfc9778ff2bc78c20c1fe5a80b7290baa1b57e98f04435eafa09a68/bitarray-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1a5b7919ab373df270f268ab35c49aa311ed06412290313cecfae9406003ee", size = 350281, upload-time = "2026-07-25T06:37:25.015Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ec/1ccde07e363791274c6f4f376d1e844254d97ea762fd25a2372a1e56f92a/bitarray-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3aba64f5b449f50498185c81ba16dc9b819b06d985630f1b630e836081cf5051", size = 148006, upload-time = "2026-07-25T06:37:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/85/26/ee9cbd0b3df4cb9939bbbe41b0e9fca5185c33b0788eb5129a432784bf58/bitarray-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3a6bda7bd4a77926a18dc9890acad77ae96c2e4d9ff163a20438a67a23d7d956", size = 157184, upload-time = "2026-07-25T06:37:27.093Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ae/04a86f8fc6a57205b27bad5e201aaa3bdafd70324eb4407c764018b712a2/bitarray-3.9.2-cp310-cp310-win_arm64.whl", hash = "sha256:4f9576e2e417eb4b45257c135b40d5e465969382dbf4291377f4b7327ca74603", size = 153173, upload-time = "2026-07-25T06:37:28.061Z" }, - { url = "https://files.pythonhosted.org/packages/61/cf/1c8efde1c873445d7609e2b1576edb29a7b946a8135a8a5f60b5e775fb79/bitarray-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38c850db9ae8602e21c5c29193546a7ac85f1a70adfd4d65853154825675fce2", size = 156241, upload-time = "2026-07-25T06:37:29.185Z" }, - { url = "https://files.pythonhosted.org/packages/bb/2e/21ebb66fb272295c1e90ee04245e00f3655109b95881cf014485fae35526/bitarray-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:06df682d5b9ef233a21fab71912f131b2c295913202f2ad5370e44b0e0b26241", size = 152869, upload-time = "2026-07-25T06:37:30.329Z" }, - { url = "https://files.pythonhosted.org/packages/e7/db/4af8e55d377c25ca37a1da550d1852e44fd280ce5b550bef91d5aa52d2e9/bitarray-3.9.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0c064263c54fffe8fdfe8e20074816c67ec70608b0efb42a7d9fdf35ef67271", size = 359514, upload-time = "2026-07-25T06:37:31.384Z" }, - { url = "https://files.pythonhosted.org/packages/b6/20/464b03e4388d2093998bb355de1396d318728f9d8e1cc715770295babda7/bitarray-3.9.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0a61eac6a5a47cc7bc458b1f0737a701767543949580605cd85272c48121fb0a", size = 381209, upload-time = "2026-07-25T06:37:32.499Z" }, - { url = "https://files.pythonhosted.org/packages/02/fd/f24ec964e5737180957366473b664f8587a34dc7792e674a9d6c839db2e6/bitarray-3.9.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b62d7de8e4dc4ffbff407e1c7f868b84a00ac4a9919e28755fd299e86740fced", size = 393701, upload-time = "2026-07-25T06:37:33.501Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c2/0ac619df1eb86593c7b7d833325cfa6cf5a3bcd55f3fa0871e30e1d6036d/bitarray-3.9.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ea75f495073f95e96e3f3049411854b5db1693901d8b324439a170ef45088fa", size = 362278, upload-time = "2026-07-25T06:37:34.691Z" }, - { url = "https://files.pythonhosted.org/packages/23/6b/3325564d17cebb71f6beca6b41d463d1083acc68ae06abb3ef2729f78b46/bitarray-3.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f3eb99f3cc4ef93b4830e8532ba506be4105f15daddba9302bbd96f24b1cf72", size = 357205, upload-time = "2026-07-25T06:37:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/16/37/6253ca3aa44d257c8a70ce9bb98c0d05302bff586979c22b2264c9ae4457/bitarray-3.9.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e22236531f0193457277a7340d8bdd006f202f563f22f29abb873dc0850eb472", size = 378079, upload-time = "2026-07-25T06:37:36.958Z" }, - { url = "https://files.pythonhosted.org/packages/04/4f/e7595cb740e1b296f601b0d1292702b867292204e8f4ff010772f3a75c02/bitarray-3.9.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:92a438122d35d5e5703983c7dda7de90fc25c50282ce60f73ad61a6ced3ce0bc", size = 374941, upload-time = "2026-07-25T06:37:38.021Z" }, - { url = "https://files.pythonhosted.org/packages/87/61/135e3b0b42d65577725f2ddada426fce35cf816bc7d07e954ff6194b1193/bitarray-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47ec58fceafd38d5839bb9d8262ca6150e222f81d1e4adc8feafda6caf360334", size = 359249, upload-time = "2026-07-25T06:37:39.079Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/9b7a01e4d5d634f36fc1151854fdc61c09678b53dfd667f66baaff7efbff/bitarray-3.9.2-cp311-cp311-win32.whl", hash = "sha256:e8ec38401d9370684709f8cc3a0febbb742edf9fbacb1e432bcad8d023990e6d", size = 148130, upload-time = "2026-07-25T06:37:40.15Z" }, - { url = "https://files.pythonhosted.org/packages/fe/be/ee0813622d5fc2c6419aa5ec8e12407593f56c41a6e70fe1b8092b158c82/bitarray-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:30e2516878f7745957948e1a2d877f56ceda13b5f6b51d92d2174ef1756ab473", size = 157489, upload-time = "2026-07-25T06:37:41.158Z" }, - { url = "https://files.pythonhosted.org/packages/26/4c/1ad11f39eb6b1851777fb25185e839d19d273f2350c5e1bcbf0e014a7fda/bitarray-3.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:89c87ffdb399a53821cb0f5566a7d04e0f2870096a4964b038866a894b40c9c6", size = 153465, upload-time = "2026-07-25T06:37:42.151Z" }, - { url = "https://files.pythonhosted.org/packages/73/c3/6d33775d73e22cfe3fe23dae2ed50dbabdb38ea651e8b5c983262c39ce26/bitarray-3.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c967be9bd2fadd58e0d2c4e139e6e7443b5dd9162e52e43ef5e2489218c4fecb", size = 156385, upload-time = "2026-07-25T06:37:43.505Z" }, - { url = "https://files.pythonhosted.org/packages/9c/9f/24b22bfa3016fdd50966966f7dec0ecf11a911429be5178880228068e108/bitarray-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c17c918e569a81a2dd87794e15991c9643accf72b5c005e8791cdedcb2d6189", size = 152941, upload-time = "2026-07-25T06:37:44.64Z" }, - { url = "https://files.pythonhosted.org/packages/48/61/affb7fd0ce338529b57eea7d1fda983b4f4adde89eb7ea8e4201c4aa4e47/bitarray-3.9.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eea0dc21749eeb53b163d45c3dd0524e62c0301dd512bfb4f5ef34ccce7be6ad", size = 362020, upload-time = "2026-07-25T06:37:45.687Z" }, - { url = "https://files.pythonhosted.org/packages/31/e1/43f6eed3ae86d08c11eb424ad0c7ffd40bb081eb50c682c76adeead3ed0d/bitarray-3.9.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:64fb672905884fc236ef200b9fbde529c6673a8217c62bf8651bdce4842ba366", size = 382732, upload-time = "2026-07-25T06:37:47.212Z" }, - { url = "https://files.pythonhosted.org/packages/12/f1/cf664f54fef3c5c921e7133a2795201e508c2c984ffe7c0f3f851f2b976d/bitarray-3.9.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:be25330b9da322a806b29bae0147e6ea3730b6f85a5036e25f8e50247fee013a", size = 396696, upload-time = "2026-07-25T06:37:48.309Z" }, - { url = "https://files.pythonhosted.org/packages/35/fc/6d6409ec30dabd388b6f3ee6c461eae353f04d95dc287f7be19fffff8fcc/bitarray-3.9.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e14fd457ccea13e0adca60d93ffcd17b70184e7f485a986523241a077f3ac9", size = 366507, upload-time = "2026-07-25T06:37:49.671Z" }, - { url = "https://files.pythonhosted.org/packages/37/47/d241a6dde522a213cd624a820e0be998b0e5548d141098e990bba03913cd/bitarray-3.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f719003b0d692d3088e8f783ec684bf7caf00bddb5392efbe7842ba1ff15e8e", size = 359937, upload-time = "2026-07-25T06:37:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/d3/a6/0f4dd5a813f87b79e07e4bece697e872f4836486fca92903c63cc75c42db/bitarray-3.9.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a72e6b82f1800a08dff90a9d401ffd3f8c278644e4f6711a7458c9f16ee9446e", size = 379627, upload-time = "2026-07-25T06:37:51.884Z" }, - { url = "https://files.pythonhosted.org/packages/cc/27/cbc55ea191b52710c19702cbbae7814ff55082b02a4c23aba11c23dd1990/bitarray-3.9.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d043ef192ae741f0524b3d3404a2e6e8dbb6affeda421d40d46456cf6cfa1064", size = 378404, upload-time = "2026-07-25T06:37:53.43Z" }, - { url = "https://files.pythonhosted.org/packages/fc/70/20179544538444c0896099a10a035d857c2f4ce707a950834aa62d645714/bitarray-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23fe97bfed91ae4c927871b779caa159000da958fe5521d92420a09ed524efdf", size = 363092, upload-time = "2026-07-25T06:37:54.796Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/c9b206363aad0239cf966ea4eefaaa0da2717cd695ac11af1effbe3819bb/bitarray-3.9.2-cp312-cp312-win32.whl", hash = "sha256:ff72bbdd7c01ca661cb6712e706c0e0f0bc45a6b2426f69e9517cf7508b8d294", size = 148523, upload-time = "2026-07-25T06:37:55.915Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/67f161485ba266ad76064405cd19dda50057f466d796b4ad9d9e2645b877/bitarray-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:12c2e7556f7d7f3d2a2017d66159790ed6cadf849a3aa436d9c3541dd3facd6c", size = 157829, upload-time = "2026-07-25T06:37:57.285Z" }, - { url = "https://files.pythonhosted.org/packages/58/cc/aefdf64e5c3b057538ed6e5f244fa055bc825b10cb3a08ead0895ba9ff67/bitarray-3.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:76c0a98d85b2ed7090ff6c0fff9917a2ddffaffb82c2d0ce7d424f0830dc8dee", size = 153677, upload-time = "2026-07-25T06:37:58.332Z" }, - { url = "https://files.pythonhosted.org/packages/9f/33/a15849ec7ca05b55835b52baa4f226ee1d75cffd0943cf0a648c52bf9048/bitarray-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5156ae1fb0d56238b2ba64b8c0da9a14dbc2e16c9d79114ad3850b7aff513b0a", size = 156131, upload-time = "2026-07-25T06:37:59.76Z" }, - { url = "https://files.pythonhosted.org/packages/53/98/f097b0e50c31c4e1cc8f8612e21b8d193a66c0d14ca07f06eba376c5f090/bitarray-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:054fc478842a95d8e9e200117a7e170cd8ec17608e3cfa451e44fc78e5ab5007", size = 152618, upload-time = "2026-07-25T06:38:00.769Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c2/ea3786bb4231564482f23f1aaf478a1ddf8e8d08cfb90167c17cca89623d/bitarray-3.9.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d5e428a70d6e395b9fd415bba70bfb463d2b9e8010d471cb99eb0f82e91d179", size = 360335, upload-time = "2026-07-25T06:38:01.829Z" }, - { url = "https://files.pythonhosted.org/packages/fd/93/5772f7c4ffa4d6a1e9e486e8623f4f89041268e8c91b5761ad0920fc53e8/bitarray-3.9.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a34d717b362f96a767b03f350cefecd0f613a44ce774868d3635423eec1e5d75", size = 381029, upload-time = "2026-07-25T06:38:03.1Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/e84a9a61cbf7db68703f6ebeb853c4d323c9b1d83bd4d863ceb424575785/bitarray-3.9.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:068a100b2d3507e1ebd6f0cb3515551ddf3c7db1c98426dc854ddff985508eb8", size = 394606, upload-time = "2026-07-25T06:38:04.149Z" }, - { url = "https://files.pythonhosted.org/packages/1a/3c/127d48763eaf8941556486fc7883096d9024139276dfc19150523bfc6e31/bitarray-3.9.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a797165662ce08029a5975e05e928b77f979d8322cc719ba46eb8b3d9ab5c9b", size = 364341, upload-time = "2026-07-25T06:38:05.401Z" }, - { url = "https://files.pythonhosted.org/packages/1e/9b/d58b96fc10bec25fa59aa894964dadb5cbfb150fe4b30aeecea162c2cda4/bitarray-3.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6468d775054cb4fe777d585c661df89e99f9398fa9cb31e428a9b42105e6ce48", size = 358005, upload-time = "2026-07-25T06:38:06.464Z" }, - { url = "https://files.pythonhosted.org/packages/61/5f/25989fb53e1e4f079b2c75b24d0bcb500b407cb0be2c35953fee092a872a/bitarray-3.9.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd26caa42d12c747af2c3a3995b08db32ac7afa0c5262c0b6c6931293067805", size = 378038, upload-time = "2026-07-25T06:38:07.818Z" }, - { url = "https://files.pythonhosted.org/packages/fa/42/e80c45b496d60e216af776ed284c65cc55434cfbe39cd459975f1fb6a0ca/bitarray-3.9.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:11574711bae2b501141f64cf6d7a9819c9f45e7d8edcd2d9fe282b377dadb900", size = 376295, upload-time = "2026-07-25T06:38:09.079Z" }, - { url = "https://files.pythonhosted.org/packages/04/84/8bc4a8e12a802199b20912f57427e3ba6b8cc3649190fecb7c97bb35a69d/bitarray-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c6e16d6a8d93362f65336fc57e76fb1fe94f257bb54a843cf1a83c86887ac1ca", size = 360959, upload-time = "2026-07-25T06:38:10.299Z" }, - { url = "https://files.pythonhosted.org/packages/34/ca/3ebd885ae0df8f9ec508315361e602fada65c5c75d8b1d1660c81659cce0/bitarray-3.9.2-cp313-cp313-win32.whl", hash = "sha256:7fd8d649b10ba26c032c76ffb2610385496ebf5e52c44c26864e9b16ac8102b3", size = 148350, upload-time = "2026-07-25T06:38:11.473Z" }, - { url = "https://files.pythonhosted.org/packages/6b/92/7ecff07e9bbf6f51092bc3a01361bd5bae5814cd3326abc8423a2ae637a0/bitarray-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:0df2bd4b5fea65be332cf9c17351d4c68d3eb442c0e13a5bda8019554cb36ce1", size = 157627, upload-time = "2026-07-25T06:38:12.591Z" }, - { url = "https://files.pythonhosted.org/packages/7c/c4/28122fd875b6bdbebfc15bf60ea3704149540b0f3f43e15f247a7e9604ba/bitarray-3.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:94278c9d37a6f4316ae1f100e98e6582369f25b1f8186bb595ee1578fbc5c52e", size = 153528, upload-time = "2026-07-25T06:38:13.863Z" }, - { url = "https://files.pythonhosted.org/packages/5c/b2/307b9857295d37489b3269eb3cbcf62ab55d19e17c6c567602a2fbf3a589/bitarray-3.9.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b0debddd2eb7d1a0417f602b2520dd6fb6897ab140c1d69b8c42a69d164ef61b", size = 156152, upload-time = "2026-07-25T06:38:14.92Z" }, - { url = "https://files.pythonhosted.org/packages/95/55/3d13cb858176c2a9189c35becccef29b45c32720e7a1f36c57af431ff7c0/bitarray-3.9.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5d0387459ac35f6be40c4b809c34f34afe14aa930493e0b06f6df5d327f405c3", size = 152690, upload-time = "2026-07-25T06:38:16.096Z" }, - { url = "https://files.pythonhosted.org/packages/59/1f/7d01c3130fac3d7cd9f52697019e4dbf992fc67befb7c915faf456bc1c1c/bitarray-3.9.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bde034041875a48ea8c0a4b272e9a6e29174b234bd4905c86a65484f34cd140", size = 360268, upload-time = "2026-07-25T06:38:17.27Z" }, - { url = "https://files.pythonhosted.org/packages/25/61/2b6f05eb6c855727799dd3092b70e933ff433e9464740245042c2bfc68f7/bitarray-3.9.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:37c76ff084118ab1db8b826e35c01ce4d0137e66b611542e16b1f296c96e6aa6", size = 381244, upload-time = "2026-07-25T06:38:18.462Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/72d15249f41ffcfad56b4cea7d2a518d936c33c3548f6f474de96ae9da88/bitarray-3.9.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d12c7b574e2903477124a0d77bbc903fd4c3beaefa841f4a63c199014192447b", size = 393965, upload-time = "2026-07-25T06:38:19.823Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6c/e880b52ebed9441a423afdb9f8f3b71186b689cefdcdc6fcb434ca4775ee/bitarray-3.9.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bbff5cccf3a1f4129c8ad43b2bcb789882fba47c87422b30a74bc3bee98a292", size = 363960, upload-time = "2026-07-25T06:38:21.06Z" }, - { url = "https://files.pythonhosted.org/packages/5e/39/2fb0d14bc884de3dda42a6cfbe2325d76af45733e6f97709924352db2bf8/bitarray-3.9.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c374884716ab641c36ee4f51859fa4274fbdf0355ae7242290a29af3cf49381d", size = 357843, upload-time = "2026-07-25T06:38:22.263Z" }, - { url = "https://files.pythonhosted.org/packages/af/ca/5f7fe5898ea35e34ce79b653ae513d4c67bc0ae607229c51a09d8278195e/bitarray-3.9.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fe1d36b6d816512af098699c85e9237ce5978863a336a2833ebc03f59545781", size = 378105, upload-time = "2026-07-25T06:38:23.436Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c9/c6b3121421783f10481836140ab976cba7b73bceb9f58216eabf1cee073b/bitarray-3.9.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4f719595fde8bdf030282c8221c738248a3a73928db3087ea62db2c29de5342d", size = 375952, upload-time = "2026-07-25T06:38:24.695Z" }, - { url = "https://files.pythonhosted.org/packages/30/95/05f50342d799d7f49688371ebd3695b79c6fa65db4eeb9fc335c72aa755e/bitarray-3.9.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:28c4edee03ece6768ee51c1889c5d4df05764f8ad956fed6295912dbe4aef729", size = 360654, upload-time = "2026-07-25T06:38:25.944Z" }, - { url = "https://files.pythonhosted.org/packages/30/88/836ad7a1421cd4dd888ed602eb7e7fb297de866e64dab19150be596e9abf/bitarray-3.9.2-cp314-cp314-win32.whl", hash = "sha256:f01b94e8bd0229c214f0873f8d19cf4db4602a230064317a8be00428922f7987", size = 147264, upload-time = "2026-07-25T06:38:27.106Z" }, - { url = "https://files.pythonhosted.org/packages/2a/cd/ca5feb145af0ad602c88a2bb43ff3c44067846324c58e8c972a397adf2ee/bitarray-3.9.2-cp314-cp314-win_amd64.whl", hash = "sha256:7892775de516287fbc850cfb9476599bc399b971105fbbe216513ac4cb67d6bf", size = 156687, upload-time = "2026-07-25T06:38:28.268Z" }, - { url = "https://files.pythonhosted.org/packages/5e/f4/8d9516a9aa91b18c9c0075451386b09c342acf3cca536331438d38e8c955/bitarray-3.9.2-cp314-cp314-win_arm64.whl", hash = "sha256:7dcbf363dce364722a3cd3875be5453a36db8c476af551bc5b194ea9960c591f", size = 153104, upload-time = "2026-07-25T06:38:29.357Z" }, - { url = "https://files.pythonhosted.org/packages/38/ac/62db07d42f4713d8032acb2c5776550f99da74ead726d3ab49845ac2b768/bitarray-3.9.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:732fc6464eebdfbbdb28e7a2e98424ba7633619665fb065cb6d756ffef5f6b08", size = 159619, upload-time = "2026-07-25T06:38:30.939Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4a/7ddb362b4dc3f7beb428e96c129fbc96196c7a716658d5d677dffe8fffbd/bitarray-3.9.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c23f1dc6f118c3a885ee2a281261da37e98c35b4361dcf2c9a9d3d70370b32e6", size = 155980, upload-time = "2026-07-25T06:38:32.085Z" }, - { url = "https://files.pythonhosted.org/packages/0f/78/08b2ac57ff404a33e98193481d1a4fe716999ed2255335fbd56ddda7737d/bitarray-3.9.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ada39b08677af76c48a64f1f14e8e518315cf478004ddf4b6c580ce3625c1a9d", size = 378321, upload-time = "2026-07-25T06:38:33.376Z" }, - { url = "https://files.pythonhosted.org/packages/e5/79/5a0734dbc080748b11680483d74f29469c85b56ff6b490028c301732c658/bitarray-3.9.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70e5a48906a8cab0b8d84ad356d8cf8f0fb533b93475da26437a4a3aed8a9a4b", size = 397775, upload-time = "2026-07-25T06:38:34.619Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2f/a4d87cc0a1a01851fb76cb13633413fc8d2187cfbf00d9f85397edba301c/bitarray-3.9.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b14351e75d76662d10c1479c8864f4076ab393a297323b85277d2253ccfb96e", size = 411104, upload-time = "2026-07-25T06:38:35.945Z" }, - { url = "https://files.pythonhosted.org/packages/0e/df/5b2d12198c9403a8b757dfc54fee1199e202ec361ad7fb4cb84f295e917e/bitarray-3.9.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:920edc84385932401ddabe62e244774acaa69e991072768fcf7e621c29599f6f", size = 380204, upload-time = "2026-07-25T06:38:37.14Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5f/b6daaa21059439703d53349d9e9158860fa04d31b8a5613dfdc45c8f4b9c/bitarray-3.9.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3d6dd06fd0e1c88e6da86e4c0479457c74f03f66a2dc16c0c60ecce12c5dee1d", size = 374273, upload-time = "2026-07-25T06:38:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/1f/4f/625e820bdc9235f56ef9644892c7b6b89a96cb7f5a0823748e9a912f1d1a/bitarray-3.9.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ee339e8f9a763499c5fc855927c8174680183e8751f03f797234a0bd1223c3ff", size = 394096, upload-time = "2026-07-25T06:38:39.785Z" }, - { url = "https://files.pythonhosted.org/packages/02/27/04ee7179dd670781a3c457969e94503a10c516e43340faf04f6862c7c0ef/bitarray-3.9.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:50f0ff3b5e5cfa7ca42e2469418a26d098a42e47b0e171adf9c6c69ea800d055", size = 391958, upload-time = "2026-07-25T06:38:41.011Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7d/ef4ce2906e6c3056021196d11efb220f351f7db74783882e802c8f81c17f/bitarray-3.9.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c898478e6dde8ca75970b91a602b1f48ebedff49841b89cb8495c1949b6d762c", size = 376178, upload-time = "2026-07-25T06:38:42.535Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/ff961878f0671e921d03a498705b4a15bb96b53474685f2d04abb79204f4/bitarray-3.9.2-cp314-cp314t-win32.whl", hash = "sha256:415675dbae4d1c58a5ae31550db6224e0785443e41d9ed74cf4371aec38d61d3", size = 150524, upload-time = "2026-07-25T06:38:43.939Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ec/a3eb15a4a9730e3f48315f8d510867944f9051b82a465f4fe1d28b9a9e76/bitarray-3.9.2-cp314-cp314t-win_amd64.whl", hash = "sha256:de17695646139d2a9863cf1d8ff83f1335aa3d593f2730c9ae442fb08ef80241", size = 160476, upload-time = "2026-07-25T06:38:45.145Z" }, - { url = "https://files.pythonhosted.org/packages/c9/81/73c274307ec6f74fb072623ec2644e6467f36b24f7464f0be2ed236c183c/bitarray-3.9.2-cp314-cp314t-win_arm64.whl", hash = "sha256:19105d5ff84450b341a0dc1021c7f8d63cb1ffedcdc78b46e62f4cd76f28a7f1", size = 156219, upload-time = "2026-07-25T06:38:46.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fc/4352b1dd55a50c85f7b502c011d40279a66a05eb0c6a5d3d44160838d9a4/bitarray-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d42c34da2974a5e2e0b51c57ecf89892c1e83ed67e1084d1e27eefc27add91", size = 149074, upload-time = "2026-04-02T16:26:16.319Z" }, + { url = "https://files.pythonhosted.org/packages/34/06/104c9ff50e5230f6581056d6f4b0d1e0db14aba41549cae4b0541be0369c/bitarray-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0793c51d3b1c7410bde1f7254fff71fabff1bc0cdeba1fa51319ac4e7931df3d", size = 146031, upload-time = "2026-04-02T16:26:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/65/0b/99d65fa6ceb3616c4b96ab9fef2dcd4994ad05fa48f595706ba001f13ba7/bitarray-3.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133648c3405564e7fef9103f1768cb018de1b4976f3d8beff09cd4acea73bfe4", size = 325129, upload-time = "2026-04-02T16:26:19.77Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/e0913c6b15fbd1e6b4d60a541a6784eb5d8f1fddcbcbb8c076240f665f2e/bitarray-3.8.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4fd3399eaf6f1c77ea3132611efbc3d7a8c0eb899793387b3266be221dc75fd", size = 353126, upload-time = "2026-04-02T16:26:21.274Z" }, + { url = "https://files.pythonhosted.org/packages/49/1b/0fd86dece4eca8078a99e54ca01183d5b660195dea8f2c8ad5740b190e9f/bitarray-3.8.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b9790ae107fc8648155f120e80a58ef8e94424efefff5b355de84061de6a18b", size = 363588, upload-time = "2026-04-02T16:26:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2d/e62ddf9e52a0124a19f2cd83be5dfa256c6c1f20722fb0bb4b0aed51bb0b/bitarray-3.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af01133e78e5528ee282ceb1cf4bc54aecb937c2001913e751452ad7dffbbeb1", size = 331725, upload-time = "2026-04-02T16:26:24.296Z" }, + { url = "https://files.pythonhosted.org/packages/cd/fc/ea1c532169d56747c128f4e0256a3ad1f0c91ed00ca83cdf93964a60fec3/bitarray-3.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2da2ca9495668ab77132a911f6bd530d2bfe686d10467584894efc3b66e9ffb5", size = 322939, upload-time = "2026-04-02T16:26:25.904Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/8fe68993779f077c713fc4c21c0d9ba2719beeea596bcdc37f9660b6f181/bitarray-3.8.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:72a0e87b2196120523fc6194ca6b580fcffa12d7daa4d57a16d7838e60f82d0e", size = 351084, upload-time = "2026-04-02T16:26:27.396Z" }, + { url = "https://files.pythonhosted.org/packages/96/c5/f5cb62b60e0da428ef9457e7e1d9a3d3d8874b4f0f925adfff4b9ab3a319/bitarray-3.8.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:defa3c12cb06b2fd2066a9e21bf00aab96465be84d9585c8c05195f080510506", size = 347489, upload-time = "2026-04-02T16:26:28.935Z" }, + { url = "https://files.pythonhosted.org/packages/88/71/bb9baadbdd305f80def4220ce38266f53404433661492fc2c3d894129bfb/bitarray-3.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7eae9e763fbd32f19f2a66dfc2e37906f8422e0c4ad4a6c9dcf9d3246740812e", size = 328394, upload-time = "2026-04-02T16:26:30.585Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/c4d487b029488bebef38a4c04df34294d27f313b5ab3491aa3a051394f24/bitarray-3.8.1-cp310-cp310-win32.whl", hash = "sha256:3b9358f6437a5fa0c765ffae5810c9830547baf4bcf469438b82845c3f33f998", size = 143245, upload-time = "2026-04-02T16:26:32.414Z" }, + { url = "https://files.pythonhosted.org/packages/98/ae/adadedf7cdd49fb8d81b8013d3471c193d6208035a0748205c808b1709cd/bitarray-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f92d12a46b2a67d56194bb5d226dabf586b386d1f1a5e25be5b745a3080dbba", size = 149976, upload-time = "2026-04-02T16:26:33.868Z" }, + { url = "https://files.pythonhosted.org/packages/26/de/2a7a8c2868d85e671a6cdb5282bbb299d98cdc0b4c4ade0cfa9a2a21d91d/bitarray-3.8.1-cp310-cp310-win_arm64.whl", hash = "sha256:8e12d50d4d65c74bd877e15c276992263b878456a7cfcf72521e7205a553557f", size = 146729, upload-time = "2026-04-02T16:26:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/05/5c/32ace44d0313b4a9986d2abc3a1349744920dafcfb6a4e454a10ed09ef5a/bitarray-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:660e11b9932f58f10151d0febd11f77d3b0d48d6fa4dd4686d8983f40187101e", size = 149069, upload-time = "2026-04-02T16:26:36.671Z" }, + { url = "https://files.pythonhosted.org/packages/6d/85/7bd0a218478f0a226ddfb756dd64286f8ee3c61a17991a1a50aae8d89dca/bitarray-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb1df55f5700187c6db4b47dbdaf8a0653a111341ac7fccc596b397aa3399e65", size = 146036, upload-time = "2026-04-02T16:26:38.179Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e9/e4e6aec6874efac185959f4627b6a61a88c0dad3ec92eee433fd395daa78/bitarray-3.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:838fd67b3d00c5a64181073282a2c0bf8f76465da4844d5e79d2dbbc64c987dc", size = 333036, upload-time = "2026-04-02T16:26:39.723Z" }, + { url = "https://files.pythonhosted.org/packages/50/5f/d493eb77f79b58eaa489e9e032aa1c91f6af844287b341c6be681df11b0d/bitarray-3.8.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5743f532e408cfd716fa16776b5a6447b83ff2cf39021fb5f8d052aa0f331508", size = 361247, upload-time = "2026-04-02T16:26:41.023Z" }, + { url = "https://files.pythonhosted.org/packages/24/a3/2e3f33c66f61754b5bb4724d54c9c1122699facc580bcb416d44f1164ffc/bitarray-3.8.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0c8c66f5d8055cb84ad0ea14af57b3579cb0b6db589f2086f5e33f0922cf2354", size = 371922, upload-time = "2026-04-02T16:26:42.373Z" }, + { url = "https://files.pythonhosted.org/packages/05/03/4dfca9a69dfa69cde6fdbcfafbc039e069e105ea2443688177f6873d8444/bitarray-3.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c3fe25871f1758519a3ad8dcafb1bd95c5d1aaeb122e6492ac739ab11fa5907", size = 339203, upload-time = "2026-04-02T16:26:43.915Z" }, + { url = "https://files.pythonhosted.org/packages/14/5d/a2275da6c935893f275624c88afab6cdd5b6aa916d0b45c50dd400cafb20/bitarray-3.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e9ff57452fcadfd1a379314234657b8f4e9967ae64480ddf7c2fd82139bc8cf8", size = 330956, upload-time = "2026-04-02T16:26:45.675Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/7f4041c7a7e94ef3e7de86fdb4102d3fe366998b507de77ba0fe5dff6c44/bitarray-3.8.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4e34f1cb6cdb036c5f4a839a2b74419f75fa36177a70c4bab2867f48973cbe44", size = 358882, upload-time = "2026-04-02T16:26:47.327Z" }, + { url = "https://files.pythonhosted.org/packages/29/4e/2d0c381327c0f5bc49681b799bbe7d80d5e629079f9609a79d39da6e8b8f/bitarray-3.8.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:698c37fca3761af69a09a1d39cc0492f7e8cb9e263af39a288dce8f3b8a9e2bc", size = 355761, upload-time = "2026-04-02T16:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d9/66644d45d9f844d1c78b80f3517c8717ac4b4d9853ec61bd02b3cabc06e6/bitarray-3.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:81ede1f094f26eeaff62e029ff1bc4e84e9d568f20d4669f64dcf7c7b18a28fc", size = 336422, upload-time = "2026-04-02T16:26:49.988Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7d/4ea3fd2424535630d4d236bc0c721621260b39878eed669dbc1deb5c6b22/bitarray-3.8.1-cp311-cp311-win32.whl", hash = "sha256:8a345b5dc8ab8cafdf338e08530d48fe3f73df27f4ff569be793c7a7e7bb6b6b", size = 143391, upload-time = "2026-04-02T16:26:51.69Z" }, + { url = "https://files.pythonhosted.org/packages/d0/4f/46309fcf9e1793c7184e3fc1aa73d7daf2b6a2b0fa1efbcf8d497101690e/bitarray-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:ddcd25a1f72b2b545fb27e17882046a6c161f3f24514b2e028c00c58ed73a2dd", size = 150143, upload-time = "2026-04-02T16:26:52.9Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1e/10289fb8e44fdd2d01adcc24d64b5c45ead709fbec76ee973f42e22b3059/bitarray-3.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:dc2cab92c42991b711132bc52405680e075d1505d4356c4468bc6e9c93d49137", size = 147024, upload-time = "2026-04-02T16:26:54.151Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4f/6ab3767b6642a6cbee4353f10a71fe25ade9899d539fae47c3d50686ebe2/bitarray-3.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4494c599effa16064f2b600f6eb28115182d6826847d795a55691339788d8a4d", size = 149202, upload-time = "2026-04-02T16:26:55.635Z" }, + { url = "https://files.pythonhosted.org/packages/eb/53/22bfffd13dd0a266f90011338b24eec45f25c91d37155bb2aa330351e17d/bitarray-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ff2ca039a161d49a8c713f5380def315c6f793df5fe348b94782b1dbee37a644", size = 145999, upload-time = "2026-04-02T16:26:56.849Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/60aff29c88b648e18248921001cf9d7169abeda4d8db96f2dc1a24ed98ca/bitarray-3.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df3ffa6ef88166bb36f5d1492e71e664868b9b8b6afd55821e0ac0cb96625441", size = 335945, upload-time = "2026-04-02T16:26:58.403Z" }, + { url = "https://files.pythonhosted.org/packages/83/c8/225380610a01ae0d8f2f5256e531bae7135b2ade6f4607156424718ec43a/bitarray-3.8.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:478b9f0ea86f957624dd2b159066855716f78db94666e9b04babe85fc013e01b", size = 364213, upload-time = "2026-04-02T16:26:59.742Z" }, + { url = "https://files.pythonhosted.org/packages/6c/df/83899be9a74ec5878972e8b636f645ef1771e146c6425a161fdafdd74aaa/bitarray-3.8.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e127b2e7fc533728295196f9265d12834530f475bc6cd6f74619df415d04b8b1", size = 375409, upload-time = "2026-04-02T16:27:01.081Z" }, + { url = "https://files.pythonhosted.org/packages/6c/93/38bc15cb097107d220a942eb66dc50882496d7da54f41e5eea6c31b1c443/bitarray-3.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ef49462a615de062dcac8281944d0b036fe1e9c96a6c690bf6cf5e4b5488f0e", size = 343645, upload-time = "2026-04-02T16:27:02.577Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c3/75fae6991946f8bf643ec50233432ea81b5b65bfdb2918b09d7e37605380/bitarray-3.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4da256fc567a57ded2a4aa962fc9e9d430ab740e5c67be9e98a63ef4eb467f2f", size = 333844, upload-time = "2026-04-02T16:27:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7e/649e7c3bb12ba938c387bcad6a6c0b84312663c9807ec1457888936690d8/bitarray-3.8.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b46b7aec9272fd81c984e723e599957629a91204120b3e7f0933f138e0792fdf", size = 361267, upload-time = "2026-04-02T16:27:05.361Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5f/db0fb71a7c6c3ef047b84256157e96fa35e10ed8b79b80e892d354ab37f6/bitarray-3.8.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2dc07dab252c63c4f6600e200b26fa05207db6b650d41ae88ab0cec4d6c59459", size = 359373, upload-time = "2026-04-02T16:27:07.106Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b6/a082d84cba7ba509b48d160034f6a2d31df6bf4fff0471801e888bba96c9/bitarray-3.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:29c8c10a49d6a9586f592116618b99c3dabcb24d881b7a649e0691ef87f314c4", size = 340633, upload-time = "2026-04-02T16:27:08.794Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b7/1ba7ec1f3aa62933dfef505b09de0b75778a3cb05984ee8bb798539381db/bitarray-3.8.1-cp312-cp312-win32.whl", hash = "sha256:67125404d12547443d74113862a80c10310cf875aff8dbfc5548fee1d9737123", size = 143521, upload-time = "2026-04-02T16:27:10.423Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/5ff9d30a1121810f336517e51b1cbdea0fa92e92b142efe0741e335dc14e/bitarray-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba0339d6aa80615a17f47fabc5700485e9469121d658458f95cdd2003288c28b", size = 150451, upload-time = "2026-04-02T16:27:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/a6/08/51e49eb09ca45ecda4a5f05b70a10977a5f0ac39967c79479e9d3e41cb29/bitarray-3.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:c0b367a00e8c88a714b2384c97dedcc85340547b3a54b6037a42fca5554d0576", size = 147218, upload-time = "2026-04-02T16:27:13.566Z" }, + { url = "https://files.pythonhosted.org/packages/13/79/015a30f40f716a0372907a7ac5c399db5428209dcf264b85ef1305f9b3e2/bitarray-3.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:55f4b105a1686eb486069a9e578d502d1998e890d8144012225de9e0450aeabd", size = 149201, upload-time = "2026-04-02T16:27:15.383Z" }, + { url = "https://files.pythonhosted.org/packages/23/fe/f70b150ea9a330daecc546a5a63576ba2d6b3bacc1ccde42abc9dd35a1ad/bitarray-3.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b3118ec012a799456f7fca6cc002c078590578b7640fbaab52d8ecb9a651f1c1", size = 146001, upload-time = "2026-04-02T16:27:17.041Z" }, + { url = "https://files.pythonhosted.org/packages/78/49/2c637658851ea0408c7375f5f278c0ebb69cbe861f8fcc9477db14ee7fa2/bitarray-3.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2762db8049b230520358ac742cbc57bceaacebe34e5d25c096f2b4bc3887a3a8", size = 335162, upload-time = "2026-04-02T16:27:18.587Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3c/ae665a0b2d6183cc706c03b683b7f9ad53195731379ab82dfa537e73f70f/bitarray-3.8.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b67b869f860eb19055e2560844d8c7d0935245938935bdb764b3e683e2014e2", size = 363031, upload-time = "2026-04-02T16:27:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ee/7b7c37fbb15209525f0daff1a51a042c035e931ebd526aabb483fdc7a476/bitarray-3.8.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a661f3492462e7adf8a054fb7414a22fc8251f1e18b9d8cbcf008d2dc85f012", size = 374623, upload-time = "2026-04-02T16:27:21.468Z" }, + { url = "https://files.pythonhosted.org/packages/96/dd/26a17534742561974e5b2a3448d70fd8d370ed885bd88bbbb36bdd022875/bitarray-3.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:300e3026d17ae3328320ba78d3165bdb1c43d0dfdbc461a69ebbdc005d9ce0b3", size = 342850, upload-time = "2026-04-02T16:27:22.814Z" }, + { url = "https://files.pythonhosted.org/packages/58/f1/97410e88a8b441c1a6e5841c651e483787c3c87f2b98c1d2421aee23790d/bitarray-3.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ad5a71c1ef4a2e404c2c888db09226c821d9d14eff8813e1da873572f5fbb89d", size = 333109, upload-time = "2026-04-02T16:27:24.271Z" }, + { url = "https://files.pythonhosted.org/packages/64/1a/74a3af2d314ec6a035ae8f139491ace4fc8b3362bfdc86aee652b8f15be5/bitarray-3.8.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:78cbda57a2808d994517b53571eaa2d9299359f63aa71cf4bc94210169aad8b1", size = 360334, upload-time = "2026-04-02T16:27:25.999Z" }, + { url = "https://files.pythonhosted.org/packages/d8/86/01ea58ca9795401489f9de662ef9ba759d6712870696a5806441b2c14224/bitarray-3.8.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:89c7c125a0913d71ba9cc1fa8e14c7cfe1517b1c1f45416e1f9babcedd3b545d", size = 358674, upload-time = "2026-04-02T16:27:27.597Z" }, + { url = "https://files.pythonhosted.org/packages/68/c9/14587fd3c712047af60a875889ad69926386c3fdbf8061e9baf23d12d997/bitarray-3.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7875abfd90f2ae3aa22d50f3fa1c93bbae456458cc73d3179b838f07bed1fc10", size = 339689, upload-time = "2026-04-02T16:27:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/5b/82/a8cc5172dba50c90d7cc89d9f5c1cfb99deb77af10b4762eb75ece52e20a/bitarray-3.8.1-cp313-cp313-win32.whl", hash = "sha256:21add0aa968496a2bd8341d85720d09808e22e0adc7dbefc1e0f8f67c4b83f36", size = 143503, upload-time = "2026-04-02T16:27:30.948Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b2/f647dcd098c275a67b89d21c92471180996a797cec11e308b4d1936d170d/bitarray-3.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:40d1b57012bf9b4fefd25345aaa95aab3ca510cc693f33c2cb02a4b771d8e51a", size = 150441, upload-time = "2026-04-02T16:27:32.642Z" }, + { url = "https://files.pythonhosted.org/packages/ba/78/bde39d566f70149c6858c7e61c0a0d902a643a136a56dd37b6135cc59a68/bitarray-3.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:72b32d8c471930c95d49640ec99f7694f9b040ca1342ff03ed69d3aea90f9339", size = 147209, upload-time = "2026-04-02T16:27:34.289Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/d07ee3ef120a3b3f1db2434c4b955fbf900bb3f878e25a71ee82408e9d91/bitarray-3.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fe989bbed9d6f332c1e24d333936f3fa1375f380cd8028da0b985dcdefa6015a", size = 149181, upload-time = "2026-04-02T16:27:35.608Z" }, + { url = "https://files.pythonhosted.org/packages/ab/bf/43bf76bbf95354e74b80923e8aa7d6cb178e25546eeab0705524ad4d5171/bitarray-3.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:75e33c9187da271d1dbeb2582ab2df2e441346492098f67559b09173ea4edde4", size = 146020, upload-time = "2026-04-02T16:27:37.279Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/868644e4f61220529ceea0be6dff1c659a7c20dc354f8c5aa367409e6150/bitarray-3.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd7e3158be382f8f140caccc0dc7742a7553ce4bf2978982abe3054d2cedd705", size = 335102, upload-time = "2026-04-02T16:27:39.065Z" }, + { url = "https://files.pythonhosted.org/packages/f9/6f/0eb0ed1214bb6436e078a44006127685b587b6aeb1600bc2f77bf53e96b9/bitarray-3.8.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9fa5620f7f352f9706924c0e2071a212be36421f09ee064b0fd7e1128289fcdb", size = 363405, upload-time = "2026-04-02T16:27:40.596Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/07d6ec5ad40792ff92857ac51cb91c01f855e3edb7b589eb099937420722/bitarray-3.8.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:190b20cbffc9cd7f308f7a57d406119c3af3ae197613325fd2d92d99c8882ad6", size = 374225, upload-time = "2026-04-02T16:27:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/46/53/2c5d688ea0f91025d3fdd08e13f9d5195c384953961070ce79719efd18b3/bitarray-3.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec3d0a6c37a816ea6e3550697c60d90861c9b0f982a98a40b59ac1f7a360bfa9", size = 342742, upload-time = "2026-04-02T16:27:43.706Z" }, + { url = "https://files.pythonhosted.org/packages/2e/8a/25a932f02a8d1ca0c97cae62399f475d219669dbfecca3b2d7567effec73/bitarray-3.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:746e25f17ba4203b5933773782cf2d30bca5cdb66a9ba5d48a53a6c795aedc57", size = 333236, upload-time = "2026-04-02T16:27:45.237Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a2/83fc66eb64ee0e74dc04894fbe8ae7e2d083c824ae9c1396d68a14c50760/bitarray-3.8.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ab363a5baae965fb3438f2137583853ad9c77d7e45f2a62ba63e609a34d792ea", size = 360526, upload-time = "2026-04-02T16:27:46.724Z" }, + { url = "https://files.pythonhosted.org/packages/a1/60/eee517c36956d9fc8d4ae2b2fbcf9122477b0730dacd52a2800226b11e61/bitarray-3.8.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5e30d8e399f38ae1ec86aa9be76d20ba15872dd0c41b4b46d1b78905857363b9", size = 358208, upload-time = "2026-04-02T16:27:48.221Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a5/2d35fb2d1abc8afa4fef93f3ad96192eebd81595c3f9389a95f5d01ee782/bitarray-3.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0f099a4a77daf9bb99787070854894fe588c7d6988ea729f970ba2b3b82c7559", size = 339373, upload-time = "2026-04-02T16:27:49.911Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b5/64d6d485725076472e0f151643ac4fa78ed54f10f6b7bf9620690a24af7a/bitarray-3.8.1-cp314-cp314-win32.whl", hash = "sha256:539880ddf9a8cc54c9e6126e7d072c991563f0c90ef73b3519a783d53df00352", size = 142632, upload-time = "2026-04-02T16:27:51.371Z" }, + { url = "https://files.pythonhosted.org/packages/04/e9/cf02dfac88f4c7d3de2dbafec4ec0616eaf9547dd7be98e81dc0fde97a77/bitarray-3.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:c08cd5b19c570e1e9e094a6ce70d35bb39d12360e0763474ed9374229f174fcc", size = 149180, upload-time = "2026-04-02T16:27:53.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a8/8e56397347bad7b042aefee9afc0cb085f2a779f7c8cc38954b12671d37c/bitarray-3.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0da5f17bed67ffe1d72f79fbf98403513a6e51a4f9b8293c1ff8a64e121242be", size = 146389, upload-time = "2026-04-02T16:27:54.759Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a6/52bcd001c5cdf5c381a7317b07157070be1a6bc7fa5d58314ef6da33626b/bitarray-3.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:154a19e1dcd430494fdad7d1a0fb36383baaa363e1cb9d5a7b744cd2418c44d2", size = 150091, upload-time = "2026-04-02T16:27:56.154Z" }, + { url = "https://files.pythonhosted.org/packages/b6/22/86f51124a9d0e622be0bd171b797e0d507d5c9d6f76f5b97cb12e4ecf113/bitarray-3.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:814bb54db2a016026efc055a3527461e5eb551c0d91b32eeade003829ff84311", size = 147130, upload-time = "2026-04-02T16:27:57.916Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d4/2153c6de23e26d0bfa65e0994ef771f06f8697a9ae65473923f6922ab1b9/bitarray-3.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac49519fcfeb4a7ecdf6b7d0ec6cac409e59f94c1bb54630db577a97893b6e38", size = 343168, upload-time = "2026-04-02T16:27:59.345Z" }, + { url = "https://files.pythonhosted.org/packages/55/bd/6ff9be5965c11e2f67bec674cd1bbe41e81531ec970251986f4d4978a72d/bitarray-3.8.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:329b994944993c45c3845047476ef4f231fe1a53972f18f8d005fd12fac163e1", size = 371961, upload-time = "2026-04-02T16:28:00.956Z" }, + { url = "https://files.pythonhosted.org/packages/7d/24/9aca563d253ed28be47b9a8d5f2fe0942e0191bc4ef49589e7670177807c/bitarray-3.8.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1d7b786a1ddd9b8dda17c445060a94a465cba2e113603ae7bdc5364efc1efd11", size = 381778, upload-time = "2026-04-02T16:28:02.589Z" }, + { url = "https://files.pythonhosted.org/packages/be/e2/81ac4c98694857b7eabdb9ada77db5b44fb6b6d5d19a3a716fb8a486c251/bitarray-3.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd9b848c17ef034f2ae31b2a1bd9276710c2baf03509f1f3fa4dc4382b0a1b53", size = 348165, upload-time = "2026-04-02T16:28:04.431Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c5/2107bf1474a139f934621703135985f2acfae92d786561edde62ec557f60/bitarray-3.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a33f8931ac91ebc23ce4decb99ed8fdddba2bafd2af3bb2781bcfd9878d4822", size = 340225, upload-time = "2026-04-02T16:28:05.899Z" }, + { url = "https://files.pythonhosted.org/packages/31/95/a3d2571055279a09373d1f93249404ac37a34045e334935adbc2ce780f83/bitarray-3.8.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07626f76a248fce5ebbb10fb0d4899d3c7f908ba21cb2fb4f5a7a9daf24c20cd", size = 369440, upload-time = "2026-04-02T16:28:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/ef/56/7fad5bb52c0b9cdcdea4405f5d9a41f5efbf2873045d668bc2b8db10213b/bitarray-3.8.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:18f3a2c8908e63a66d3994808254397a5f989b1fb91087c33739f62bf1a1a064", size = 364733, upload-time = "2026-04-02T16:28:09.086Z" }, + { url = "https://files.pythonhosted.org/packages/10/b0/a23a1c312206c65146021aea68d69c8c7d817ae2f99698cbc23b3c744bba/bitarray-3.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ced27af6aee28782260bfa5643797937e96a6489bca972202834017208cf74f5", size = 343729, upload-time = "2026-04-02T16:28:11.09Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/fe5dbdfadcba2314551614d023db100e3aea7b09da2cdcbf1386f5c797a6/bitarray-3.8.1-cp314-cp314t-win32.whl", hash = "sha256:cf99e36c0f6ae5643ecef7ad7e1194aeb4a9798d9cff60b20ac041533fa6db0a", size = 144160, upload-time = "2026-04-02T16:28:12.702Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0a/e95ed44f6d89e63ed666755fc0773223b10df0058d5de30d529f4cf35948/bitarray-3.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:9befda0dbd27ed95fba1c26be4bf98a49ba166b3c91beb5fc04364c130ce950c", size = 150852, upload-time = "2026-04-02T16:28:14.149Z" }, + { url = "https://files.pythonhosted.org/packages/0f/90/454b88b193743b4cd0fce0819a11b1c43b7f629cb2533f6ddc62cbb5e097/bitarray-3.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:4b7d7d10a1c82050efbb9a83d7a43974f70cf8f021afb86463b42e4ac4e5a46b", size = 147343, upload-time = "2026-04-02T16:28:15.632Z" }, ] [[package]] @@ -143,208 +148,198 @@ wheels = [ [[package]] name = "certifi" -version = "2026.7.22" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] name = "cffi" -version = "2.1.0" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, - { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, - { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, - { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, - { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, - { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, - { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, - { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, - { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, - { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, - { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, - { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, - { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, - { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, - { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, - { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, - { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, - { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, - { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, - { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, - { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, - { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, - { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, - { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, - { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, - { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, - { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, - { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, - { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, - { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, - { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, - { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, - { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, - { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, - { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, - { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, - { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, - { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, - { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, - { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, - { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, - { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, - { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, - { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, - { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, - { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, - { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, - { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, - { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, - { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, - { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, - { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, - { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, - { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, - { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, - { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, - { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, - { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, - { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.9" +version = "3.4.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, - { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, - { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, - { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, - { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, - { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, - { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, - { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, - { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, - { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, - { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, - { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, - { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, - { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, - { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, - { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, - { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, - { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, - { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, - { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, - { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, - { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, - { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, - { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, - { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, - { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, - { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, - { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, - { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, - { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, - { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, - { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, - { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, - { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, - { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, - { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, - { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, - { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, - { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] @@ -362,14 +357,14 @@ wheels = [ [[package]] name = "click" -version = "8.4.2" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] [[package]] @@ -393,62 +388,56 @@ dev = [ [package.metadata.requires-dev] dev = [ - { name = "nostr-sdk", specifier = ">=0.45" }, - { name = "pyln-client", specifier = ">=25.9" }, - { name = "pyln-proto", specifier = ">=25.9" }, - { name = "pyln-testing", specifier = ">=25.9" }, - { name = "pytest", specifier = ">=8,<10" }, + { name = "nostr-sdk", specifier = ">=0.44" }, + { name = "pyln-client", specifier = ">=25.5" }, + { name = "pyln-proto", specifier = ">=25.5" }, + { name = "pyln-testing", specifier = ">=25.5" }, + { name = "pytest", specifier = ">=7.4,<9" }, { name = "pytest-asyncio", specifier = ">=0.23.8,<2" }, { name = "pytest-timeout", specifier = ">=2.4,<3" }, { name = "pytest-xdist", specifier = ">=3.7,<4" }, ] [[package]] -name = "coincurve-cp314-fix" -version = "22.0.1" +name = "coincurve" +version = "20.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/f9/e9d3c502369933915d3e72d596a1ddf81b3d5ae986f5bd65bc5ada52e6b9/coincurve_cp314_fix-22.0.1.tar.gz", hash = "sha256:b68397f902a73ba15ab5652fae712f6ba85625e382026be2a5c5e698845a79f4", size = 129346, upload-time = "2026-07-17T14:49:10.202Z" } +dependencies = [ + { name = "asn1crypto" }, + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/4c/9e5e51e6c12cec6444c86697992f9c6ccffa19f84d042ff939c8b89206ff/coincurve-20.0.0.tar.gz", hash = "sha256:872419e404300302e938849b6b92a196fabdad651060b559dc310e52f8392829", size = 122865, upload-time = "2024-06-02T18:15:50.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/ca/ec5e2c4ba1df02844e1087e58636ecc78a096fa7e7dfd85cab286c0c1c31/coincurve_cp314_fix-22.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9fec35946bfe7784e92aaa656b298b3b67f0a093f52c7970496dde4621bbe6c0", size = 1388687, upload-time = "2026-07-17T14:48:04.925Z" }, - { url = "https://files.pythonhosted.org/packages/a2/01/5c0435325516a6d7ce2958e90e4d050652d105465a187ff8e983a1271370/coincurve_cp314_fix-22.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5f4abc2e639a4007ca212b7029ea9fc549dc1e1207348f1c8729e34b6e0a94c", size = 1383962, upload-time = "2026-07-17T14:48:06.636Z" }, - { url = "https://files.pythonhosted.org/packages/4c/93/d9ea4c7955a1f67740e492adca7d8ffdb7b9434c2ddc9628e578d37552bb/coincurve_cp314_fix-22.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2dc1deb3cec48571fa05064fa8dad3488a0d63e3f16fa731c7815f3f204a2cb", size = 1371893, upload-time = "2026-07-17T14:48:08.247Z" }, - { url = "https://files.pythonhosted.org/packages/d6/0c/eec3262ad43b0f40203656d1a1e52377d603a47f76de6352dd15659285bc/coincurve_cp314_fix-22.0.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b60d06533f4820023a4e91679c2181ccc29a23193d55fc51e6d55874186b67", size = 1370994, upload-time = "2026-07-17T14:48:09.9Z" }, - { url = "https://files.pythonhosted.org/packages/36/9c/733843a2423ec759ccdd78338dba53f9bb9093f011ea8b31feab07e510cc/coincurve_cp314_fix-22.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:01428e65cfac5bedbeb7a6c1e146970d8aa8a384a7dc39f0c45c38350c6960cc", size = 1377492, upload-time = "2026-07-17T14:48:11.756Z" }, - { url = "https://files.pythonhosted.org/packages/33/e1/6c8bca371a395b32ddd86b2bcbae146789984b0bdf329524f6e85a73846e/coincurve_cp314_fix-22.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b787b23c80501e1af903bfe4f89a07c51ccc02e2333913e152f4e4c2cdbf644f", size = 1374123, upload-time = "2026-07-17T14:48:13.33Z" }, - { url = "https://files.pythonhosted.org/packages/d7/52/e9a7a1c82d615e9f4060ac77509bae9d95e1c9a4ad24941abca9f356d091/coincurve_cp314_fix-22.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:6b02bca3564e795a945c0a2a7f575d3af7d6e3c0e84133c610ca84cf0b824d21", size = 1329759, upload-time = "2026-07-17T14:48:14.845Z" }, - { url = "https://files.pythonhosted.org/packages/5f/55/c858d0ce0a2a3ec590463838af052492680debae92ca38b071ee459f351c/coincurve_cp314_fix-22.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:c2a0afd16f410f0b9932cc2123ba1adceca8676cffd649c4a5c1fe344d3a55df", size = 1320063, upload-time = "2026-07-17T14:48:16.294Z" }, - { url = "https://files.pythonhosted.org/packages/74/d9/6e191269f241861ed25ce193bb1d24e8758eb83276a0bc28d7f929704d45/coincurve_cp314_fix-22.0.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c7beead9480fc1e47b3727172af2279f1043bacc68f1a7226af7687bd893058e", size = 1388699, upload-time = "2026-07-17T14:48:18.13Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1e/a330eba734c262182bd7d35595c1e4b72a32cced388b649ab826b8b93d47/coincurve_cp314_fix-22.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3a710296ef735bb96434a152101bcdef9bdabbec291305eb0e6cc864935e7db", size = 1383919, upload-time = "2026-07-17T14:48:19.759Z" }, - { url = "https://files.pythonhosted.org/packages/52/ef/fa17567e5044fdbe958534191b31e461affc2019df6e21f6644981bc2cb1/coincurve_cp314_fix-22.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8bb83a3ea92906d5fe1623c3554c4973c0b51cb7f3a5e10dae549eec9b1b1eb", size = 1371952, upload-time = "2026-07-17T14:48:21.412Z" }, - { url = "https://files.pythonhosted.org/packages/5c/d1/b6cbf61dcbb11b053891d593d82fb976e5cc44092fef09d0787c8edb7071/coincurve_cp314_fix-22.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9d88f8faf7156fddad08dbd032671d6bb2427a262fc57e81be5fc4f9d1357aa", size = 1370118, upload-time = "2026-07-17T14:48:23.358Z" }, - { url = "https://files.pythonhosted.org/packages/99/f5/8d629287c7c2e7b9efca2cab3cfa95a02c2312881551b39875cb81671edc/coincurve_cp314_fix-22.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8a41bd1c4012d8ff480a3972cc9300400530edb8e7f0d16617e0f241fe0b72d2", size = 1377513, upload-time = "2026-07-17T14:48:24.923Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ca/8aac5adcd9616dd73095a2c4a707b30bc31674fae8522de44992309d34df/coincurve_cp314_fix-22.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b9083d1e5042866a1e28e91e9d5c8fdcf51629dd2a273f6b405059e7c95945fb", size = 1373226, upload-time = "2026-07-17T14:48:26.512Z" }, - { url = "https://files.pythonhosted.org/packages/dd/98/1b4f2f26d6980bdd17a8f2864832f9dff895b032a49234b51389ab58596d/coincurve_cp314_fix-22.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:918b4ad4d1e4f4cf38c11aa39b9d842418987f898caccc6114b45aa081ae2c90", size = 1329815, upload-time = "2026-07-17T14:48:27.899Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e1/21f17125e8b9160dd5f6db3f92da6e18b1f4cdececfc3a154f023a7f9ad7/coincurve_cp314_fix-22.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:ab926906a2718de26a3e23955103e41e39cb316e5cbb68489405d4a2bc2011c3", size = 1320212, upload-time = "2026-07-17T14:48:29.473Z" }, - { url = "https://files.pythonhosted.org/packages/4a/32/24fef91a512c51b57b5980207bdf4974ab6e998aefde3c50abb565b4f346/coincurve_cp314_fix-22.0.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:b077081c058338aaab7fe634275da0248fe59b469be9ec2bfa598e6e328fc5d1", size = 1389725, upload-time = "2026-07-17T14:48:31.19Z" }, - { url = "https://files.pythonhosted.org/packages/a6/7a/24b3079bee4bd2f081865e28a08eb13b909470c7d8ce3a8c4ac9aae80727/coincurve_cp314_fix-22.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ed7e6c3434333ecc17b00834f17effa5f48fc89b3e130d19157e3b18e0bd149", size = 1384501, upload-time = "2026-07-17T14:48:32.942Z" }, - { url = "https://files.pythonhosted.org/packages/41/26/7a4fbaff523784b9339f247a315056f99439fe671705d92bd9df01612e59/coincurve_cp314_fix-22.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b72535b8303d45bfc47bb27e7b1b898cfba11ddb9ca6db9e528f0e23360da5f3", size = 1375639, upload-time = "2026-07-17T14:48:34.777Z" }, - { url = "https://files.pythonhosted.org/packages/82/2e/c6f93f5bbbe6c050f5425b9e1ebb1fc8a28e3074b4ebe6542c91be186e27/coincurve_cp314_fix-22.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a56ec57a1ce4116db6281703c5c64d35a9fbcb4ed2ab65ac38109c412afb3b6", size = 1374117, upload-time = "2026-07-17T14:48:37.179Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f0/911d0295ed3a199c7a4eb43d2f8ffff1d1dcca39f2128fe5fc7f1fe52c17/coincurve_cp314_fix-22.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0501e49a206b5ab4105f65daeb338dcf3559bd7d5392a03afc3f31db83f661fc", size = 1381644, upload-time = "2026-07-17T14:48:39.018Z" }, - { url = "https://files.pythonhosted.org/packages/be/68/b3ecefd5edccd3202f24c0cfb696aff0756e6095db9f7c67da78aff57f27/coincurve_cp314_fix-22.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a5d93127fea898e53882ac106535fbc39ff4f2a26e9c5f23e1efae716bcf6a6", size = 1377529, upload-time = "2026-07-17T14:48:40.608Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/be427f7e8b037b14b94eb97e22fab08b8d41535075dd1b1d7a668fc1d4dc/coincurve_cp314_fix-22.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07fcc0a4b6c86566462c9918f6c68303e3699820c4df595daef9a8d131584f29", size = 1330618, upload-time = "2026-07-17T14:48:42.12Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0b/1833d9b75a8d0dabba52db6a4e665cd0bf24401c513a062f82c8fc4f56a2/coincurve_cp314_fix-22.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:f7b282d27d234b96e064e6bf061d56a4148a4e4fb14f0309fdcc555206892ab6", size = 1320184, upload-time = "2026-07-17T14:48:43.588Z" }, - { url = "https://files.pythonhosted.org/packages/93/9f/2d2576282a16fcddd4bacaae9767852203bc6fe6a851a2410b0dffb0bc87/coincurve_cp314_fix-22.0.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:227158f4cbd329f0b75780198ab312ac72d697b2ec28daade45d0c3d8aa7ae3a", size = 1389695, upload-time = "2026-07-17T14:48:45.298Z" }, - { url = "https://files.pythonhosted.org/packages/9b/6a/fc8e76c2f6d62af177960a719b5987e644412b13fd673175dea4f2696370/coincurve_cp314_fix-22.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6cde6eda5348115cd46555aae8e5fc8a1329cdd34560293a371a3e8394390c16", size = 1384517, upload-time = "2026-07-17T14:48:46.86Z" }, - { url = "https://files.pythonhosted.org/packages/63/f1/f3a62079cb2d4205712545d70e7bd221b276b0b379329d844ed825076014/coincurve_cp314_fix-22.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:155c9364dcea3b4028fc03d02b5ac4c1d791c7989c54ea8d73ff52600fa9bab1", size = 1375601, upload-time = "2026-07-17T14:48:48.388Z" }, - { url = "https://files.pythonhosted.org/packages/ac/1c/d4769d938a000d14ce99f08c42148e8fda85b5b243943e804a5f27e4d951/coincurve_cp314_fix-22.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ccf73b280220759fa02251f09300d85af54ff23631da3e95c14eef30cba7408", size = 1374082, upload-time = "2026-07-17T14:48:49.852Z" }, - { url = "https://files.pythonhosted.org/packages/fd/88/246bad1924267d0064a7a6092d10851f66765caefe9a0010be8c25c94a6c/coincurve_cp314_fix-22.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5eb5a6aa08e8e07d2edb3a6b8e6602f0dc94a1b0fbf4d83138df749b921fe8b2", size = 1381599, upload-time = "2026-07-17T14:48:51.33Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/08dbec75aced7f23dba7dc52f1294f30e862de33cc9112bd01ccf3b3f50d/coincurve_cp314_fix-22.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bde261973270d2313bf82d526a6a395d084faa74f514d98c2dfe96bcb3d331f", size = 1377435, upload-time = "2026-07-17T14:48:53.016Z" }, - { url = "https://files.pythonhosted.org/packages/28/ac/ff86fc78fa9dcf4792f57e0675030dd21701e0f121c165801a2434598e20/coincurve_cp314_fix-22.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:07ab4f37249dab54d3dc02e35dd82f0309c9995a1656f91ab1c8efebf7c35ed4", size = 1330553, upload-time = "2026-07-17T14:48:54.539Z" }, - { url = "https://files.pythonhosted.org/packages/1f/17/5f27565dd86fff04636b5a4a7a5450f6f35474b3c02ad6832852ce98e134/coincurve_cp314_fix-22.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:8d0d6057895da2d4fb7da49463c08ccbc3399a57df5b1ae18b296edcfe724cae", size = 1320215, upload-time = "2026-07-17T14:48:56.047Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/721383a5cc7079360afa5a235c8bbddbdf09505f39cc315d5eafba90f638/coincurve_cp314_fix-22.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b18156408e60b12b87810ae323ef0b483251c33f05f172e344c325a034014e5", size = 1389892, upload-time = "2026-07-17T14:48:57.533Z" }, - { url = "https://files.pythonhosted.org/packages/fd/f2/9f8518e3b138256d80d40ccdb3cb8edb78dd01846ff221da3d212f6c44b9/coincurve_cp314_fix-22.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:15c9257f42a42c14e098e0f37b8b5fc32371854c84f3bebe2448a307ca674b3e", size = 1384743, upload-time = "2026-07-17T14:48:58.999Z" }, - { url = "https://files.pythonhosted.org/packages/44/40/913a7a3f02678bc1990e508fba9fa156d643a788cceb611d8c35b99a4436/coincurve_cp314_fix-22.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c04475afd71244fb6e04af2d8f2e2ad98264f6b6f549826560ea96925425f33", size = 1375586, upload-time = "2026-07-17T14:49:00.759Z" }, - { url = "https://files.pythonhosted.org/packages/46/6d/21c2888ac918199cedd9b438a2bb5cd9e1746bff4cccbdf27f35c646b94d/coincurve_cp314_fix-22.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2e1066e72ac20310fb2359f0c7bdcc7d09f796eb3d316b3e03b3c5e59fab349", size = 1373843, upload-time = "2026-07-17T14:49:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/61/64/d1ae34a2843dc76e69f65ef6c611fd3f09bc6a11924a56c555b6aea9339e/coincurve_cp314_fix-22.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a629e282b8a9d531b882ee1735a6ce1cfc0c8c37531583131ade5c4ca5d08b1", size = 1381502, upload-time = "2026-07-17T14:49:03.993Z" }, - { url = "https://files.pythonhosted.org/packages/06/ad/1c0b90430feefd17594fda215880137a40bb7858dde62bb60c4b58cf0771/coincurve_cp314_fix-22.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f7697cc7d75de9b9e34e041cfe7dd903db2141a4c31c92ec420674f937c3af8", size = 1377090, upload-time = "2026-07-17T14:49:05.59Z" }, - { url = "https://files.pythonhosted.org/packages/7c/20/1267eb8492b9d2d7a904739476eebc7d4d3921d13e716621ca99128e25cb/coincurve_cp314_fix-22.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:56ddf3b2340c23c22fe16fbf171e18219de291cf2c26f75805c92640ec5ef8c8", size = 1334976, upload-time = "2026-07-17T14:49:07.092Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e7/9765c390984195f79c6b0d3ab299cc17749f57a9856651db30065cc66264/coincurve_cp314_fix-22.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:acfcd79081fc04e4f699731973c41165dfeee85b855dc352ee2d46250dd46f6e", size = 1327417, upload-time = "2026-07-17T14:49:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/14/0c/f6a8b06f461089aeab441824134ea5d5824dba3acaac0a9dbf8444cbe1d6/coincurve-20.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d559b22828638390118cae9372a1bb6f6594f5584c311deb1de6a83163a0919b", size = 1255634, upload-time = "2024-06-02T18:14:26.381Z" }, + { url = "https://files.pythonhosted.org/packages/62/c2/0dbabd2c6648f49f730fdcbba84c53b5ffaf452fca85c750633141fe049c/coincurve-20.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33d7f6ebd90fcc550f819f7f2cce2af525c342aac07f0ccda46ad8956ad9d99b", size = 1255532, upload-time = "2024-06-02T18:14:28.792Z" }, + { url = "https://files.pythonhosted.org/packages/f5/77/c4fa50f8cb5d050a9bcab806503acdd1705b0dfb5c554eed15cc18bc12e8/coincurve-20.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22d70dd55d13fd427418eb41c20fde0a20a5e5f016e2b1bb94710701e759e7e0", size = 1191928, upload-time = "2024-06-02T18:14:30.874Z" }, + { url = "https://files.pythonhosted.org/packages/a3/11/6254ea354a32a3a1d70722daf58f2ebf0f689f0940eaced5127233416553/coincurve-20.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f18d481eaae72c169f334cde1fd22011a884e0c9c6adc3fdc1fd13df8236a3", size = 1194364, upload-time = "2024-06-02T18:14:32.684Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a9/d8717d41eb02688691adc30d7348f7c5fdc78e977f4cea83ee84622050b5/coincurve-20.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de1ec57f43c3526bc462be58fb97910dc1fdd5acab6c71eda9f9719a5bd7489", size = 1204658, upload-time = "2024-06-02T18:14:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/55/13/40923832d99c18fb01a00f83e5f6f702156e71cc0eb5d6281535eee662af/coincurve-20.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6f007c44c726b5c0b3724093c0d4fb8e294f6b6869beb02d7473b21777473a3", size = 1215298, upload-time = "2024-06-02T18:14:35.318Z" }, + { url = "https://files.pythonhosted.org/packages/4d/87/646462a7a7810c7a3dcadae8969e1b78d535bcff072c26b17588e93a39b8/coincurve-20.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0ff1f3b81330db5092c24da2102e4fcba5094f14945b3eb40746456ceabdd6d9", size = 1204504, upload-time = "2024-06-02T18:14:36.567Z" }, + { url = "https://files.pythonhosted.org/packages/01/58/fbb9a312d559aee701491435b691e409fb0efa12eabf269ff651d537fed4/coincurve-20.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82f7de97694d9343f26bd1c8e081b168e5f525894c12445548ce458af227f536", size = 1209298, upload-time = "2024-06-02T18:14:38.384Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d0/1d5679c000b31f3b32512632d98571f2bb752cd25c127d6f5bf3711b6eae/coincurve-20.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e905b4b084b4f3b61e5a5d58ac2632fd1d07b7b13b4c6d778335a6ca1dafd7a3", size = 1198934, upload-time = "2024-06-02T18:14:39.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/f6/8c1499f730fac49ec13740fb1c015ce8082fa6b917790056988559f22212/coincurve-20.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:3657bb5ed0baf1cf8cf356e7d44aa90a7902cc3dd4a435c6d4d0bed0553ad4f7", size = 1193319, upload-time = "2024-06-02T18:14:41.165Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/d60a41b3f0a546854c9b7ca65ab99a5fdf1c9e158ae264a580de8f23fd1c/coincurve-20.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44087d1126d43925bf9a2391ce5601bf30ce0dba4466c239172dc43226696018", size = 1255635, upload-time = "2024-06-02T18:14:42.483Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4a/727fab66c0fbecfd7beeb38467910bd3652a77df649565e30160a9d2bae2/coincurve-20.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccf0ba38b0f307a9b3ce28933f6c71dc12ef3a0985712ca09f48591afd597c8", size = 1255536, upload-time = "2024-06-02T18:14:44.077Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8b/25d4ae5bb60665023e6d71681fada88ee95b5010dae6fc0b44d8b23b8df1/coincurve-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566bc5986debdf8572b6be824fd4de03d533c49f3de778e29f69017ae3fe82d8", size = 1191928, upload-time = "2024-06-02T18:14:45.739Z" }, + { url = "https://files.pythonhosted.org/packages/0d/86/8c32c512fa27bfe7cfe70329fd43ebac23c0c8cec202cf6e4f52854e7ce3/coincurve-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4d70283168e146f025005c15406086513d5d35e89a60cf4326025930d45013a", size = 1194365, upload-time = "2024-06-02T18:14:47.008Z" }, + { url = "https://files.pythonhosted.org/packages/fe/74/fefbe512f54df7d02a7ea4821b87cf199a91b3565cdf0c94448b3f6b1af1/coincurve-20.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:763c6122dd7d5e7a81c86414ce360dbe9a2d4afa1ca6c853ee03d63820b3d0c5", size = 1204658, upload-time = "2024-06-02T18:14:48.348Z" }, + { url = "https://files.pythonhosted.org/packages/09/68/05b29f881f628ce8e8468f5f7420f6c4d7c129f43964e81d15bf388ae67a/coincurve-20.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f00c361c356bcea386d47a191bb8ac60429f4b51c188966a201bfecaf306ff7f", size = 1215301, upload-time = "2024-06-02T18:14:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5d/d91549cf5a163797b0724dc2dcd551b908b6beddb6598b37743df7f6f3ec/coincurve-20.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4af57bdadd2e64d117dd0b33cfefe76e90c7a6c496a7b034fc65fd01ec249b15", size = 1204505, upload-time = "2024-06-02T18:14:51.816Z" }, + { url = "https://files.pythonhosted.org/packages/37/0f/898022e08760fb57d281f3695576e859b0f8a8ac629670223d9066c3f60d/coincurve-20.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a26437b7cbde13fb6e09261610b788ca2a0ca2195c62030afd1e1e0d1a62e035", size = 1209305, upload-time = "2024-06-02T18:14:53.39Z" }, + { url = "https://files.pythonhosted.org/packages/57/b9/643567d3f680ddf8d1bf10a56112ae7755296500d8eaaef498be637a8533/coincurve-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed51f8bba35e6c7676ad65539c3dbc35acf014fc402101fa24f6b0a15a74ab9e", size = 1198932, upload-time = "2024-06-02T18:14:54.751Z" }, + { url = "https://files.pythonhosted.org/packages/b3/3a/898f5c12469b292042608dd0702bcb0420ec32bac6b1ca2a0dd790f922bd/coincurve-20.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:594b840fc25d74118407edbbbc754b815f1bba9759dbf4f67f1c2b78396df2d3", size = 1193318, upload-time = "2024-06-02T18:14:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/8f/24/e1bf259dd57186fbdc7cec51909db320884162cfad5ec72cbaa63573ff9d/coincurve-20.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4df4416a6c0370d777aa725a25b14b04e45aa228da1251c258ff91444643f688", size = 1255671, upload-time = "2024-06-02T18:14:57.863Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c5/1817f87d1cd5ff50d8537fe60fb96f66b76dd02da885d970952e6189a801/coincurve-20.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1ccc3e4db55abf3fc0e604a187fdb05f0702bc5952e503d9a75f4ae6eeb4cb3a", size = 1255565, upload-time = "2024-06-02T18:14:59.128Z" }, + { url = "https://files.pythonhosted.org/packages/90/9f/35e15f993717ed1dcc4c26d9771f073a1054af26808a0f421783bb4cd7e0/coincurve-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8335b1658a2ef5b3eb66d52647742fe8c6f413ad5b9d5310d7ea6d8060d40f", size = 1191953, upload-time = "2024-06-02T18:15:01.047Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3d/6a9bc32e69b738b5e05f5027bace1da6722352a4a447e495d3c03a601d99/coincurve-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ac025e485a0229fd5394e0bf6b4a75f8a4f6cee0dcf6f0b01a2ef05c5210ff", size = 1194425, upload-time = "2024-06-02T18:15:02.919Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a6/15424973dc47fc7c87e3c0f8859f6f1b1032582ee9f1b85fdd5d1e33d630/coincurve-20.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e46e3f1c21b3330857bcb1a3a5b942f645c8bce912a8a2b252216f34acfe4195", size = 1204678, upload-time = "2024-06-02T18:15:04.308Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e7/71ddb4d66c11c4ad13e729362f8852e048ae452eba3dfcf57751842bb292/coincurve-20.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:df9ff9b17a1d27271bf476cf3fa92df4c151663b11a55d8cea838b8f88d83624", size = 1215395, upload-time = "2024-06-02T18:15:05.701Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7d/03e0a19cfff1d86f5d019afc69cfbff02caada701ed5a4a50abc63d4261c/coincurve-20.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4155759f071375699282e03b3d95fb473ee05c022641c077533e0d906311e57a", size = 1204552, upload-time = "2024-06-02T18:15:07.107Z" }, + { url = "https://files.pythonhosted.org/packages/07/cd/e9bd4ca7d931653a35c74194da04191a9aecc54b8f48a554cd538dc810e4/coincurve-20.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0530b9dd02fc6f6c2916716974b79bdab874227f560c422801ade290e3fc5013", size = 1209392, upload-time = "2024-06-02T18:15:08.663Z" }, + { url = "https://files.pythonhosted.org/packages/99/54/260053f14f74b99b645084231e1c76994134ded49407a3bba23a8ffc0ff6/coincurve-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:eacf9c0ce8739c84549a89c083b1f3526c8780b84517ee75d6b43d276e55f8a0", size = 1198932, upload-time = "2024-06-02T18:15:10.786Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b5/c465e09345dd38b9415f5d47ae7683b3f461db02fcc03e699b6b5687ab2b/coincurve-20.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:52a67bfddbd6224dfa42085c88ad176559801b57d6a8bd30d92ee040de88b7b3", size = 1193324, upload-time = "2024-06-02T18:15:12.511Z" }, ] [[package]] @@ -462,59 +451,62 @@ wheels = [ [[package]] name = "cryptography" -version = "49.0.0" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, ] [[package]] @@ -531,7 +523,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -566,11 +558,11 @@ wheels = [ [[package]] name = "idna" -version = "3.18" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] @@ -593,14 +585,14 @@ wheels = [ [[package]] name = "jaraco-functools" -version = "4.6.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/1f/c23395957d41ccf27c4e535c3d334c4051e5395b3752057ba4cbaec35c56/jaraco_functools-4.6.0.tar.gz", hash = "sha256:880c577ec9720b3a052d5bc611fb9f2269b3d87902ef42440df443b88e443280", size = 20837, upload-time = "2026-07-14T01:28:02.544Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl", hash = "sha256:99e3dc0060c5cbe8fcd1cdb36258e2a65ca40f1566b2033b12abb1bb44dd3c30", size = 11677, upload-time = "2026-07-14T01:28:01.59Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] [[package]] @@ -623,8 +615,7 @@ dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, { name = "referencing" }, - { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "rpds-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ @@ -739,40 +730,40 @@ wheels = [ [[package]] name = "more-itertools" -version = "11.1.0" +version = "11.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, ] [[package]] name = "nostr-sdk" -version = "0.45.0" +version = "0.44.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/73/9401a17b0a43bf22542f31e5c5624db7f838f7690977ec786edd72b0880b/nostr_sdk-0.45.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5bd1a6b972e9f5ae8c4f4448a148f668f124a3389b72eafb9de00f714e829059", size = 3835872, upload-time = "2026-08-05T13:14:21.43Z" }, - { url = "https://files.pythonhosted.org/packages/e2/5d/355adf2a676493101357080857cf7896880c6556fe84795281bc283513c5/nostr_sdk-0.45.0-cp39-abi3-macosx_11_0_x86_64.whl", hash = "sha256:a20cb90f4fa225e57118431fc05d1d567a78a047dab54d34897365fcc5b82498", size = 3901388, upload-time = "2026-08-05T13:14:22.92Z" }, - { url = "https://files.pythonhosted.org/packages/a1/72/489cd53d491afe180f71d24c4f4f334f6da38d1bf683e8907771f436bffd/nostr_sdk-0.45.0-cp39-abi3-manylinux_2_17_aarch64.whl", hash = "sha256:00da5e7e387515d1e12779eccfb2c794856f774b4534694200434aa0146a03ed", size = 4323888, upload-time = "2026-08-05T13:14:24.348Z" }, - { url = "https://files.pythonhosted.org/packages/7e/cb/9a5edbea9db9995704ebc44ec4fca153c5f7d9ea07d668cf7837f01ddf1e/nostr_sdk-0.45.0-cp39-abi3-manylinux_2_17_armv7l.whl", hash = "sha256:e5cae5ef56411d6665de78c39822a4e80382231d44b4a6fc3db096b8b203ec26", size = 3867750, upload-time = "2026-08-05T13:14:26.066Z" }, - { url = "https://files.pythonhosted.org/packages/c1/d3/1a464004596e9ea0366a62e1671fc41adefde97b477bf60e69615dedec64/nostr_sdk-0.45.0-cp39-abi3-manylinux_2_17_i686.whl", hash = "sha256:9d91d519a93c48ab4d689f975f406d8ee96e39d6876d2aeaaee58de7e66ded93", size = 4393993, upload-time = "2026-08-05T13:14:27.766Z" }, - { url = "https://files.pythonhosted.org/packages/c1/57/1684e67051af3540666e6aeba4ed5bc9ea027cbe311789dce79863c85b48/nostr_sdk-0.45.0-cp39-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:8152945cb27b48bbf6f89d73d8560bfbadd202b8e5c165bf5b5532e91feed417", size = 4426295, upload-time = "2026-08-05T13:14:29.397Z" }, - { url = "https://files.pythonhosted.org/packages/34/b0/526e134d23e2bdbac523bb38351d940a478cc2d7499c0ee9d1ee499ecfb4/nostr_sdk-0.45.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a7e0a886b427356dc94cd0b1bc8421b01eeb5aa9ea26e1b69c21f0a3cc1bc2ad", size = 4336015, upload-time = "2026-08-05T13:14:30.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/27/764af3a4bc1ec8542401616f11c41bbf9c42fbaef8659bf26bab4097ed6f/nostr_sdk-0.45.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:3b314b5160014bf70b39e95b3cf5cb059050e2fc2f782a0d9916c7ae1559e34f", size = 3864414, upload-time = "2026-08-05T13:14:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/bd/31/1289150239dfce59c1c2afb8313dfd21411040103c68e6121f1ba4515fb7/nostr_sdk-0.45.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:15346b498f4df4bb42ef2987cecd9e97ba60294b9bc7bb486e1c4b6bee4f528e", size = 4205457, upload-time = "2026-08-05T13:14:33.858Z" }, - { url = "https://files.pythonhosted.org/packages/8b/63/3964d86ed2f36f691374813751fb342de0c169ea0d926609379e50fd761c/nostr_sdk-0.45.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:78ccdb4766ae627ac9e2c10a7bb224d1e00da48fc7b61c7f48935ae23241fd64", size = 4410510, upload-time = "2026-08-05T13:14:35.224Z" }, - { url = "https://files.pythonhosted.org/packages/0d/15/a9c8da6d37bbbbfe07cba889a3d149f030985e524758ba51b44ca7a29ca0/nostr_sdk-0.45.0-cp39-abi3-win32.whl", hash = "sha256:45ff1ae2f19fedfa9f91b0c81c1be755b706e2fd1e7865ea46612c2ad1d98a3e", size = 3549588, upload-time = "2026-08-05T13:14:36.642Z" }, - { url = "https://files.pythonhosted.org/packages/99/f0/e03b7abb4dc016ea19dc2f311f367e472df47c187fbb69cee6df3ed2f323/nostr_sdk-0.45.0-cp39-abi3-win_amd64.whl", hash = "sha256:b3904abc4d7e4ac46ed66c8aa528e89748f3f25023708e4bbab58bc81d3a99f7", size = 3870530, upload-time = "2026-08-05T13:14:38.079Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d0/f80080bd2aa73ed25c7a9d8d21020fc73eaa10591b39e13ec2b88228a8ce/nostr_sdk-0.45.0-cp39-abi3-win_arm64.whl", hash = "sha256:5a0fc63b69995bf2dac44e479aaec1ce1af71e687c9f4258891e429ed3db25e5", size = 3704095, upload-time = "2026-08-05T13:14:39.494Z" }, + { url = "https://files.pythonhosted.org/packages/35/ea/cd40b12352a5cba810116641959a7054f83d5f48d16e5e47614992b9ba64/nostr_sdk-0.44.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:184075689531e34085bc1e71b62f3a964df2d4aeb15a25f94557e15a4294c584", size = 3360561, upload-time = "2026-01-29T10:56:37.285Z" }, + { url = "https://files.pythonhosted.org/packages/ed/66/81057ec283cb6bcc5df27c1d58a0a7996afa4fb96c0c9a4570ae50190f9a/nostr_sdk-0.44.2-cp39-abi3-macosx_11_0_x86_64.whl", hash = "sha256:65e85c79295f4e258e5276a82b52e36ffb3f5cf59c2c7c4a959970ed9303cf6f", size = 3475196, upload-time = "2026-01-29T10:56:39.296Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/567b67c832b6263817470885e92b059b7d19cd9967b83ef1f59b6e04e603/nostr_sdk-0.44.2-cp39-abi3-manylinux_2_17_aarch64.whl", hash = "sha256:d1d557aeb8a423dcd054f699bbf7371eb268a6cec5916bf147957cb3f9f7da02", size = 3613359, upload-time = "2026-01-29T10:56:41.079Z" }, + { url = "https://files.pythonhosted.org/packages/fc/93/cdf2cd98f8fbb045a1ca12f073ad747342961b0a3bbe9cd6a09760cb0132/nostr_sdk-0.44.2-cp39-abi3-manylinux_2_17_armv7l.whl", hash = "sha256:41b1bb050f890f81b4c4edaf4d6cc3054ae9e783911eb4ce7dce6b8e97dfa60e", size = 3364451, upload-time = "2026-01-29T10:56:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ec/7e9e9630af0b49e931cff8f65a5c9ce4086e9f2c372213d355b53871253c/nostr_sdk-0.44.2-cp39-abi3-manylinux_2_17_i686.whl", hash = "sha256:db584ba95de5abbb74da77036c3316e9a166bc22d9d2ea3ca38fc09325d9c6a9", size = 3603547, upload-time = "2026-01-29T10:56:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/6a/03/c7ed0230f84615e2930259788b1fc1c485b159093f2070125ae9ddae327e/nostr_sdk-0.44.2-cp39-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:4e1dccf29b7ad6eeb44945175a45334d4e45d82881ae5f3a28433899f155831c", size = 3726609, upload-time = "2026-01-29T10:56:45.709Z" }, + { url = "https://files.pythonhosted.org/packages/d1/8d/bfb2172743a99a21bbba0a4f77572be50cb8b10dbcf837b70be0e0b8f4b7/nostr_sdk-0.44.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:aa347c6437dc33ae45cc2ed56177f5aeded0fda42285b1b325a840de44e1f708", size = 3607268, upload-time = "2026-01-29T10:56:47.312Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/cb61a46d91bf61f15b628d188a2338b9e76e9fee4078728d4a7ded876381/nostr_sdk-0.44.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:a7a03b35ddbc69f31d79bea17ac60b8d250ef62bcf1d52fc33b9e36e0ecf772b", size = 3360529, upload-time = "2026-01-29T10:56:49.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/5405965ef11cc784be2dd473297caf997f8fa00b5f2e92884a029eae6eee/nostr_sdk-0.44.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:e53682dbf192acd137a92ae5d91903fbb22728d55452a8fa2315581f1d8413f2", size = 3494705, upload-time = "2026-01-29T10:56:51.158Z" }, + { url = "https://files.pythonhosted.org/packages/03/89/febbbec3d20cb63ab30eee84f59cb06317f24b8e2ab15c2d99212d71d622/nostr_sdk-0.44.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b3e6dd4191997546f157aba4532a995d06ad8b0cf9c17e77c0015553f06a9cf2", size = 3727113, upload-time = "2026-01-29T10:56:52.513Z" }, + { url = "https://files.pythonhosted.org/packages/67/d5/718b0428af9a3c9e33bd6a21b2b32920559db356e0369d0013b6421286d9/nostr_sdk-0.44.2-cp39-abi3-win32.whl", hash = "sha256:aa94a18eb8f4d77559c0290c0f17d6816c00f4fdddba5f1fcf96ff01b4690b42", size = 3164430, upload-time = "2026-01-29T10:56:54.334Z" }, + { url = "https://files.pythonhosted.org/packages/96/67/3f19800f6a52c2e1e7ba2b2b04ed0fca7a11a1819a2d02cdd961c4f4a54b/nostr_sdk-0.44.2-cp39-abi3-win_amd64.whl", hash = "sha256:31e609a864c3857cddde70d8a91181e2b2cadf923e562b6a9d82e655edded117", size = 3384420, upload-time = "2026-01-29T10:56:56.19Z" }, + { url = "https://files.pythonhosted.org/packages/71/bb/b2c2bb390fb309477bff63507c9d6058bea55d01f4938a749269dd5dd8bc/nostr_sdk-0.44.2-cp39-abi3-win_arm64.whl", hash = "sha256:3a9d3284af0547e224bf705d90cd6d9ca5a1b48f5becea1021da915de4408310", size = 3233797, upload-time = "2026-01-29T10:56:57.538Z" }, ] [[package]] name = "packaging" -version = "26.2" +version = "26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] [[package]] @@ -904,36 +895,36 @@ wheels = [ [[package]] name = "pyln-client" -version = "26.6.6" +version = "26.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyln-bolt7" }, { name = "pyln-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/5f/7e52fbe6b6d7d65651d2c908e1f68449256b3007a8259fa589282e9a4c0f/pyln_client-26.6.6.tar.gz", hash = "sha256:01c02021a62347d4d7dbaaa97bb627e9c2b60212a2e146107a782c1c156588ba", size = 92480, upload-time = "2026-07-20T12:39:51.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/14/0faf6b8b04f368629897a010357d3b8d21c8a800b972b433a358caebe32b/pyln_client-26.4.tar.gz", hash = "sha256:e696076fcf1329ee22198097b8139a096f653cbbfc6a03e471455ca6f5d68a46", size = 92160, upload-time = "2026-04-20T04:58:12.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/c0/2efddeec3dda9c915b02fc406bcc1f179d8c45cc9a2e8089608cbdde1a0e/pyln_client-26.6.6-py3-none-any.whl", hash = "sha256:6afa9019aef1e54fa01e185be160773ac4db2fdc7cf864a319e7374b71cd6a6d", size = 37975, upload-time = "2026-07-20T12:39:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/f67df5064b4b9d258fb30e96e1d3ca8f0395c7c0098b24091dde6238fb8e/pyln_client-26.4-py3-none-any.whl", hash = "sha256:d5470e1ecccc1b6e46e39658c1019c62868c5aed4a0203635052803ddedcce2e", size = 37655, upload-time = "2026-04-20T04:58:11.429Z" }, ] [[package]] name = "pyln-proto" -version = "26.6.6" +version = "26.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "base58" }, { name = "bitstring" }, - { name = "coincurve-cp314-fix" }, + { name = "coincurve" }, { name = "cryptography" }, { name = "pysocks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/97/219900f708391678165461e6b519c6272edb5966b2e20c62856e9c1f736a/pyln_proto-26.6.6.tar.gz", hash = "sha256:b6d424eb22d33de9fc21e4216502759e34a62eaf460a47cc45dbbb208096db8a", size = 44879, upload-time = "2026-07-20T12:40:07.68Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/35/5d30332fcaa8d77be87290c5534d45c48822310611ae6ea6d3e48c242ded/pyln_proto-26.4.tar.gz", hash = "sha256:dde9e243c7bf6fbcf7904e0dbd18c4f05dc17e668b074bfab090a7376e3de6cf", size = 44861, upload-time = "2026-04-20T04:58:09.361Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/18/ed5d323f55350b175e5f8bbed7b0c8acb988eb48ee37dd5b44d9e7a41799/pyln_proto-26.6.6-py3-none-any.whl", hash = "sha256:b7b12769e2c37e6d1ce7c2063bbab67f5959e411459ff089d0dbc14109778e03", size = 31833, upload-time = "2026-07-20T12:40:06.677Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c9/8a34960e1e9550f87697d1f05208d9cc916c14419f18f2d6013801079fc1/pyln_proto-26.4-py3-none-any.whl", hash = "sha256:97772d285aa0d0c8a1545577aeef8821e08d59676f63ae760d270e1b3015bc9d", size = 31811, upload-time = "2026-04-20T04:58:08.369Z" }, ] [[package]] name = "pyln-testing" -version = "26.6.6" +version = "26.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cheroot" }, @@ -948,9 +939,9 @@ dependencies = [ { name = "python-bitcoinlib" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/86/49b10e9317142f989dc03c7d42d512789a83000da7660177393a9d7e681b/pyln_testing-26.6.6.tar.gz", hash = "sha256:31caa503724d02bb736d8b1e11cd344cff15e0229bc6f709bd1571df5066b432", size = 53213, upload-time = "2026-07-20T12:39:37.337Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/7e/275f0416e30faa3093ad31703f3d8702dc06569164a185813b23672ba6d9/pyln_testing-26.4.tar.gz", hash = "sha256:cca1f4fec65f12bfda809674ea7c231285a96ef0b8bc2c69ef4919f1b465d2ae", size = 52609, upload-time = "2026-04-20T04:58:07.746Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/28/58c3524d0d1c1e244580bd82730f7c0e72b60e5a52b0d34c4395e7f46a1d/pyln_testing-26.6.6-py3-none-any.whl", hash = "sha256:24b814f6977faa1e66f98a6a447b397d148ba683c53a73e595a429c53a88af78", size = 54976, upload-time = "2026-07-20T12:39:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c4/3cb55bf86a2499510a24b310897903efafce847869eb1ec1c7b67b7fcfa4/pyln_testing-26.4-py3-none-any.whl", hash = "sha256:ccb7641bbbb1b6b650f1b8238aacf04d7ebeea944ebd2855a901e25d18846804", size = 54365, upload-time = "2026-04-20T04:58:06.793Z" }, ] [[package]] @@ -964,7 +955,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.1.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -975,23 +966,23 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.4.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] @@ -1034,8 +1025,7 @@ version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, - { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } @@ -1045,7 +1035,7 @@ wheels = [ [[package]] name = "requests" -version = "2.34.2" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1053,18 +1043,15 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, @@ -1183,132 +1170,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] -[[package]] -name = "rpds-py" -version = "2026.6.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, - { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, - { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, - { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, - { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, - { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, - { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, - { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, - { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, - { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, - { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, - { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, - { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, - { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, - { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, - { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, - { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, - { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, - { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, - { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, - { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, - { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, - { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, - { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, - { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, - { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, - { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, - { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, - { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, - { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, - { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, - { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, - { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, - { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, - { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, - { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, - { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, - { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, - { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, - { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, - { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, - { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, - { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, - { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, - { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, - { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, - { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, - { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, - { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, - { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, - { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, - { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, - { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, - { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, - { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, - { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, - { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, - { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, - { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, - { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, - { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, - { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, - { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, - { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, - { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, - { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, - { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, - { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, - { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, - { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, - { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, -] - [[package]] name = "tibs" version = "0.5.7" @@ -1403,20 +1264,20 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.16.0" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "urllib3" -version = "2.7.0" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]]