diff --git a/Dockerfile b/Dockerfile index fbbacd912..52be4b475 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,15 @@ FROM python:3.10.12-slim as builder -RUN apt-get update && \ - apt-get install -y gcc git -ADD requirements.txt ./ -RUN mkdir -p /install -# NOTE: We have to do this to force pyyaml to install -RUN pip3 install --prefix=/install "Cython<3.0" "pyyaml==5.4.1" --no-build-isolation -RUN pip3 install --prefix=/install -r requirements.txt - -FROM python:3.10.12-slim -COPY --from=builder /install /usr/local -RUN apt-get update -RUN apt-get install python3-tk -y +RUN apt-get update && apt-get install -y gcc git python3-tk RUN mkdir -p /app/yearn-exporter WORKDIR /app/yearn-exporter +RUN pip3 install poetry +ADD pyproject.toml ./ +ADD poetry.lock ./ +RUN poetry run pip install "cython<3.0.0" wheel +RUN poetry run pip install pyyaml==5.4.1 --no-build-isolation +RUN poetry install +# 0.5.1 is faster than 0.5.0 due to parsimonius 0.10 but won't install with brownie at the moment +#RUN poetry run pip3 install "eth-abi>=5.1,<6" ADD . /app/yearn-exporter ENTRYPOINT ["./entrypoint.sh"] diff --git a/Makefile b/Makefile index f23670c00..52df66172 100644 --- a/Makefile +++ b/Makefile @@ -280,6 +280,18 @@ apy-yeth-monitoring: apy-yeth: make up commands="yeth" network=eth filter=yeth + +aerodrome-apy-previews: + make up commands="drome_apy_previews" network=base + +aerodrome-apy-previews-monitoring: + make up commands="drome_apy_previews with_monitoring" network=base + +velodrome-apy-previews: + make up commands="drome_apy_previews" network=optimism + +velodrome-apy-previews-monitoring: + make up commands="drome_apy_previews with_monitoring" network=optimism aerodrome-apy-previews: make up commands="drome_apy_previews" network=base @@ -324,8 +336,8 @@ yeth: # utils fetch-memray: mkdir reports/memray -p - sudo cp -r /var/lib/docker/volumes/yearn-exporter-worker-ethereum_memray/_data/ reports/memray/ethereum - sudo cp -r /var/lib/docker/volumes/yearn-exporter-worker-fantom_memray/_data/ reports/memray/fantom - sudo cp -r /var/lib/docker/volumes/yearn-exporter-worker-arbitrum_memray/_data/ reports/memray/arbitrum - sudo cp -r /var/lib/docker/volumes/yearn-exporter-worker-optimism_memray/_data/ reports/memray/optimism - sudo cp -r /var/lib/docker/volumes/yearn-exporter-worker-gnosis_memray/_data/ reports/memray/gnosis + sudo cp -r /raid/docker/volumes/yearn-exporter-worker-ethereum_memray/_data/ reports/memray/ethereum + sudo cp -r /raid/docker/volumes/yearn-exporter-worker-fantom_memray/_data/ reports/memray/fantom + sudo cp -r /raid/docker/volumes/yearn-exporter-worker-arbitrum_memray/_data/ reports/memray/arbitrum + sudo cp -r /raid/docker/volumes/yearn-exporter-worker-optimism_memray/_data/ reports/memray/optimism + sudo cp -r /raid/docker/volumes/yearn-exporter-worker-gnosis_memray/_data/ reports/memray/gnosis diff --git a/brownie-config.yaml b/brownie-config.yaml index 3634532ce..ae7d45701 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -2,6 +2,7 @@ networks: default: mainnet autofetch_sources: false +eager_caching: false compiler: evm_version: london diff --git a/brownie_init.sh b/brownie_init.sh index fb8e0aa44..5baf485e7 100755 --- a/brownie_init.sh +++ b/brownie_init.sh @@ -5,11 +5,11 @@ BROWNIE_NETWORK=${BROWNIE_NETWORK:-mainnet} # default to Ethereum mainnet EXPLORER=${EXPLORER:-$DEFAULT_EXPLORER} # add Base to brownie's network list -if ! brownie networks list | grep Base > /dev/null; then - brownie networks add Base base-main host=https://base.meowrpc.com chainid=8453 explorer=https://api.basescan.org/api || true +if ! poetry run brownie networks list | grep Base > /dev/null; then + poetry run brownie networks add Base base-main host=https://base.meowrpc.com chainid=8453 explorer=https://api.basescan.org/api || true fi # modify the network if [[ ! -z "$WEB3_PROVIDER" ]]; then - brownie networks modify $BROWNIE_NETWORK host=$WEB3_PROVIDER explorer=$EXPLORER + poetry run brownie networks modify $BROWNIE_NETWORK host=$WEB3_PROVIDER explorer=$EXPLORER fi diff --git a/entrypoint.sh b/entrypoint.sh index 5d09780e5..964134937 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -9,10 +9,10 @@ fi echo "Running brownie for $@ on network $BROWNIE_NETWORK..." # Normal Mods -brownie run $@ --network $BROWNIE_NETWORK -r +poetry run brownie run $@ --network $BROWNIE_NETWORK -r # Memray Live Mode -#memray run --trace-python-allocators --live-remote --live-port=9999 /usr/local/bin/brownie run $@ --network $BROWNIE_NETWORK -r +#poetry run memray run --trace-python-allocators --live-remote --live-port=9999 /usr/local/bin/brownie run $@ --network $BROWNIE_NETWORK -r # Memray Export Mode -#memray run --trace-python-allocators --output /app/yearn-exporter/memray/$BROWNIE_NETWORK/$@.bin --force /usr/local/bin/brownie run $@ --network $BROWNIE_NETWORK -r \ No newline at end of file +#poetry run memray run --trace-python-allocators --output /app/yearn-exporter/memray/$BROWNIE_NETWORK/$@.bin --force /usr/local/bin/brownie run $@ --network $BROWNIE_NETWORK -r \ No newline at end of file diff --git a/loader.py b/loader.py new file mode 100644 index 000000000..3bf917b0b --- /dev/null +++ b/loader.py @@ -0,0 +1,27 @@ + +import asyncio +import hashlib +import os +import time +from types import ModuleType + +import aiofiles +from async_lru import alru_cache + + +@alru_cache(maxsize=None) +async def hash_module(module: ModuleType) -> bytes: + is_directory = hasattr(module, '__path__') + if not is_directory: + return hashlib.sha256(await read_file(module.__file__)).digest() + contents = await asyncio.gather(*[ + read_file(f'{dir}/{file}') + for path in module.__path__ + for dir, dirs, files in os.walk(path) + for file in files + ]) + return hashlib.sha256(b"".join(contents)).digest() + +async def read_file(file_path: str) -> bytes: + async with aiofiles.open(file_path, mode='rb') as handle: + return await handle.read() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..bf466ea36 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,4683 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "24.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] + +[[package]] +name = "aiohttp" +version = "3.9.3" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiolimiter" +version = "1.0.0" +description = "asyncio rate limiter, a leaky bucket implementation" +optional = false +python-versions = ">=3.6.1,<4.0.0" +files = [ + {file = "aiolimiter-1.0.0-py3-none-any.whl", hash = "sha256:f1c5ba2a2861cd4a126c1294f5282208383e67d5b128a4f32def0c702cae8039"}, + {file = "aiolimiter-1.0.0.tar.gz", hash = "sha256:9d40767e4476048145dfa9f61948445168d6e63cf42c95785a20b9aaff2e4564"}, +] + +[package.extras] +docs = ["aiohttp-theme (>=0.1.6,<0.2.0)", "sphinx (>=2.2.1,<5.0.0)", "sphinx-autodoc-typehints (>=1.10.3,<2.0.0)", "sphinxcontrib-spelling (>=4.3,<8.0)", "toml (>=0.10.0,<0.11.0)"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.6.2.post1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +files = [ + {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, + {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "apscheduler" +version = "3.6.3" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = "*" +files = [ + {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, + {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, +] + +[package.dependencies] +pytz = "*" +setuptools = ">=0.7" +six = ">=1.4.0" +tzlocal = ">=1.2" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=2.8)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["mock", "pytest", "pytest-asyncio", "pytest-asyncio (<0.6)", "pytest-cov", "pytest-tornado5"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "async-lru-threadsafe" +version = "2.0.4" +description = "Simple threadsafe LRU cache for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async-lru-threadsafe-2.0.4.tar.gz", hash = "sha256:ee6d6ced06e474fa1fabc3a757e50148184bb2c4ce0812e294c68112d1b7ea00"}, + {file = "async_lru_threadsafe-2.0.4-py3-none-any.whl", hash = "sha256:6fef5a5545d1f254738c68db8899a6d39b4e47837ba45ae2291856f69a533a84"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "async-property" +version = "0.2.1" +description = "Python decorator for async properties." +optional = false +python-versions = "*" +files = [ + {file = "async_property-0.2.1-py2.py3-none-any.whl", hash = "sha256:f1f105009a6216ed9a13031aa13632754ed8a5c2e329fb8f9f2082d83529eacd"}, + {file = "async_property-0.2.1.tar.gz", hash = "sha256:53826fd45a67d7d6cca3d7abbc0e8ba951f7c7618c830021fbd3675979b0b67d"}, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "bitarray" +version = "2.9.2" +description = "efficient arrays of booleans -- C extension" +optional = false +python-versions = "*" +files = [ + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, +] + +[[package]] +name = "black" +version = "24.2.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, + {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, + {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, + {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, + {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, + {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, + {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, + {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, + {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, + {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, + {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, + {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, + {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, + {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, + {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, + {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, + {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, + {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, + {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, + {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, + {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, + {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "bobs-lazy-logging" +version = "0.0.4" +description = "Debug logging for lazy people" +optional = false +python-versions = "*" +files = [ + {file = "bobs_lazy_logging-0.0.4-py3-none-any.whl", hash = "sha256:b6e83b2c2a597d74dab804174b494f31ffa8e802a6d6fe50f5b7fa6beab61ed2"}, + {file = "bobs_lazy_logging-0.0.4.tar.gz", hash = "sha256:27f7c3efc6e7735501d0db3bd2c44dab910c1c6591311748cb6ecfe4c6086b01"}, +] + +[[package]] +name = "boto3" +version = "1.34.162" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "boto3-1.34.162-py3-none-any.whl", hash = "sha256:d6f6096bdab35a0c0deff469563b87d184a28df7689790f7fe7be98502b7c590"}, + {file = "boto3-1.34.162.tar.gz", hash = "sha256:873f8f5d2f6f85f1018cbb0535b03cceddc7b655b61f66a0a56995238804f41f"}, +] + +[package.dependencies] +botocore = ">=1.34.162,<1.35.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.10.0,<0.11.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.34.162" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.8" +files = [ + {file = "botocore-1.34.162-py3-none-any.whl", hash = "sha256:2d918b02db88d27a75b48275e6fb2506e9adaaddbec1ffa6a8a0898b34e769be"}, + {file = "botocore-1.34.162.tar.gz", hash = "sha256:adc23be4fb99ad31961236342b7cbf3c0bfc62532cd02852196032e8c0d682f3"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.21.2)"] + +[[package]] +name = "cachetools" +version = "4.2.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = "~=3.5" +files = [ + {file = "cachetools-4.2.2-py3-none-any.whl", hash = "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001"}, + {file = "cachetools-4.2.2.tar.gz", hash = "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"}, +] + +[[package]] +name = "cbor2" +version = "5.6.2" +description = "CBOR (de)serializer with extensive tag support" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cbor2-5.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:516b8390936bb172ff18d7b609a452eaa51991513628949b0a9bf25cbe5a7129"}, + {file = "cbor2-5.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1b8b504b590367a51fe8c0d9b8cb458a614d782d37b24483097e2b1e93ed0fff"}, + {file = "cbor2-5.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f687e6731b1198811223576800258a712ddbfdcfa86c0aee2cc8269193e6b96"}, + {file = "cbor2-5.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e94043d99fe779f62a15a5e156768588a2a7047bb3a127fa312ac1135ff5ecb"}, + {file = "cbor2-5.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8af7162fcf7aa2649f02563bdb18b2fa6478b751eee4df0257bffe19ea8f107a"}, + {file = "cbor2-5.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ea7ecd81c5c6e02c2635973f52a0dd1e19c0bf5ef51f813d8cd5e3e7ed072726"}, + {file = "cbor2-5.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3c7f223f1fedc74d33f363d184cb2bab9e4bdf24998f73b5e3bef366d6c41628"}, + {file = "cbor2-5.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ea9e150029c3976c46ee9870b6dcdb0a5baae21008fe3290564886b11aa2b64"}, + {file = "cbor2-5.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:922e06710e5cf6f56b82b0b90d2f356aa229b99e570994534206985f675fd307"}, + {file = "cbor2-5.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b01a718e083e6de8b43296c3ccdb3aa8af6641f6bbb3ea1700427c6af73db28a"}, + {file = "cbor2-5.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac85eb731c524d148f608b9bdb2069fa79e374a10ed5d10a2405eba9a6561e60"}, + {file = "cbor2-5.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03e5b68867b9d89ff2abd14ef7c6d42fbd991adc3e734a19a294935f22a4d05a"}, + {file = "cbor2-5.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7221b83000ee01d674572eec1d1caa366eac109d1d32c14d7af9a4aaaf496563"}, + {file = "cbor2-5.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:9aca73b63bdc6561e1a0d38618e78b9c204c942260d51e663c92c4ba6c961684"}, + {file = "cbor2-5.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:377cfe9d5560c682486faef6d856226abf8b2801d95fa29d4e5d75b1615eb091"}, + {file = "cbor2-5.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fdc564ef2e9228bcd96ec8c6cdaa431a48ab03b3fb8326ead4b3f986330e5b9e"}, + {file = "cbor2-5.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d1c0021d9a1f673066de7c8941f71a59abb11909cc355892dda01e79a2b3045"}, + {file = "cbor2-5.6.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fde9e704e96751e0729cc58b912d0e77c34387fb6bcceea0817069e8683df45"}, + {file = "cbor2-5.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:30e9ba8f4896726ca61869efacda50b6859aff92162ae5a0e192859664f36c81"}, + {file = "cbor2-5.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a1e18e65ac71e04434ff5b58bde5c53f85b9c5bc92a3c0e2265089d3034f3"}, + {file = "cbor2-5.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:94981277b4bf448a2754c1f34a9d0055a9d1c5a8d102c933ffe95c80f1085bae"}, + {file = "cbor2-5.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f70db0ebcf005c25408e8d5cc4b9558c899f13a3e2f8281fa3d3be4894e0e821"}, + {file = "cbor2-5.6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:22c24fe9ef1696a84b8fd80ff66eb0e5234505d8b9a9711fc6db57bce10771f3"}, + {file = "cbor2-5.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a4a3420f80d6b942874d66eaad07658066370df994ddee4125b48b2cbc61ece"}, + {file = "cbor2-5.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b28d8ff0e726224a7429281700c28afe0e665f83f9ae79648cbae3f1a391cbf"}, + {file = "cbor2-5.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c10ede9462458998f1b9c488e25fe3763aa2491119b7af472b72bf538d789e24"}, + {file = "cbor2-5.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ea686dfb5e54d690e704ce04993bc8ca0052a7cd2d4b13dd333a41cca8a05a05"}, + {file = "cbor2-5.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:22996159b491d545ecfd489392d3c71e5d0afb9a202dfc0edc8b2cf413a58326"}, + {file = "cbor2-5.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9faa0712d414a88cc1244c78cd4b28fced44f1827dbd8c1649e3c40588aa670f"}, + {file = "cbor2-5.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6031a284d93fc953fc2a2918f261c4f5100905bd064ca3b46961643e7312a828"}, + {file = "cbor2-5.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30c8a9a9df79f26e72d8d5fa51ef08eb250d9869a711bcf9539f1865916c983"}, + {file = "cbor2-5.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44bf7457fca23209e14dab8181dff82466a83b72e55b444dbbfe90fa67659492"}, + {file = "cbor2-5.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc29c068687aa2e7778f63b653f1346065b858427a2555df4dc2191f4a0de8ce"}, + {file = "cbor2-5.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42eaf0f768bd27afcb38135d5bfc361d3a157f1f5c7dddcd8d391f7fa43d9de8"}, + {file = "cbor2-5.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:8839b73befa010358477736680657b9d08c1ed935fd973decb1909712a41afdc"}, + {file = "cbor2-5.6.2-py3-none-any.whl", hash = "sha256:c0b53a65673550fde483724ff683753f49462d392d45d7b6576364b39e76e54c"}, + {file = "cbor2-5.6.2.tar.gz", hash = "sha256:b7513c2dea8868991fad7ef8899890ebcf8b199b9b4461c3c11d7ad3aef4820d"}, +] + +[package.extras] +benchmarks = ["pytest-benchmark (==4.0.0)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions"] +test = ["coverage (>=7)", "hypothesis", "pytest"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "checksum-dict" +version = "1.1.4" +description = "checksum_dict's objects handle the simple but repetitive task of checksumming addresses before setting/getting dictionary values." +optional = false +python-versions = "*" +files = [ + {file = "checksum_dict-1.1.4-py3-none-any.whl", hash = "sha256:8469211006e7ece547e3f474ef6253fdea3d95f453bc5c55a75f9bb0a91284ee"}, + {file = "checksum_dict-1.1.4.tar.gz", hash = "sha256:67d03c44de60e9654276ab7d9d2a50697af32d3a787819d1edaa199cbaed0065"}, +] + +[package.dependencies] +eth-typing = "*" +eth-utils = "*" + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "contourpy" +version = "1.3.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "cytoolz" +version = "0.12.3" +description = "Cython implementation of Toolz: High performance functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + +[[package]] +name = "dank-mids" +version = "4.20.98" +description = "Multicall batching middleware for asynchronous scripts using web3.py" +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "dank_mids-4.20.98-py3-none-any.whl", hash = "sha256:13d8f012caaf4129b448c75e119f3ec2b0e8f1bf6dc7f1d66c4075a65ed4c9b4"}, + {file = "dank_mids-4.20.98.tar.gz", hash = "sha256:be15d2d1d1282b284fc85dd5489ba8bd1f920432d39214d517285fcfe8b7a190"}, +] + +[package.dependencies] +aiofiles = "*" +eth-retry = ">=0.1.15,<0.2.0" +evmspec = ">=0.0.1,<0.1" +ez-a-sync = ">=0.20.7,<0.24" +multicall = ">=0.6.2,<1" +typed-envs = ">=0.0.2,<0.0.3" +web3 = ">=5.27,<5.29.dev0 || ==5.31.0.* || >5.31.1,<5.31.2 || >5.31.2,<8" + +[[package]] +name = "dataclassy" +version = "0.11.1" +description = "A fast and flexible reimplementation of data classes" +optional = false +python-versions = ">=3.6" +files = [ + {file = "dataclassy-0.11.1-py3-none-any.whl", hash = "sha256:bcb030d3d700cf9b1597042bbc8375b92773e6f68f65675a7071862c0ddb87f5"}, + {file = "dataclassy-0.11.1.tar.gz", hash = "sha256:ad6622cb91e644d13f68768558983fbc22c90a8ff7e355638485d18b9baf1198"}, +] + +[[package]] +name = "dictstruct" +version = "0.0.1" +description = "A msgspec.Struct implementation compatible with the standard dictionary API" +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "dictstruct-0.0.1-py3-none-any.whl", hash = "sha256:efa89f4cf077f0b8e7eec325dd833aaa577d92d95d8a9fca18f08cea40bc3cfa"}, + {file = "dictstruct-0.0.1.tar.gz", hash = "sha256:423fae56834bb02b6eb071b474de93c386e310331a8baa7e6adee983a4d5e37b"}, +] + +[package.dependencies] +msgspec = ">=0.18.5" + +[[package]] +name = "eip712" +version = "0.2.4" +description = "eip712: Message classes for typed structured data hashing and signing in Ethereum" +optional = false +python-versions = ">=3.8,<4" +files = [ + {file = "eip712-0.2.4-py3-none-any.whl", hash = "sha256:19d9abdb4b0ffb97f12a68addc2bbcdb2f0a2da17bc038a22c77b42353de1ecd"}, + {file = "eip712-0.2.4.tar.gz", hash = "sha256:e69760414523f60328279620776549a17ff72f89974688710d3467ae08717070"}, +] + +[package.dependencies] +dataclassy = ">=0.8.2,<1" +eth-abi = ">=4.2.1,<6" +eth-account = ">=0.10.0,<0.11" +eth-hash = {version = "*", extras = ["pycryptodome"]} +eth-typing = ">=3.5.2,<4" +eth-utils = ">=2.3.1,<3" +hexbytes = ">=0.3.0,<1" + +[package.extras] +dev = ["IPython", "Sphinx (>=5.3.0,<6)", "black (>=23.12.0,<24)", "commitizen (>=2.42,<3)", "flake8 (>=6.1.0,<7)", "hypothesis (>=6.70.0,<7)", "ipdb", "isort (>=5.12.0,<6)", "mdformat (>=0.7.17,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.7.1,<2)", "myst-parser (>=0.18.1,<0.19)", "pre-commit", "pytest (>=6.0,<8)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-setuptools", "wheel"] +doc = ["Sphinx (>=5.3.0,<6)", "myst-parser (>=0.18.1,<0.19)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] +lint = ["black (>=23.12.0,<24)", "flake8 (>=6.1.0,<7)", "isort (>=5.12.0,<6)", "mdformat (>=0.7.17,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.7.1,<2)", "types-setuptools"] +release = ["setuptools", "twine", "wheel"] +test = ["hypothesis (>=6.70.0,<7)", "pytest (>=6.0,<8)", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "eth-abi" +version = "5.0.0" +description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth_abi-5.0.0-py3-none-any.whl", hash = "sha256:936a715d7366ac13cac665cbdaf01cc4aabbe8c2d810d716287a9634f9665e01"}, + {file = "eth_abi-5.0.0.tar.gz", hash = "sha256:89c4454d794d9ed92ad5cb2794698c5cee6b7b3ca6009187d0e282adc7f9b6dc"}, +] + +[package.dependencies] +eth-typing = ">=3.0.0" +eth-utils = ">=2.0.0" +parsimonious = ">=0.9.0,<0.10.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)"] +tools = ["hypothesis (>=4.18.2,<5.0.0)"] + +[[package]] +name = "eth-account" +version = "0.10.0" +description = "eth-account: Sign Ethereum transactions and messages with local private keys" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "eth-account-0.10.0.tar.gz", hash = "sha256:474a2fccf7286230cf66502565f03b536921d7e1fdfceba198e42160e5ac4bc1"}, + {file = "eth_account-0.10.0-py3-none-any.whl", hash = "sha256:b7a83f506a8edf57926569e5f04471ce3f1700e572d3421b4ad0dad7a26c0978"}, +] + +[package.dependencies] +bitarray = ">=2.4.0" +eth-abi = ">=4.0.0-b.2" +eth-keyfile = ">=0.6.0" +eth-keys = ">=0.4.0" +eth-rlp = ">=0.3.0" +eth-utils = ">=2.0.0" +hexbytes = ">=0.1.0,<0.4.0" +rlp = ">=1.0.0" + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coverage", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=4.18.0,<5)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-brownie" +version = "1.20.6" +description = "A Python framework for Ethereum smart contract deployment, testing and interaction." +optional = false +python-versions = "<4,>=3.10" +files = [ + {file = "eth_brownie-1.20.6-py3-none-any.whl", hash = "sha256:dffffcb29cef4a9c5a2e8a0c6ea57ef6a10a0952687cf49e61860bbe83140f33"}, + {file = "eth_brownie-1.20.6.tar.gz", hash = "sha256:9705e2575b7cc988130956228a98c237e1ba2c682d4bc722b7097f0129b721d2"}, +] + +[package.dependencies] +aiohttp = "3.9.3" +aiosignal = "1.3.1" +asttokens = "2.4.1" +attrs = "23.2.0" +bitarray = "2.9.2" +black = "24.2.0" +cbor2 = "5.6.2" +certifi = "2024.2.2" +charset-normalizer = "3.3.2" +click = "8.1.7" +cytoolz = "0.12.3" +dataclassy = "0.11.1" +eip712 = "0.2.4" +eth-abi = "5.0.0" +eth-account = "0.10.0" +eth-event = "1.2.5" +eth-hash = {version = "0.6.0", extras = ["pycryptodome"]} +eth-keyfile = "0.7.0" +eth-keys = "0.5.0" +eth-rlp = "1.0.1" +eth-typing = "3.5.2" +eth-utils = "2.3.1" +execnet = "2.0.2" +frozenlist = "1.4.1" +hexbytes = "0.3.1" +hypothesis = "6.27.3" +idna = "3.6" +importlib-metadata = "7.0.1" +iniconfig = "2.0.0" +jsonschema = "4.21.1" +jsonschema-specifications = "2023.12.1" +lazy-object-proxy = "1.10.0" +lru-dict = "1.2.0" +multidict = "6.0.5" +mypy-extensions = "1.0.0" +packaging = "23.2" +parsimonious = "0.9.0" +pathspec = "0.12.1" +platformdirs = "4.2.0" +pluggy = "1.4.0" +prompt-toolkit = "3.0.43" +protobuf = "4.25.3" +psutil = "5.9.8" +py = "1.11.0" +py-solc-ast = "1.2.10" +py-solc-x = "1.1.1" +pycryptodome = "3.20.0" +pygments = "2.17.2" +pygments-lexer-solidity = "0.7.0" +pytest = "6.2.5" +pytest-forked = "1.6.0" +pytest-xdist = "1.34.0" +python-dotenv = "0.16.0" +pyunormalize = "15.1.0" +pyyaml = "6.0.1" +referencing = "0.33.0" +regex = "2023.12.25" +requests = "2.31.0" +rlp = "4.0.0" +rpds-py = "0.18.0" +semantic-version = "2.10.0" +six = "1.16.0" +sortedcontainers = "2.4.0" +toml = "0.10.2" +toolz = "0.12.1" +tqdm = "4.66.2" +typing-extensions = "4.9.0" +urllib3 = "2.2.1" +vvm = "0.1.0" +vyper = "0.3.10" +wcwidth = "0.2.13" +web3 = "6.15.1" +websockets = "12.0" +wheel = "0.42.0" +wrapt = "1.16.0" +yarl = "1.9.4" +zipp = "3.17.0" + +[[package]] +name = "eth-event" +version = "1.2.5" +description = "Ethereum event decoder and topic generator" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "eth-event-1.2.5.tar.gz", hash = "sha256:b733447fbdefd35a3f3deeddb78eae9362246bcbfb19075952cc592803b29586"}, + {file = "eth_event-1.2.5-py3-none-any.whl", hash = "sha256:4572adf97d301b72061684ef0b252e659a9968d29705297a25eadc2784e636ac"}, +] + +[package.dependencies] +eth-abi = ">=4,<6" +eth-hash = {version = ">=0.2.0,<1.0.0", extras = ["pycryptodome"]} +eth-utils = ">=2,<4" +hexbytes = "<1" + +[[package]] +name = "eth-hash" +version = "0.6.0" +description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-hash-0.6.0.tar.gz", hash = "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478"}, + {file = "eth_hash-0.6.0-py3-none-any.whl", hash = "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3"}, +] + +[package.dependencies] +pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} +safe-pysha3 = {version = ">=1.0.0", optional = true, markers = "python_version >= \"3.9\" and extra == \"pysha3\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +pycryptodome = ["pycryptodome (>=3.6.6,<4)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keyfile" +version = "0.7.0" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-keyfile-0.7.0.tar.gz", hash = "sha256:6bdb8110c3a50439deb68a04c93c9d5ddd5402353bfae1bf4cfca1d6dff14fcf"}, + {file = "eth_keyfile-0.7.0-py3-none-any.whl", hash = "sha256:6a89b231a2fe250c3a8f924f2695bb9cce33ddd0d6f7ebbcdacd183d7f83d537"}, +] + +[package.dependencies] +eth-keys = ">=0.4.0" +eth-utils = ">=2" +pycryptodome = ">=3.6.6,<4" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keys" +version = "0.5.0" +description = "eth-keys: Common API for Ethereum key operations" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-keys-0.5.0.tar.gz", hash = "sha256:a0abccb83f3d84322591a2c047a1e3aa52ea86b185fa3e82ce311d120ca2791e"}, + {file = "eth_keys-0.5.0-py3-none-any.whl", hash = "sha256:b2bed3ff3bcede68cc0cd4458c7147baaeaac1211a1efdb6ca019f9d3d989f2b"}, +] + +[package.dependencies] +eth-typing = ">=3" +eth-utils = ">=2" + +[package.extras] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3,<6)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3,<6)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] + +[[package]] +name = "eth-portfolio" +version = "0.0.1.dev511" +description = "eth-portfolio makes it easy to analyze your portfolio." +optional = false +python-versions = "*" +files = [] +develop = false + +[package.dependencies] +checksum_dict = ">=1.1.2" +dank_mids = ">=4.20.97" +eth-brownie = ">=1.19.3,<1.21" +eth_retry = ">=0.1.15,<1" +ez-a-sync = ">=0.22.9" +numpy = "<2" +pandas = ">=1.4.3,<1.6" +ypricemagic = ">=4.0.14,<4.1" + +[package.source] +type = "git" +url = "https://www.github.com/BobTheBuidler/eth-portfolio" +reference = "master" +resolved_reference = "40ab4cb388c33a56523535e19e2af4bfb8ea6f6d" + +[[package]] +name = "eth-retry" +version = "0.1.20" +description = "Provides a decorator that automatically catches known transient exceptions that are common in the Ethereum/EVM ecosystem and reattempts to evaluate your decorated function" +optional = false +python-versions = "*" +files = [ + {file = "eth_retry-0.1.20-py3-none-any.whl", hash = "sha256:7012bef06b1ec58012167f7242b5f3b9e08c4d4b566cb117f4e7f972fc4d5f1c"}, + {file = "eth_retry-0.1.20.tar.gz", hash = "sha256:492e9a143081e998f828512feb3ba741c300e0b1acbcbd923c85d38c92e16693"}, +] + +[package.dependencies] +typing-extensions = ">=4.0.1" + +[[package]] +name = "eth-rlp" +version = "1.0.1" +description = "eth-rlp: RLP definitions for common Ethereum objects in Python" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-rlp-1.0.1.tar.gz", hash = "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027"}, + {file = "eth_rlp-1.0.1-py3-none-any.whl", hash = "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517"}, +] + +[package.dependencies] +eth-utils = ">=2.0.0" +hexbytes = ">=0.1.0,<1" +rlp = ">=0.6.0" +typing-extensions = {version = ">=4.0.1", markers = "python_version <= \"3.11\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-typing" +version = "3.5.2" +description = "eth-typing: Common type annotations for ethereum python packages" +optional = false +python-versions = ">=3.7.2, <4" +files = [ + {file = "eth-typing-3.5.2.tar.gz", hash = "sha256:22bf051ddfaa35ff827c30090de167e5c5b8cc6d343f7f35c9b1c7553f6ab64d"}, + {file = "eth_typing-3.5.2-py3-none-any.whl", hash = "sha256:1842e628fb1ffa929b94f89a9d33caafbeb9978dc96abb6036a12bc91f1c624b"}, +] + +[package.dependencies] +typing-extensions = ">=4.0.1" + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] +docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "types-setuptools"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-utils" +version = "2.3.1" +description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "eth-utils-2.3.1.tar.gz", hash = "sha256:56a969b0536d4969dcb27e580521de35abf2dbed8b1bf072b5c80770c4324e27"}, + {file = "eth_utils-2.3.1-py3-none-any.whl", hash = "sha256:614eedc5ffcaf4e6708ca39e23b12bd69526a312068c1170c773bd1307d13972"}, +] + +[package.dependencies] +cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} +eth-hash = ">=0.3.1" +eth-typing = ">=3.0.0" +toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==3.8.3)", "hypothesis (>=4.43.0)", "ipython", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] +docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] +test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] + +[[package]] +name = "evmspec" +version = "0.0.2" +description = "A collection of msgspec.Struct definitions for use with the Ethereum Virtual Machine" +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "evmspec-0.0.2-py3-none-any.whl", hash = "sha256:281632baeae5379071e2cba4e46ea36049bee48ffda898e440b1c5c38b026c1f"}, + {file = "evmspec-0.0.2.tar.gz", hash = "sha256:c71f8e8cbc6648b0d03638a8515a49b8bde660be381ead18e28d2a4ecc23e4ae"}, +] + +[package.dependencies] +cachetools = ">=4.1.1,<6" +dictstruct = ">=0.0.1" +eth-utils = ">=1.10.0" +hexbytes = "*" +typing_extensions = ">=4.0.0" + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.0.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.7" +files = [ + {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, + {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "ez-a-sync" +version = "0.22.14" +description = "A library that makes it easy to define objects that can be used for both sync and async use cases." +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "ez_a_sync-0.22.14-py3-none-any.whl", hash = "sha256:4eaef0dca7950c1e5234d69f70f31e06e62eb5050aa3dbb5d5571ea4e1f7a183"}, + {file = "ez_a_sync-0.22.14.tar.gz", hash = "sha256:6b5c23e280af2fc7e5c5696ea263f76d2487d2bc0d4c9e12542e9bf4366f6335"}, +] + +[package.dependencies] +aiolimiter = "1.0.0" +async-lru-threadsafe = "2.0.4" +async-property = "0.2.1" +typed-envs = ">=0.0.2" +typing-extensions = ">=4.1.0" + +[[package]] +name = "fastapi" +version = "0.115.4" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"}, + {file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.42.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fonttools" +version = "4.54.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, + {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, + {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, + {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, + {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, + {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, + {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, + {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, + {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, + {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, + {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, + {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, + {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, + {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, + {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, + {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, + {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "hexbytes" +version = "0.3.1" +description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "hexbytes-0.3.1-py3-none-any.whl", hash = "sha256:383595ad75026cf00abd570f44b368c6cdac0c6becfae5c39ff88829877f8a59"}, + {file = "hexbytes-0.3.1.tar.gz", hash = "sha256:a3fe35c6831ee8fafd048c4c086b986075fc14fd46258fa24ecb8d65745f9a9d"}, +] + +[package.extras] +dev = ["black (>=22)", "bumpversion (>=0.5.3)", "eth-utils (>=1.0.1,<3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=22)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)"] +test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "hypothesis" +version = "6.27.3" +description = "A library for property-based testing" +optional = false +python-versions = ">=3.6" +files = [ + {file = "hypothesis-6.27.3-py3-none-any.whl", hash = "sha256:1c4568f40ca893c884330a1de0e0e5dcb1e867c60a56f414cb7bce97afc8dfec"}, + {file = "hypothesis-6.27.3.tar.gz", hash = "sha256:587da483bcc324494cec09cbbde3396c00da280c1732e387d7b5b89eff1aaff3"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "importlib-resources (>=3.3.0)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2020.4)"] +cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=2.2)", "pytz (>=2014.1)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark-parser (>=0.6.5)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=0.25)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "importlib-resources (>=3.3.0)", "tzdata (>=2020.4)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.0.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "inflection" +version = "0.5.1" +description = "A port of Ruby on Rails inflector to Python" +optional = false +python-versions = ">=3.5" +files = [ + {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, + {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + +[[package]] +name = "jsonschema" +version = "4.21.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "kiwisolver" +version = "1.4.7" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.8" +files = [ + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +] + +[[package]] +name = "linkify-it-py" +version = "2.0.3" +description = "Links recognition library with FULL unicode support." +optional = false +python-versions = ">=3.7" +files = [ + {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, + {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, +] + +[package.dependencies] +uc-micro-py = "*" + +[package.extras] +benchmark = ["pytest", "pytest-benchmark"] +dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] +doc = ["myst-parser", "sphinx", "sphinx-book-theme"] +test = ["coverage", "pytest", "pytest-cov"] + +[[package]] +name = "lru-dict" +version = "1.2.0" +description = "An Dict like LRU container." +optional = false +python-versions = "*" +files = [ + {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, + {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, + {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, + {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, + {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, + {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, + {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, + {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, + {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, + {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, + {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, + {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, + {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, + {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, + {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "mako" +version = "1.3.6" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.6-py3-none-any.whl", hash = "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a"}, + {file = "mako-1.3.6.tar.gz", hash = "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""} +mdit-py-plugins = {version = "*", optional = true, markers = "extra == \"plugins\""} +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "matplotlib" +version = "3.9.2" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +description = "Collection of plugins for markdown-it-py" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, + {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, +] + +[package.dependencies] +markdown-it-py = ">=1.0.0,<4.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["myst-parser", "sphinx-book-theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "memray" +version = "1.14.0" +description = "A memory profiler for Python applications" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "memray-1.14.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:745d9014cb662065501441a7b534c29914fe2b68398b37385aba9f4a1c51c723"}, + {file = "memray-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f62a402ca1a7126f749544c3d6493672d6330ffd37d59ba230bc73e5143b3bc2"}, + {file = "memray-1.14.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:36840f39277b1871ecb5a9592dd1aa517a17b9f855f4e3ff08aa328a9d305e69"}, + {file = "memray-1.14.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c7933ca70c0d59d0ce9b1064a6eda86231248759b46ed6dabedf489039d1aa1"}, + {file = "memray-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a5907345ff845652e709ddce3171a9ba2d65c62e8bd49a99131066e2a7ce3b"}, + {file = "memray-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:88c89c3797834eec177a89ad528699c75b94e2ed08c00754141eae69c520b894"}, + {file = "memray-1.14.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:d6087f291fd68acdf0a833efb57bc0f192c98ae89b4377c690c28313e78d029c"}, + {file = "memray-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6ba7bff9dfa37bf3b80a5b83b50eadf20afb1f0e8de4a0139019154086d6bed"}, + {file = "memray-1.14.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9bb0cfe1b755a860435cd52047b2e3f4f7b0c3887e0c1bf98da7127948284a91"}, + {file = "memray-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:638ba74e1735a40b6595fee7f37b426b9a95d244091a1f5df3dc5d98df1cbd4b"}, + {file = "memray-1.14.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7227ebf024cb0688a68ed91ed3e05c61a13751a9e875807195076b827bfde464"}, + {file = "memray-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:248dea8cfb5a615345e28b7e25c94377a8d198da3b6957ee443afa6f4ff1b733"}, + {file = "memray-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d03f6be66aa259df7fa50082876fbe6461108d77d46c1f720c46067d60685d4"}, + {file = "memray-1.14.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:9af9d30b1e484fd8591c9a7f322fd50b9192a2bce660be92385a01555af9968b"}, + {file = "memray-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c4088b391c04796c888ac751b5d387f6e8212b3515d4c53ba540c65a6efe4bda"}, + {file = "memray-1.14.0-cp312-cp312-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af8aee7e8e5cac1e4130f1184b3e03b6bb08264e4ba1696551791ed3f8fb824e"}, + {file = "memray-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4352f9e85957f2cbe45a9e1c87dfc19d2df77e93dcd8a558794a683eeee57b7b"}, + {file = "memray-1.14.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5953f0d2aa31b23d4cce20236a03d90b7c71903709a57b456d6494bfe6f470b7"}, + {file = "memray-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4ccaca04365efcda51036fe2add980030e33cfc4f3a194a01f530a5c923c65"}, + {file = "memray-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f85a27eb8a65c810161bb992116a66d328546f78a4a4c7c1868949651b917c08"}, + {file = "memray-1.14.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:958d57f7149b8fa4831785394f2a7ace93dbc2be6c49a1c07987a8972986474a"}, + {file = "memray-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287a01953bc44dd0a32549a23bdacb5f9734e345ca289fa3923867c637715056"}, + {file = "memray-1.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc17cba35d98e3d2ca20ab995f17eec3edba7138b062cbc1aa36d53d9d2d955"}, + {file = "memray-1.14.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c82342cead930ca50235f59740ca238808f9c33ef31d994712972966beb6226e"}, + {file = "memray-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22a826b4047e839310514f4889c24e45a66ea222fca19ac0ae7b2f89bbb0281"}, + {file = "memray-1.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:344f3c73b97ffc8f1666b404deafbc31a19e6b2881341b706aa7ec20afb0e8b1"}, + {file = "memray-1.14.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43455233d534e9c0e8dabe827d451124874a6455b2afcbcd60b823241ea5843"}, + {file = "memray-1.14.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e05a3b6bc82ef01821beaee98e86bd8de2ada06cb8781add9c40a3ae4a040383"}, + {file = "memray-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bc27e5483c70236c9379b99277b4ea8fa4b3f73a99e37af81190649bd877881"}, + {file = "memray-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a7e5604448b2a78e329addfb099384515d3f973a03711c4e2a7b6c9f7f34f53"}, + {file = "memray-1.14.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:443885a96ab9f67d46288240e2593b5c3ecb2c507ddb4e3b10695e104403d001"}, + {file = "memray-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:52a45d96ed717d8efb645e99646a92dd21a2ca38bdb823fe22e38c429cba9513"}, + {file = "memray-1.14.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:72febec7b287667e8ea9ee3e879a4da19a4318bc47e211da815be74acd961994"}, + {file = "memray-1.14.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e07bdc3a4979b335c2b6b93a81b807d5aacd8dbbea56c41c6899a8bc0d2beb3"}, + {file = "memray-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b5e729d03caf426dc45a258270537a603794ecc067ccfd92f9c67ba9332e788"}, + {file = "memray-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1d0a1397b5387b75dc9d9759beb022cb360948584840e850474d7d39ad267f85"}, + {file = "memray-1.14.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:c119b600e7c665e0713f09e25f9ee09733a98035688ecc1ec8fd168fa37a77f6"}, + {file = "memray-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:29a2e7d84d1652ef4664bcceb155630979b4797180b70da35525d963a4ca707f"}, + {file = "memray-1.14.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b3b8d46b6447cdecba3ba100d47c62e78cdad58b00b2d6ba004d6bad318c8668"}, + {file = "memray-1.14.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57f9bf3f1c648f1ea877a84c21c449fdafd8cc105763ada6023e36bae9b45eb8"}, + {file = "memray-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7a59346d242fc39041d87a71cb6cf45baf492ffbb69da9690de49346be64a8"}, + {file = "memray-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:11fb00105572b70f2aca8b787ce9748b0c94672fbb6334f1604f7f813ca3dca6"}, + {file = "memray-1.14.0.tar.gz", hash = "sha256:b5d8874b7b215551f0ae9fa8aef3f2f52321a6460dc0141aaf9374709e6b0eb7"}, +] + +[package.dependencies] +jinja2 = ">=2.9" +rich = ">=11.2.0" +textual = ">=0.41.0" + +[package.extras] +benchmark = ["asv"] +dev = ["Cython", "asv", "black", "bump2version", "check-manifest", "flake8", "furo", "greenlet", "ipython", "isort", "mypy", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools", "sphinx", "sphinx-argparse", "textual (>=0.43,!=0.65.2,!=0.66)", "towncrier"] +docs = ["IPython", "bump2version", "furo", "sphinx", "sphinx-argparse", "towncrier"] +lint = ["black", "check-manifest", "flake8", "isort", "mypy"] +test = ["Cython", "greenlet", "ipython", "packaging", "pytest", "pytest-cov", "pytest-textual-snapshot", "setuptools", "textual (>=0.43,!=0.65.2,!=0.66)"] + +[[package]] +name = "msgspec" +version = "0.18.6" +description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f"}, + {file = "msgspec-0.18.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd"}, + {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177"}, + {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410"}, + {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a"}, + {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4"}, + {file = "msgspec-0.18.6-cp310-cp310-win_amd64.whl", hash = "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7"}, + {file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"}, + {file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"}, + {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"}, + {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"}, + {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"}, + {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"}, + {file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"}, + {file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"}, + {file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"}, + {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"}, + {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca"}, + {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57"}, + {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6"}, + {file = "msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0"}, + {file = "msgspec-0.18.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7d9faed6dfff654a9ca7d9b0068456517f63dbc3aa704a527f493b9200b210a"}, + {file = "msgspec-0.18.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9da21f804c1a1471f26d32b5d9bc0480450ea77fbb8d9db431463ab64aaac2cf"}, + {file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46eb2f6b22b0e61c137e65795b97dc515860bf6ec761d8fb65fdb62aa094ba61"}, + {file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8355b55c80ac3e04885d72db515817d9fbb0def3bab936bba104e99ad22cf46"}, + {file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9080eb12b8f59e177bd1eb5c21e24dd2ba2fa88a1dbc9a98e05ad7779b54c681"}, + {file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc001cf39becf8d2dcd3f413a4797c55009b3a3cdbf78a8bf5a7ca8fdb76032c"}, + {file = "msgspec-0.18.6-cp38-cp38-win_amd64.whl", hash = "sha256:fac5834e14ac4da1fca373753e0c4ec9c8069d1fe5f534fa5208453b6065d5be"}, + {file = "msgspec-0.18.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4"}, + {file = "msgspec-0.18.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e"}, + {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c"}, + {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07"}, + {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be"}, + {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece"}, + {file = "msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090"}, + {file = "msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e"}, +] + +[package.extras] +dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"] +doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] +test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"] +toml = ["tomli", "tomli-w"] +yaml = ["pyyaml"] + +[[package]] +name = "multicall" +version = "0.9.0" +description = "aggregate results from multiple ethereum contract calls" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "multicall-0.9.0-py3-none-any.whl", hash = "sha256:5fa89bbfd03eac3982dc49e25244bb26ecbaa88caa1f52a342e902e86706da30"}, + {file = "multicall-0.9.0.tar.gz", hash = "sha256:c7422414360cad9f0b39f7cf1118fbcfa62b654606cc876267bfa7abfb9c7466"}, +] + +[package.dependencies] +eth_retry = ">=0.1.8" +web3 = ">=5.27,<5.29.dev0 || >5.31.0,<5.31.1 || >5.31.1,<5.31.2 || >5.31.2" + +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pandas" +version = "1.5.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, + {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, + {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, + {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, + {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, + {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, + {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, + {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, +] + +[package.dependencies] +numpy = {version = ">=1.21.0", markers = "python_version >= \"3.10\""} +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] + +[[package]] +name = "parse" +version = "1.20.2" +description = "parse() is the opposite of format()" +optional = false +python-versions = "*" +files = [ + {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}, + {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, +] + +[[package]] +name = "parse-type" +version = "0.6.4" +description = "Simplifies to build parse types based on the parse module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,>=2.7" +files = [ + {file = "parse_type-0.6.4-py2.py3-none-any.whl", hash = "sha256:83d41144a82d6b8541127bf212dd76c7f01baff680b498ce8a4d052a7a5bce4c"}, + {file = "parse_type-0.6.4.tar.gz", hash = "sha256:5e1ec10440b000c3f818006033372939e693a9ec0176f446d9303e4db88489a6"}, +] + +[package.dependencies] +parse = {version = ">=1.18.0", markers = "python_version >= \"3.0\""} +six = ">=1.15" + +[package.extras] +develop = ["build (>=0.5.1)", "coverage (>=4.4)", "pylint", "pytest (<5.0)", "pytest (>=5.0)", "pytest-cov", "pytest-html (>=1.19.0)", "ruff", "setuptools", "setuptools-scm", "tox (>=2.8,<4.0)", "twine (>=1.13.0)", "virtualenv (<20.22.0)", "virtualenv (>=20.0.0)", "wheel"] +docs = ["Sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6.0)"] +testing = ["pytest (<5.0)", "pytest (>=5.0)", "pytest-html (>=1.19.0)"] + +[[package]] +name = "parsimonious" +version = "0.9.0" +description = "(Soon to be) the fastest pure-Python PEG parser I could muster" +optional = false +python-versions = "*" +files = [ + {file = "parsimonious-0.9.0.tar.gz", hash = "sha256:b2ad1ae63a2f65bd78f5e0a8ac510a98f3607a43f1db2a8d46636a5d9e4a30c1"}, +] + +[package.dependencies] +regex = ">=2022.3.15" + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pillow" +version = "11.0.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pony" +version = "0.7.19" +description = "Pony Object-Relational Mapper" +optional = false +python-versions = "*" +files = [ + {file = "pony-0.7.19-py3-none-any.whl", hash = "sha256:5112b4cf40d3f24e93ae66dc5ab7dc6813388efa870e750928d60dc699873cf5"}, + {file = "pony-0.7.19.tar.gz", hash = "sha256:f7f83b2981893e49f7f18e8def52ad8fa8f8e6c5f9583b9aaed62d4d85036a0f"}, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.43" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "protobuf" +version = "4.25.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, +] + +[[package]] +name = "psutil" +version = "5.9.8" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, +] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + +[[package]] +name = "py-solc-ast" +version = "1.2.10" +description = "A tool for exploring the abstract syntax tree generated by solc." +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "py-solc-ast-1.2.10.tar.gz", hash = "sha256:fb5defdb6e82ca4175a0ecd1c04ce37134df0c141fc60b08a5068e119d7e9850"}, + {file = "py_solc_ast-1.2.10-py3-none-any.whl", hash = "sha256:afef589268bea5ce10e217cf147c0a5e539a83ee48efba91d9b0d6f611cd05cd"}, +] + +[[package]] +name = "py-solc-x" +version = "1.1.1" +description = "Python wrapper and version management tool for the solc Solidity compiler." +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "py-solc-x-1.1.1.tar.gz", hash = "sha256:d8b0bd2b04f47cff6e92181739d9e94e41b2d62f056900761c797fa5babc76b6"}, + {file = "py_solc_x-1.1.1-py3-none-any.whl", hash = "sha256:8f5caa4f54e227fc301e2e4c8aa868e869c2bc0c6636aa9e8115f8414bb891f9"}, +] + +[package.dependencies] +requests = ">=2.19.0,<3" +semantic-version = ">=2.8.1,<3" + +[[package]] +name = "pycryptodome" +version = "3.20.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pygments-lexer-solidity" +version = "0.7.0" +description = "Solidity lexer for Pygments (includes Yul intermediate language)" +optional = false +python-versions = ">=3.3, <4" +files = [ + {file = "pygments-lexer-solidity-0.7.0.tar.gz", hash = "sha256:a347fd96981838331b6d98b0f891776908a49406d343ff2a40a6a1c8475a9350"}, +] + +[package.dependencies] +pygments = ">=2.1" + +[[package]] +name = "pyparsing" +version = "3.2.0" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-bdd" +version = "7.3.0" +description = "BDD for pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_bdd-7.3.0-py3-none-any.whl", hash = "sha256:168ede4a118e348feb70182590ee4a2f856e68dafe54a75a4e9203da37d4ade6"}, + {file = "pytest_bdd-7.3.0.tar.gz", hash = "sha256:9dfeb1d8565d9548907f36a5a9e2c8e1e0cbac3b2724e17331b87386a19fbc16"}, +] + +[package.dependencies] +Mako = "*" +packaging = "*" +parse = "*" +parse-type = "*" +pytest = ">=6.2.0" +typing-extensions = "*" + +[[package]] +name = "pytest-forked" +version = "1.6.0" +description = "run tests in isolated forked subprocesses" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-forked-1.6.0.tar.gz", hash = "sha256:4dafd46a9a600f65d822b8f605133ecf5b3e1941ebb3588e943b4e3eb71a5a3f"}, + {file = "pytest_forked-1.6.0-py3-none-any.whl", hash = "sha256:810958f66a91afb1a1e2ae83089d8dc1cd2437ac96b12963042fbb9fb4d16af0"}, +] + +[package.dependencies] +py = "*" +pytest = ">=3.10" + +[[package]] +name = "pytest-xdist" +version = "1.34.0" +description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pytest-xdist-1.34.0.tar.gz", hash = "sha256:340e8e83e2a4c0d861bdd8d05c5d7b7143f6eea0aba902997db15c2a86be04ee"}, + {file = "pytest_xdist-1.34.0-py2.py3-none-any.whl", hash = "sha256:ba5d10729372d65df3ac150872f9df5d2ed004a3b0d499cc0164aafedd8c7b66"}, +] + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=4.4.0" +pytest-forked = "*" +six = "*" + +[package.extras] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "0.16.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = "*" +files = [ + {file = "python-dotenv-0.16.0.tar.gz", hash = "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"}, + {file = "python_dotenv-0.16.0-py2.py3-none-any.whl", hash = "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-telegram-bot" +version = "13.15" +description = "We have made you a wrapper you can't refuse" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-telegram-bot-13.15.tar.gz", hash = "sha256:b4047606b8081b62bbd6aa361f7ca1efe87fa8f1881ec9d932d35844bf57a154"}, + {file = "python_telegram_bot-13.15-py3-none-any.whl", hash = "sha256:06780c258d3f2a3c6c79a7aeb45714f4cd1dd6275941b7dc4628bba64fddd465"}, +] + +[package.dependencies] +APScheduler = "3.6.3" +cachetools = "4.2.2" +certifi = "*" +pytz = ">=2018.6" +tornado = "6.1" + +[package.extras] +json = ["ujson"] +passport = ["cryptography (!=3.4,!=3.4.1,!=3.4.2,!=3.4.3)"] +socks = ["PySocks"] + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "pyunormalize" +version = "15.1.0" +description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyunormalize-15.1.0.tar.gz", hash = "sha256:cf4a87451a0f1cb76911aa97f432f4579e1f564a2f0c84ce488c73a73901b6c1"}, +] + +[[package]] +name = "pywin32" +version = "308" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, + {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, + {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, + {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, + {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, + {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, + {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "referencing" +version = "0.33.0" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "regex" +version = "2023.12.25" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rlp" +version = "4.0.0" +description = "rlp: A package for Recursive Length Prefix encoding and decoding" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "rlp-4.0.0-py3-none-any.whl", hash = "sha256:1747fd933e054e6d25abfe591be92e19a4193a56c93981c05bd0f84dfe279f14"}, + {file = "rlp-4.0.0.tar.gz", hash = "sha256:61a5541f86e4684ab145cb849a5929d2ced8222930a570b3941cf4af16b72a78"}, +] + +[package.dependencies] +eth-utils = ">=2" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +rust-backend = ["rusty-rlp (>=0.2.1,<0.3)"] +test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "rpds-py" +version = "0.18.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.18.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e"}, + {file = "rpds_py-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88"}, + {file = "rpds_py-0.18.0-cp310-none-win32.whl", hash = "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337"}, + {file = "rpds_py-0.18.0-cp310-none-win_amd64.whl", hash = "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836"}, + {file = "rpds_py-0.18.0-cp311-none-win32.whl", hash = "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1"}, + {file = "rpds_py-0.18.0-cp311-none-win_amd64.whl", hash = "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7"}, + {file = "rpds_py-0.18.0-cp312-none-win32.whl", hash = "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98"}, + {file = "rpds_py-0.18.0-cp312-none-win_amd64.whl", hash = "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594"}, + {file = "rpds_py-0.18.0-cp38-none-win32.whl", hash = "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e"}, + {file = "rpds_py-0.18.0-cp38-none-win_amd64.whl", hash = "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20"}, + {file = "rpds_py-0.18.0-cp39-none-win32.whl", hash = "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7"}, + {file = "rpds_py-0.18.0-cp39-none-win_amd64.whl", hash = "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f"}, + {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, +] + +[[package]] +name = "s3transfer" +version = "0.10.3" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.8" +files = [ + {file = "s3transfer-0.10.3-py3-none-any.whl", hash = "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d"}, + {file = "s3transfer-0.10.3.tar.gz", hash = "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + +[[package]] +name = "safe-pysha3" +version = "1.0.4" +description = "SHA-3 (Keccak) for Python 3.9 - 3.11" +optional = false +python-versions = "*" +files = [ + {file = "safe-pysha3-1.0.4.tar.gz", hash = "sha256:e429146b1edd198b2ca934a2046a65656c5d31b0ec894bbd6055127f4deaff17"}, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "sentry-sdk" +version = "2.1.1" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sentry_sdk-2.1.1-py2.py3-none-any.whl", hash = "sha256:99aeb78fb76771513bd3b2829d12613130152620768d00cd3e45ac00cb17950f"}, + {file = "sentry_sdk-2.1.1.tar.gz", hash = "sha256:95d8c0bb41c8b0bc37ab202c2c4a295bb84398ee05f4cdce55051cd75b926ec1"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.11" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=5)"] + +[[package]] +name = "setuptools" +version = "75.3.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, + {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.36" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "sqlmodel" +version = "0.0.22" +description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b"}, + {file = "sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e"}, +] + +[package.dependencies] +pydantic = ">=1.10.13,<3.0.0" +SQLAlchemy = ">=2.0.14,<2.1.0" + +[[package]] +name = "starlette" +version = "0.41.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, + {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "textual" +version = "0.85.2" +description = "Modern Text User Interface framework" +optional = false +python-versions = "<4.0.0,>=3.8.1" +files = [ + {file = "textual-0.85.2-py3-none-any.whl", hash = "sha256:9ccdeb6b8a6a0ff72d497f714934f2e524f2eb67783b459fb08b1339ee537dc0"}, + {file = "textual-0.85.2.tar.gz", hash = "sha256:2a416995c49d5381a81d0a6fd23925cb0e3f14b4f239ed05f35fa3c981bb1df2"}, +] + +[package.dependencies] +markdown-it-py = {version = ">=2.1.0", extras = ["linkify", "plugins"]} +platformdirs = ">=3.6.0,<5" +rich = ">=13.3.3" +typing-extensions = ">=4.4.0,<5.0.0" + +[package.extras] +syntax = ["tree-sitter (>=0.20.1,<0.21.0)", "tree-sitter-languages (==1.10.2)"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "toolz" +version = "0.12.1" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, +] + +[[package]] +name = "tornado" +version = "6.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.5" +files = [ + {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"}, + {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"}, + {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"}, + {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"}, + {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"}, + {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"}, + {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"}, + {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"}, + {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"}, + {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"}, + {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"}, + {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"}, + {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"}, + {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"}, + {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"}, + {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"}, + {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, + {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, +] + +[[package]] +name = "tqdm" +version = "4.66.2" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typed-envs" +version = "0.0.2" +description = "typed_envs is used to create specialized EnvironmentVariable objects that behave exactly the same as any other instance of the `typ` used to create them." +optional = false +python-versions = "*" +files = [ + {file = "typed-envs-0.0.2.tar.gz", hash = "sha256:7113e60f489936344f03c2e65fdbbbb1115cc8e215b823627d446396efcf4327"}, + {file = "typed_envs-0.0.2-py3-none-any.whl", hash = "sha256:8bd4c31e52011cfc7616c09d5b2db239c30190cdff938653c8cd5a05581ca6b8"}, +] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "tzdata" +version = "2024.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, +] + +[[package]] +name = "tzlocal" +version = "5.2" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + +[[package]] +name = "uc-micro-py" +version = "1.0.3" +description = "Micro subset of unicode data files for linkify-it-py projects." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, + {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, +] + +[package.extras] +test = ["coverage", "pytest", "pytest-cov"] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.32.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"}, + {file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "vvm" +version = "0.1.0" +description = "Vyper version management tool" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "vvm-0.1.0-py3-none-any.whl", hash = "sha256:814c67bc8049d45ea8049bc26b04ce4065015f5a3e2896a1a2a2a44ab6e85edc"}, + {file = "vvm-0.1.0.tar.gz", hash = "sha256:a1474915b12e0084299d2c7fe7d72434fa99c00ebb117e400756a5d7e0edac2a"}, +] + +[package.dependencies] +requests = ">=2.19.0,<3" +semantic-version = ">=2.8.1,<3" + +[[package]] +name = "vyper" +version = "0.3.10" +description = "Vyper: the Pythonic Programming Language for the EVM" +optional = false +python-versions = ">=3.10,<4" +files = [ + {file = "vyper-0.3.10-py3-none-any.whl", hash = "sha256:05636302341bf89602b19f749fcabc8d184a265d8eea4a45c20b3259780353b0"}, + {file = "vyper-0.3.10.tar.gz", hash = "sha256:8dc1f501caab417fb0ce9c68a6944587f0147ec7cc7d3889cf3a45c19466e489"}, +] + +[package.dependencies] +asttokens = ">=2.0.5,<3" +cbor2 = ">=5.4.6,<6" +importlib-metadata = "*" +packaging = ">=23.1,<24" +pycryptodome = ">=3.5.1,<4" +wheel = "*" + +[package.extras] +dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.982)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] +docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] +lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.982)"] +test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "web3" +version = "6.15.1" +description = "web3.py" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "web3-6.15.1-py3-none-any.whl", hash = "sha256:4e4a8313aa4556ecde61c852a62405b853b667498b07da6ff05c29fe8c79096b"}, + {file = "web3-6.15.1.tar.gz", hash = "sha256:f9e7eefc1b3c3d194868a4ef9583b625c18ea3f31a48ebe143183db74898f381"}, +] + +[package.dependencies] +aiohttp = ">=3.7.4.post0" +eth-abi = ">=4.0.0" +eth-account = ">=0.8.0" +eth-hash = {version = ">=0.5.1", extras = ["pycryptodome"]} +eth-typing = ">=3.0.0" +eth-utils = ">=2.1.0" +hexbytes = ">=0.1.0,<0.4.0" +jsonschema = ">=4.0.0" +lru-dict = ">=1.1.6,<1.3.0" +protobuf = ">=4.21.6" +pyunormalize = ">=15.0.0" +pywin32 = {version = ">=223", markers = "platform_system == \"Windows\""} +requests = ">=2.16.0" +typing-extensions = ">=4.0.1" +websockets = ">=10.0.0" + +[package.extras] +dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.2)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (==1.4.1)", "py-geth (>=3.14.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1,<0.23)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +ipfs = ["ipfshttpclient (==0.8.0a2)"] +linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==1.4.1)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] +tester = ["eth-tester[py-evm] (==v0.9.1-b.2)", "py-geth (>=3.14.0)"] + +[[package]] +name = "websockets" +version = "12.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, +] + +[[package]] +name = "wheel" +version = "0.42.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, + {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "ypricemagic" +version = "4.0.14" +description = "Use this tool to extract historical on-chain price data from an archive node. Shoutout to @bantg and @nymmrx for their awesome work on yearn-exporter that made this library possible." +optional = false +python-versions = "*" +files = [ + {file = "ypricemagic-4.0.14-py3-none-any.whl", hash = "sha256:6214c82b13b0fe4ce6a189f53c1bc87a2b2e5b1bbebe5a70a065ee1ca11c8c39"}, + {file = "ypricemagic-4.0.14.tar.gz", hash = "sha256:4e6825be5cef15fff4d09068b2edd28fea0b0984ae78191605781ad7ad7a7fb8"}, +] + +[package.dependencies] +bobs-lazy-logging = "0.0.4" +cachetools = ">=4.1.1,<4.3" +checksum-dict = ">=1.1.1" +dank-mids = ">=4.20.97" +eth-brownie = ">=1.19.3,<1.21" +eth-retry = ">=0.1.19,<0.2" +ez-a-sync = ">=0.22.1,<0.24" +inflection = ">=0.1,<0.6" +joblib = ">=1.0.1" +multicall = ">=0.8.2" +pony = "*" + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10,<3.11" +content-hash = "d779005004a267258a95b4acdc87539dc3017fb3204c0a8c069fb708eacd367e" diff --git a/pyproject.toml b/pyproject.toml index 0097e9f6e..a835bed41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,39 @@ [tool.black] skip-string-normalization = true + +[tool.poetry] +name = "yearn-exporter" +version = "1.0.0" +description = "yearn exporter" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = ">=3.10,<3.11" +eth-brownie = ">=1.20.3,<1.21" +click = ">=7.1.2" +tabulate = ">=0.8.7" +joblib = ">=1.0.1" +fastapi = ">=0.63.0" +uvicorn = ">=0.13.4" +sqlmodel = ">=0.0.4" +psycopg2-binary = ">=2.8.5" +#tokenlists = ">=0.1.7,<0.2" +semantic-version = ">=2.8.5" +boto3 = ">=1.34,<1.35" +matplotlib = ">=3.6.1" +sqlalchemy = ">=1.4.41" +sentry-sdk = ">=2.1.1,<2.2" +pytest-bdd = ">=5.0.0" +dank_mids = "4.20.98" +eth_retry = ">=0.1.19" +eth-hash = { version = "*", extras = ["pysha3"] } +ez-a-sync = "^0.22.13" +python-telegram-bot = "13.15" +ypricemagic = "4.0.14" +eth-portfolio = { git = "https://www.github.com/BobTheBuidler/eth-portfolio", branch = "master" } +memray = "*" +numpy = "<2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d39b4f3f7..000000000 --- a/requirements.txt +++ /dev/null @@ -1,30 +0,0 @@ -eth-brownie>=1.19.3 -click>=7.1.2 -tabulate>=0.8.7 -joblib>=1.0.1 -cachetools>=4.1.1 -fastapi>=0.63.0 -uvicorn>=0.13.4 -sqlmodel>=0.0.4 -psycopg2-binary>=2.8.5 -tokenlists>=0.1.0b0,<0.1.1 -aiofiles>=0.6.0 -semantic-version>=2.8.5 -boto3==1.17.88 -rich>=12.6.0 -matplotlib>=3.6.1 -msgspec>=0.18.5 -pandas>=1.3.0 -pony>=0.7.16 -sqlalchemy>=1.4.41 -sentry-sdk==2.8.0 -pytest-bdd>=5.0.0 -dank_mids==4.20.84 -eth_retry>=0.1.19 -eth-hash[pysha3] -ez-a-sync==0.19.2 -python-telegram-bot==13.15 -ypricemagic>=3.4.6 -git+https://www.github.com/BobTheBuidler/eth-portfolio@98613afaef2beb020e2412574d9a68e2d1a6186c -git+https://www.github.com/BobTheBuidler/toolcache@e37b53cec64556d2b35df66767fca4c8f366a2fa -memray \ No newline at end of file diff --git a/scripts/collect_reports.py b/scripts/collect_reports.py new file mode 100644 index 000000000..fcc19da20 --- /dev/null +++ b/scripts/collect_reports.py @@ -0,0 +1,811 @@ +from lib2to3.pgen2 import token +import asyncio +import logging +import os +import time +import warnings +import telebot +from datetime import datetime, timezone +from functools import lru_cache +from typing import List, Union, Tuple, Callable + +import a_sync +import dank_mids +import pandas as pd +from async_lru import alru_cache +from brownie import chain, web3, ZERO_ADDRESS, interface +from brownie.network.event import _EventItem +from discordwebhook import Discord +from sqlalchemy import desc, asc +from web3._utils.events import construct_event_topic_set +from y import Contract, ERC20, Network, get_block_timestamp_async, get_price +from y.utils.events import ProcessedEvents + +from yearn.cache import memory +from yearn.db.models import Reports, Event, Transactions, Session, engine, select +from yearn.prices import constants + +warnings.filterwarnings("ignore", ".*Class SelectOfScalar will not make use of SQL compilation caching.*") +warnings.filterwarnings("ignore", ".*Locally compiled and on-chain*") +warnings.filterwarnings("ignore", ".*It has been discarded*") +warnings.filterwarnings("ignore", ".*MismatchedABI*") + +# mainnet_public_channel = os.environ.get('TELEGRAM_CHANNEL_1_PUBLIC') +# ftm_public_channel = os.environ.get('TELEGRAM_CHANNEL_250_PUBLIC') +# discord_mainnet = os.environ.get('DISCORD_CHANNEL_1') +# discord_ftm = os.environ.get('DISCORD_CHANNEL_250') + +logger = logging.getLogger(__name__) +sync_threads = a_sync.PruningThreadPoolExecutor(8) + +VAULT_EXCEPTIONS = [ + '0xcd68c3fC3e94C5AcC10366556b836855D96bfa93', # yvCurve-dETH-f +] + +inv_telegram_key = os.environ.get('WAVEY_ALERTS_BOT_KEY') +ETHERSCANKEY = os.environ.get('ETHERSCAN_KEY') +invbot = telebot.TeleBot(inv_telegram_key) +env = os.environ.get('ENVIRONMENT') +alerts_enabled = True if env == "PROD" else False #or env == "TEST" else False + +test_channel = os.environ.get('TELEGRAM_CHANNEL_TEST') +if env == "TEST": + telegram_key = os.environ.get('WAVEY_ALERTS_BOT_KEY') + dev_channel = test_channel + bot = telebot.TeleBot(telegram_key) +else: + telegram_key = os.environ.get('HARVEST_TRACKER_BOT_KEY') + bot = telebot.TeleBot(telegram_key) + dev_channel = os.environ.get('TELEGRAM_CHANNEL_DEV') + +OLD_REGISTRY_ENDORSEMENT_BLOCKS = { + "0xE14d13d8B3b85aF791b2AADD661cDBd5E6097Db1": 11999957, + "0xdCD90C7f6324cfa40d7169ef80b12031770B4325": 11720423, + "0x986b4AFF588a109c09B50A03f42E4110E29D353F": 11881934, + "0xcB550A6D4C8e3517A939BC79d0c7093eb7cF56B5": 11770630, + "0xa9fE4601811213c340e850ea305481afF02f5b28": 11927501, + "0xB8C3B7A2A618C552C23B1E4701109a9E756Bab67": 12019352, + "0xBFa4D8AA6d8a379aBFe7793399D3DdaCC5bBECBB": 11579535, + "0x19D3364A399d251E894aC732651be8B0E4e85001": 11682465, + "0xe11ba472F74869176652C35D30dB89854b5ae84D": 11631914, + "0xe2F6b9773BF3A015E2aA70741Bde1498bdB9425b": 11579535, + "0x5f18C75AbDAe578b483E5F43f12a39cF75b973a9": 11682465, + "0x27b7b1ad7288079A66d12350c828D3C00A6F07d7": 12089661, +} + +INVERSE_PRIVATE_VAULTS = [ + "0xD4108Bb1185A5c30eA3f4264Fd7783473018Ce17", + "0x67B9F46BCbA2DF84ECd41cC6511ca33507c9f4E9", + "0xd395DEC4F1733ff09b750D869eEfa7E0D37C3eE6", +] + +REKT = [ + # these vaults are rekt, we will force price to 0 + '0xC4C319E2D4d66CcA4464C0c2B32c9Bd23ebe784e', # rekt alETH + '0x9848482da3Ee3076165ce6497eDA906E66bB85C5', # rekt jPegd pETH + '0xEd4064f376cB8d68F770FB1Ff088a3d0F3FF5c4d', # rekt crvETH +] + +CHAIN_VALUES = { + Network.Mainnet: { + "NETWORK_NAME": "Ethereum Mainnet", + "NETWORK_SYMBOL": "ETH", + "EMOJI": "🇪🇹", + "START_DATE": datetime(2020, 2, 12, tzinfo=timezone.utc), + "START_BLOCK": 11563389, + "REGISTRY_ADDRESSES": ["0x50c1a2eA0a861A967D9d0FFE2AE4012c2E053804","0xaF1f5e1c19cB68B30aAD73846eFfDf78a5863319"], + "REGISTRY_DEPLOY_BLOCK": 12045555, + "REGISTRY_HELPER_ADDRESS": "0xec85C894be162268c834b784CC232398E3E89A12", + "LENS_ADDRESS": "0x5b4F3BE554a88Bd0f8d8769B9260be865ba03B4a", + "LENS_DEPLOY_BLOCK": 12707450, + "VAULT_ADDRESS030": "0x19D3364A399d251E894aC732651be8B0E4e85001", + "VAULT_ADDRESS031": "0xdA816459F1AB5631232FE5e97a05BBBb94970c95", + "KEEPER_CALL_CONTRACT": "0x0a61c2146A7800bdC278833F21EBf56Cd660EE2a", + "KEEPER_TOKEN": "0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44", + "KEEPER_WRAPPER": "0x0D26E894C2371AB6D20d99A65E991775e3b5CAd7", + "YEARN_TREASURY": "0x93A62dA5a14C80f265DAbC077fCEE437B1a0Efde", + "STRATEGIST_MULTISIG": "0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7", + "GOVERNANCE_MULTISIG": "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52", + "EXPLORER_URL": "https://etherscan.io/", + "TENDERLY_CHAIN_IDENTIFIER": "mainnet", + "TELEGRAM_CHAT_ID": os.environ.get('TELEGRAM_CHANNEL_1_PUBLIC') if env == "PROD" else test_channel, + "TELEGRAM_CHAT_ID_INVERSE_ALERTS": os.environ.get('TELEGRAM_CHAT_ID_INVERSE_ALERTS') if env == "PROD" else test_channel, + "DISCORD_CHAN": os.environ.get('DISCORD_CHANNEL_1'), + }, + Network.Fantom: { + "NETWORK_NAME": "Fantom", + "NETWORK_SYMBOL": "FTM", + "EMOJI": "👻", + "START_DATE": datetime(2021, 4, 30, tzinfo=timezone.utc), + "START_BLOCK": 18450847, + "REGISTRY_ADDRESSES": ["0x727fe1759430df13655ddb0731dE0D0FDE929b04"], + "REGISTRY_DEPLOY_BLOCK": 18455565, + "REGISTRY_HELPER_ADDRESS": "0x8CC45f739104b3Bdb98BFfFaF2423cC0f817ccc1", + "REGISTRY_HELPER_DEPLOY_BLOCK": 18456459, + "LENS_ADDRESS": "0x97D0bE2a72fc4Db90eD9Dbc2Ea7F03B4968f6938", + "LENS_DEPLOY_BLOCK": 18842673, + "VAULT_ADDRESS030": "0x637eC617c86D24E421328e6CAEa1d92114892439", + "VAULT_ADDRESS031": "0x637eC617c86D24E421328e6CAEa1d92114892439", + "KEEPER_CALL_CONTRACT": "0x57419fb50fa588fc165acc26449b2bf4c7731458", + "KEEPER_WRAPPER": "0x0D26E894C2371AB6D20d99A65E991775e3b5CAd7", + "YEARN_TREASURY": "0x89716Ad7EDC3be3B35695789C475F3e7A3Deb12a", + "STRATEGIST_MULTISIG": "0x72a34AbafAB09b15E7191822A679f28E067C4a16", + "GOVERNANCE_MULTISIG": "0xC0E2830724C946a6748dDFE09753613cd38f6767", + "EXPLORER_URL": "https://ftmscan.com/", + "TENDERLY_CHAIN_IDENTIFIER": "fantom", + "TELEGRAM_CHAT_ID": os.environ.get('TELEGRAM_CHANNEL_250_PUBLIC'), + "TELEGRAM_CHAT_ID_INVERSE_ALERTS": os.environ.get('TELEGRAM_CHAT_ID_INVERSE_ALERTS'), + "DISCORD_CHAN": os.environ.get('DISCORD_CHANNEL_250'), + }, + Network.Arbitrum: { + "NETWORK_NAME": "Arbitrum", + "NETWORK_SYMBOL": "ARRB", + "EMOJI": "🤠", + "START_DATE": datetime(2021, 9, 14, tzinfo=timezone.utc), + "START_BLOCK": 4841854, + "REGISTRY_ADDRESSES": ["0x3199437193625DCcD6F9C9e98BDf93582200Eb1f"], + "REGISTRY_DEPLOY_BLOCK": 12045555, + "REGISTRY_HELPER_ADDRESS": "0x237C3623bed7D115Fc77fEB08Dd27E16982d972B", + "LENS_ADDRESS": "0xcAd10033C86B0C1ED6bfcCAa2FF6779938558E9f", + "VAULT_ADDRESS030": "0x239e14A19DFF93a17339DCC444f74406C17f8E67", + "VAULT_ADDRESS031": "0x239e14A19DFF93a17339DCC444f74406C17f8E67", + "KEEPER_WRAPPER": "0x0D26E894C2371AB6D20d99A65E991775e3b5CAd7", + "YEARN_TREASURY": "0x1DEb47dCC9a35AD454Bf7f0fCDb03c09792C08c1", + "STRATEGIST_MULTISIG": "0x6346282DB8323A54E840c6C772B4399C9c655C0d", + "GOVERNANCE_MULTISIG": "0xb6bc033D34733329971B938fEf32faD7e98E56aD", + "EXPLORER_URL": "https://arbiscan.io/", + "TENDERLY_CHAIN_IDENTIFIER": "arbitrum", + "TELEGRAM_CHAT_ID": os.environ.get('TELEGRAM_CHANNEL_42161_PUBLIC'), + "DISCORD_CHAN": os.environ.get('DISCORD_CHANNEL_42161'), + }, + Network.Optimism: { + "NETWORK_NAME": "Optimism", + "NETWORK_SYMBOL": "OPT", + "EMOJI": "🔴", + "START_DATE": datetime(2022, 8, 6, tzinfo=timezone.utc), + "START_BLOCK": 24097341, + "REGISTRY_ADDRESSES": ["0x1ba4eB0F44AB82541E56669e18972b0d6037dfE0", "0x79286Dd38C9017E5423073bAc11F53357Fc5C128"], + "REGISTRY_DEPLOY_BLOCK": 18097341, + "REGISTRY_HELPER_ADDRESS": "0x2222aaf54Fe3B10937E91A0C2B8a92c18A636D05", + "LENS_ADDRESS": "0xD3A93C794ee2798D8f7906493Cd3c2A835aa0074", + "VAULT_ADDRESS030": "0x0fBeA11f39be912096cEc5cE22F46908B5375c19", + "VAULT_ADDRESS031": "0x0fBeA11f39be912096cEc5cE22F46908B5375c19", + "KEEPER_WRAPPER": "0x0D26E894C2371AB6D20d99A65E991775e3b5CAd7", + "YEARN_TREASURY": "0x84654e35E504452769757AAe5a8C7C6599cBf954", + "STRATEGIST_MULTISIG": "0xea3a15df68fCdBE44Fdb0DB675B2b3A14a148b26", + "GOVERNANCE_MULTISIG": "0xF5d9D6133b698cE29567a90Ab35CfB874204B3A7", + "EXPLORER_URL": "https://optimistic.etherscan.io/", + "TENDERLY_CHAIN_IDENTIFIER": "optimistic", + "TELEGRAM_CHAT_ID": os.environ.get('TELEGRAM_CHANNEL_10_PUBLIC'), + "DISCORD_CHAN": os.environ.get('DISCORD_CHANNEL_10'), + } +} + +TREASURY = CHAIN_VALUES[chain.id]["YEARN_TREASURY"] + +if chain.id == 1: + CRV = '0xD533a949740bb3306d119CC777fa900bA034cd52' + CRV_CONTRACT = Contract(CRV) + CRV_ABI = CRV_CONTRACT.abi + VOTER = '0xF147b8125d2ef93FB6965Db97D6746952a133934' + YVECRV = '0xc5bDdf9843308380375a611c18B50Fb9341f502A' + +KP3R_TOKEN = CHAIN_VALUES[chain.id].get("KEEPER_TOKEN", "") +KEEPER_CALL_CONTRACT = CHAIN_VALUES[chain.id].get("KEEPER_CALL_CONTRACT", "") + + +# Primary vault interface +vault = Contract(CHAIN_VALUES[chain.id]["VAULT_ADDRESS031"]) +vault = web3.eth.contract(str(vault), abi=vault.abi) +topics = construct_event_topic_set( + vault.events.StrategyReported().abi, web3.codec, {} +) +# Deprecated vault interface +if chain.id == 1: + vault_v030 = Contract(CHAIN_VALUES[chain.id]["VAULT_ADDRESS030"]) + vault_v030 = web3.eth.contract(CHAIN_VALUES[chain.id]["VAULT_ADDRESS030"], abi=vault_v030.abi) + topics_v030 = construct_event_topic_set( + vault_v030.events.StrategyReported().abi, web3.codec, {} + ) + +events_to_process: List[Event] = [] +transaction_hashes: List[str] = [] + +def main(): + logging.basicConfig(level=logging.DEBUG) + logging.getLogger('a_sync.utils.iterators').disabled = True + asyncio.get_event_loop().run_until_complete(_main()) + +async def _main(dynamically_find_multi_harvest=False): + print(f"dynamic multi_harvest detection is enabled: {dynamically_find_multi_harvest}") + + last_reported_block, last_reported_block030 = last_harvest_block() + # last_reported_block = 16482431 + # last_reported_block030 = 16482431 + print("latest block (v0.3.1+ API)",last_reported_block) + print("blocks behind (v0.3.1+ API)", chain.height - last_reported_block) + if chain.id == 1: + print("latest block (v0.3.0 API)",last_reported_block030) + print("blocks behind (v0.3.0 API)", chain.height - last_reported_block030) + + filters = [StrategyReportedEvents(last_reported_block+1, dynamically_find_multi_harvest)] + if chain.id == 1: + # No old vaults deployed anywhere other than mainnet + filters.append(StrategyReportedEventsV030(last_reported_block030+1, dynamically_find_multi_harvest)) + + # while True: # Keep this as a long-running script # <--- disabled this since ypm issues + tasks: List[asyncio.Task] = [] + async for strategy_report_event in a_sync.as_yielded(*filters): + new_task = asyncio.create_task(handle_event(strategy_report_event.event, strategy_report_event.multi_harvest)) + tasks.append(new_task) + clear_finished_tasks(tasks) + + +async def handle_event(event, multi_harvest): + endorsed_vaults = await get_vaults() + txn_hash = event.transaction_hash #.hex() + if event.address in VAULT_EXCEPTIONS: + return + if event.address not in endorsed_vaults: + # check if a vault from inverse partnership + if event.address not in INVERSE_PRIVATE_VAULTS: + print(f"skipping: not endorsed. txn hash {txn_hash}. chain id {chain.id} sync {event.block_number} / {chain.height}.") + return + if event.address not in INVERSE_PRIVATE_VAULTS: + if await get_vault_endorsement_block(event.address) > event.block_number: + print(f"skipping: not endorsed yet. txn hash {txn_hash}. chain id {chain.id} sync {event.block_number} / {chain.height}.") + return + + print(txn_hash) + + r = Reports( + chain_id = chain.id, + multi_harvest = multi_harvest, + block = event.block_number, + txn_hash = txn_hash, + vault_address = event.address, + ) + try: + await Contract.coroutine(event.address) + except ValueError: + return + + r.strategy_address, *event_values = list(event.values()) + + # we create some tasks early so they can start processing before we need the results + strategy_task = asyncio.create_task(Contract.coroutine(r.strategy_address)) + receipt_task = asyncio.create_task(get_transaction_receipt(txn_hash)) + t_task = asyncio.create_task(get_db_transaction(txn_hash, r.block)) + time_task = asyncio.create_task(set_time_attrs(r)) + + # now we cache this so we don't need to call it for every event with `vault.decimals()` + r.vault_decimals = await ERC20(event.address, asynchronous=True).decimals + r.gain, r.loss, r.debt_paid, r.total_gain, r.total_loss, r.total_debt, r.debt_added, r.debt_ratio = normalize_event_values(event_values, r.vault_decimals) + + strategy = await strategy_task + + r.want_token, tx_receipt = await asyncio.gather(get_want(strategy), receipt_task) + + ( + r.vault_symbol, + r.token_symbol, + (r.vault_name, r.vault_api), + (r.strategy_name, r.strategy_api), + r.strategist, + r.want_price_at_block, + (r.gov_fee_in_want, r.strategist_fee_in_want), + ) = await asyncio.gather( + # all of these are cached + get_symbol(event.address), + get_symbol(r.want_token), + get_name_and_version(event.address), + get_name_and_version(strategy.address), + get_strategist(strategy), + # these arent + get_want_price(r), + parse_fees(tx_receipt, r.vault_address, r.strategy_address, r.vault_decimals, r.gain, r.vault_api), + ) + + r.gain_post_fees = r.gain - r.loss - r.strategist_fee_in_want - r.gov_fee_in_want + r.want_gain_usd = r.gain * float(r.want_price_at_block) + + # KeepCRV stuff + if chain.id == 1: + decoder = await get_transfer_decoder(CRV) + decoded_events = decoder(tx_receipt) + r.keep_crv = 0 + for tfr in decoded_events: + _from, _to, _val = tfr.args.values() + if tfr.address == CRV and _from == r.strategy_address and (_to == VOTER or _to == TREASURY): + r.keep_crv = _val / 1e18 + r.crv_price_usd = await get_price(CRV, r.block, sync=False) + r.keep_crv_value_usd = r.keep_crv * float(r.crv_price_usd) + + if r.keep_crv > 0: + decoder = await get_transfer_decoder(YVECRV) + decoded_events = decoder(tx_receipt) + try: + r.keep_crv_percent = await get_keepcrv(strategy) + except: + pass + for tfr in decoded_events: + _from, _to, _val = tfr.args.values() + if tfr.address == YVECRV and _from == ZERO_ADDRESS: + r.yvecrv_minted = _val/1e18 + + await asyncio.gather(time_task) + + r.updated_timestamp = datetime.now() + + with Session(engine) as session: + # the result should be ready by now, lets grab it + t = await t_task + query = select(Reports).where( + Reports.chain_id == chain.id, Reports.strategy_address == r.strategy_address + ).order_by(desc(Reports.block)) + + previous_report = session.exec(query).first() + checks = 1 + while previous_report is None: + print('no previous report found on {checks}th check') + await asyncio.sleep(60) + previous_report = session.exec(query).first() + checks += 1 + + r.previous_report_id = previous_report.id + r.rough_apr_pre_fee, r.rough_apr_post_fee = await compute_apr(r, previous_report) + + try: + # Insert to database + await db_insert(r) + print(f"report added. strategy {r.strategy_address} txn hash {r.txn_hash}. chain id {r.chain_id} sync {r.block} / {chain.height}.") + prepare_alerts(r, t) + except Exception as e: + print(f"skipped duplicate record. strategy: {r.strategy_address} at tx hash: {r.txn_hash} {e}") + + +@alru_cache(maxsize=1) +async def get_vaults() -> List[str]: + registry_helper = await Contract.coroutine(CHAIN_VALUES[chain.id]["REGISTRY_HELPER_ADDRESS"]) + return list(await registry_helper.getVaults.coroutine()) + +@alru_cache(maxsize=None) +async def get_keepcrv(strategy: Contract) -> int: + return await strategy.keepCRV.coroutine() + +@alru_cache(maxsize=None) +async def get_strategist(strategy: Contract) -> str: + return await strategy.strategist.coroutine() + +@alru_cache(maxsize=None) +async def get_want(strategy: Contract) -> str: + want = await strategy.want.coroutine() + print(f'Want token = {want}') + return want + +@alru_cache(maxsize=None) +async def get_block(number: int) -> dict: + return await dank_mids.eth.get_block(number) + +@alru_cache(maxsize=None) +async def get_transaction(hash: str) -> dict: + return await dank_mids.eth.get_transaction(hash) + +@alru_cache(maxsize=None, ttl=60*60) +async def get_transaction_receipt(hash: str) -> dict: + return await dank_mids.eth.get_transaction_receipt(hash) + +@lru_cache(maxsize=None) +def get_date_string(timestamp: int) -> str: + return datetime.utcfromtimestamp(timestamp).strftime("%m/%d/%Y, %H:%M:%S") + +@alru_cache(maxsize=None) +async def get_name_and_version(address: str) -> Tuple[str, str]: + contract = await Contract.coroutine(address) + return await asyncio.gather(ERC20(address, asynchronous=True).name, contract.apiVersion.coroutine()) + +@alru_cache(maxsize=None) +async def get_symbol(token: str) -> str: + return await ERC20(token, asynchronous=True).symbol + + +@alru_cache(maxsize=None, ttl=60*30) +async def get_db_transaction(txhash: str, block: int) -> Transactions: + if t := await sync_threads.run(transaction_record_exists, txhash): + return t + t = Transactions(chain_id=chain.id, txn_hash=txhash, block=block) + await asyncio.gather(*[setter(t) for setter in _t_attr_setters]) + t.updated_timestamp = datetime.now() + await db_insert(t) + return t + +async def set_time_attrs(o: Union[Transactions, Reports]) -> None: + o.timestamp = await get_block_timestamp_async(o.block) + o.date = datetime.utcfromtimestamp(o.timestamp) + o.date_string = get_date_string(o.timestamp) + +async def set_gas_attrs(t: Transactions) -> None: + price_task = asyncio.create_task(get_price(constants.weth, t.block, sync=False)) + tx = await get_transaction(t.txn_hash) + gas_price = tx.gasPrice + t.txn_gas_price = gas_price / 10 ** 9 # Use gwei + receipt = await get_transaction_receipt(t.txn_hash) + t.call_cost_eth = gas_price * receipt.gasUsed / 10 ** 18 + t.eth_price_at_block = await price_task + t.call_cost_usd = float(t.eth_price_at_block) * float(t.call_cost_eth) + +async def set_receipt_attrs(t: Transactions) -> None: + if chain.id == 1: + kp3r_price_task = asyncio.create_task( + get_price(KP3R_TOKEN, t.block, sync=False) + ) + + receipt = await get_transaction_receipt(t.txn_hash) + t.txn_to = receipt.to + t.txn_from = receipt["from"] + t.txn_gas_used = receipt.gasUsed + + if chain.id == 1: + t.kp3r_price_at_block, t.kp3r_paid = await asyncio.gather(kp3r_price_task, get_keeper_payment(receipt)) + t.kp3r_paid /= 10 ** 18 + t.kp3r_paid_usd = float(t.kp3r_paid) * float(t.kp3r_price_at_block) + t.keeper_called = t.kp3r_paid > 0 + else: + t.keeper_called = t.txn_to == KEEPER_CALL_CONTRACT + +_t_attr_setters = set_time_attrs, set_gas_attrs, set_receipt_attrs + + +@a_sync.a_sync(default='async') +def db_insert(obj) -> None: + with Session(engine) as session: + session.add(obj) + session.commit() + +async def get_want_price(r: Reports): + if r.vault_address == '0x9E0E0AF468FbD041Cab7883c5eEf16D1A99a47C3': + return 1 + elif r.want_token in REKT: + return 0 + return await get_price(r.want_token, r.block, sync=False) + +@lru_cache(maxsize=None) +def transaction_record_exists(txn_hash): + with Session(engine) as session: + query = select(Transactions).where( + Transactions.txn_hash == txn_hash + ) + result = session.exec(query).first() + if result == None: + return False + return result + +def last_harvest_block(): + with Session(engine) as session: + query = select(Reports.block).where( + Reports.chain_id == chain.id, Reports.vault_api != "0.3.0" + ).order_by(desc(Reports.block)) + result1 = session.exec(query).first() + if result1 == None: + result1 = CHAIN_VALUES[chain.id]["START_BLOCK"] + if chain.id == 1: + query = select(Reports.block).where( + Reports.chain_id == chain.id, Reports.vault_api == "0.3.0" + ).order_by(desc(Reports.block)) + result2 = session.exec(query).first() + if result2 == None: + result2 = CHAIN_VALUES[chain.id]["START_BLOCK"] + else: + result2 = 0 + + return result1, result2 + +async def get_keeper_payment(receipt): + amount = 0 + decoder, denominator = await asyncio.gather(get_transfer_decoder(KP3R_TOKEN), ERC20(KP3R_TOKEN, asynchronous=True).scale) + decoded_events = decoder(receipt) + for e in decoded_events: + if e.address == KP3R_TOKEN: + sender, receiver, token_amount = e.args.values() + token_amount = token_amount / denominator + if receiver == receipt["from"]: + amount = token_amount + return amount + +@alru_cache(maxsize=None) +async def get_transfer_decoder(address: str) -> Callable: + contract = await Contract.coroutine(address) + return web3.eth.contract(str(address), abi=contract.abi).events.Transfer().processReceipt + +async def compute_apr(report: Reports, previous_report): + SECONDS_IN_A_YEAR = 31557600 + seconds_between_reports = report.timestamp - previous_report.timestamp + pre_fee_apr = 0 + post_fee_apr = 0 + + if report.vault_address == '0x27B5739e22ad9033bcBf192059122d163b60349D': + vault, vault_scale = await asyncio.gather( + Contract.coroutine(report.vault_address), + ERC20(report.vault_address, asynchronous=True).scale, + ) + total_assets = await vault.totalAssets.coroutine(block_identifier=report.block) + if total_assets == 0 or seconds_between_reports == 0: + return 0, 0 + pre_fee_apr = report.gain / int(total_assets/vault_scale) * (SECONDS_IN_A_YEAR / seconds_between_reports) + if report.gain_post_fees != 0: + post_fee_apr = report.gain_post_fees / int(total_assets/vault_scale) * (SECONDS_IN_A_YEAR / seconds_between_reports) + else: + if int(previous_report.total_debt) == 0 or seconds_between_reports == 0: + return 0, 0 + else: + pre_fee_apr = report.gain / int(previous_report.total_debt) * (SECONDS_IN_A_YEAR / seconds_between_reports) + if report.gain_post_fees != 0: + post_fee_apr = report.gain_post_fees / int(previous_report.total_debt) * (SECONDS_IN_A_YEAR / seconds_between_reports) + return pre_fee_apr, post_fee_apr + +async def parse_fees(tx, vault_address, strategy_address, decimals, gain, vault_version): + v = int(''.join(x for x in vault_version.split('.'))) + if v < 35 and gain == 0: + return 0, 0 + denominator = 10 ** decimals + treasury = CHAIN_VALUES[chain.id]["YEARN_TREASURY"] + vault = await Contract.coroutine(vault_address) + decoder = await get_transfer_decoder(vault_address) + transfers = decoder(tx) + + amount = 0 + gov_fee_in_underlying = 0 + strategist_fee_in_underlying = 0 + counter = 0 + """ + Using the counter, we will keep track to ensure the expected sequence of fee Transfer events is followed. + Fee transfers always follow this sequence: + 1. mint + 2. transfer to strategy + 3. transfer to treasury + """ + for e in transfers: + if e.address == vault_address: + sender, receiver, token_amount = e.args.values() + token_amount = token_amount / denominator + if sender == ZERO_ADDRESS: + counter = 1 + continue + if receiver == strategy_address and counter == 1: + counter = 2 + strategist_fee_in_underlying = ( + token_amount * ( + await vault.pricePerShare.coroutine(block_identifier=tx.blockNumber) / + denominator + ) + ) + continue + if receiver == treasury and (counter == 1 or counter == 2): + counter = 0 + gov_fee_in_underlying = ( + token_amount * ( + await vault.pricePerShare.coroutine(block_identifier=tx.blockNumber) / + denominator + ) + ) + continue + elif counter == 1 or counter == 2: + counter = 0 + return gov_fee_in_underlying, strategist_fee_in_underlying + +@alru_cache(maxsize=None) +async def get_vault_endorsement_block(vault_address) -> int: + """An async wrapper for `_get_vault_endorsement_block`""" + return await sync_threads.run(_get_vault_endorsement_block, vault_address) + +@memory.cache() +def _get_vault_endorsement_block(vault_address) -> int: + token = Contract(vault_address).token() + try: + block = OLD_REGISTRY_ENDORSEMENT_BLOCKS[vault_address] + return block + except KeyError: + pass + registries = CHAIN_VALUES[chain.id]["REGISTRY_ADDRESSES"] + height = chain.height + v = '0x5B8C556B8b2a78696F0B9B830B3d67623122E270' + for r in registries: + r = Contract(r) + lo, hi = CHAIN_VALUES[chain.id]["START_BLOCK"], height + while hi - lo > 1: + mid = lo + (hi - lo) // 2 + try: + num_vaults = r.numVaults(token, block_identifier=mid) + if r.vaults(token, num_vaults-1, block_identifier=mid) == vault_address: + hi = mid + else: + lo = mid + except: + lo = mid + if hi < height: + return mid + return hi + +def normalize_event_values(vals, decimals): + denominator = 10**decimals + if len(vals) == 7: + gain, loss, total_gain, total_loss, total_debt, debt_added, debt_ratio = vals + debt_paid = 0 + if len(vals) == 8: + gain, loss, debt_paid, total_gain, total_loss, total_debt, debt_added, debt_ratio = vals + return ( + gain/denominator, + loss/denominator, + debt_paid/denominator, + total_gain/denominator, + total_loss/denominator, + total_debt/denominator, + debt_added/denominator, + debt_ratio + ) + +def prepare_alerts(r, t): + if alerts_enabled: + if r.vault_address not in INVERSE_PRIVATE_VAULTS: + m = format_public_telegram(r, t) + + # Only send to public TG and Discord on > $0 harvests + if r.gain != 0: + bot.send_message(CHAIN_VALUES[chain.id]["TELEGRAM_CHAT_ID"], m, parse_mode="markdown", disable_web_page_preview = True) + discord = Discord(url=CHAIN_VALUES[chain.id]["DISCORD_CHAN"]) + discord.post( + embeds=[{ + "title": "New harvest", + "description": m + }], + ) + + # Send to dev channel + m = f'Network: {CHAIN_VALUES[chain.id]["EMOJI"]} {CHAIN_VALUES[chain.id]["NETWORK_SYMBOL"]}\n\n' + m + format_dev_telegram(r, t) + bot.send_message(dev_channel, m, parse_mode="markdown", disable_web_page_preview = True) + else: + m = format_public_telegram_inv(r, t) + m = m + format_dev_telegram(r, t) + invbot.send_message(CHAIN_VALUES[chain.id]["TELEGRAM_CHAT_ID_INVERSE_ALERTS"], m, parse_mode="markdown", disable_web_page_preview = True) + +def format_public_telegram(r, t): + explorer = CHAIN_VALUES[chain.id]["EXPLORER_URL"] + sms = CHAIN_VALUES[chain.id]["STRATEGIST_MULTISIG"] + gov = CHAIN_VALUES[chain.id]["GOVERNANCE_MULTISIG"] + keeper_wrapper = CHAIN_VALUES[chain.id]["KEEPER_WRAPPER"] + from_indicator = "" + + if t.txn_to == sms or t.txn_to == gov: + from_indicator = "✍ " + + elif t.txn_from == r.strategist and t.txn_to != sms: + from_indicator = "🧠 " + + elif ( + t.keeper_called or + t.txn_from == KEEPER_CALL_CONTRACT or + t.txn_to == KEEPER_CALL_CONTRACT or + t.txn_to == '0xf4F748D45E03a70a9473394B28c3C7b5572DfA82' # ETH public harvest job + ): + from_indicator = "🤖 " + + elif t.txn_to == keeper_wrapper: # Permissionlessly executed by anyone + from_indicator = "🧍‍♂️ " + + message = "" + message += from_indicator + message += f' [{r.vault_name}]({explorer}address/{r.vault_address}) -- [{r.strategy_name}]({explorer}address/{r.strategy_address})\n\n' + message += f'📅 {r.date_string} UTC \n\n' + net_profit_want = "{:,.2f}".format(r.gain - r.loss) + net_profit_usd = "{:,.2f}".format(float(r.gain - r.loss) * float(r.want_price_at_block)) + sym = r.token_symbol.replace('_','-') + message += f'💰 Net profit: {net_profit_want} {sym} (${net_profit_usd})\n\n' + txn_cost_str = "${:,.2f}".format(t.call_cost_usd) + message += f'💸 Transaction Cost: {txn_cost_str} \n\n' + message += f'🔗 [View on Explorer]({explorer}tx/{r.txn_hash})' + if r.multi_harvest: + message += "\n\n_part of a single txn with multiple harvests_" + return message + +def format_public_telegram_inv(r, t): + explorer = CHAIN_VALUES[chain.id]["EXPLORER_URL"] + sms = CHAIN_VALUES[chain.id]["STRATEGIST_MULTISIG"] + gov = CHAIN_VALUES[chain.id]["GOVERNANCE_MULTISIG"] + from_indicator = "" + + message = f'👨‍🌾 New Harvest Detected!\n\n' + message += f' [{r.vault_name}]({explorer}address/{r.vault_address}) -- [{r.strategy_name}]({explorer}address/{r.strategy_address})\n' + message += f'{r.date_string} UTC \n' + net_profit_want = "{:,.2f}".format(r.gain - r.loss) + net_profit_usd = "{:,.2f}".format(float(r.gain - r.loss) * r.want_price_at_block) + sym = r.token_symbol.replace('_','-') + message += f'Net profit: {net_profit_want} {sym} (${net_profit_usd})\n' + txn_cost_str = "${:,.2f}".format(t.call_cost_usd) + message += f'Transaction Cost: {txn_cost_str} \n' + message += f'[View on Explorer]({explorer}tx/{r.txn_hash})' + if r.multi_harvest: + message += "\n\n_part of a single txn with multiple harvests_" + return message + +def format_dev_telegram(r, t): + tenderly_str = CHAIN_VALUES[chain.id]["TENDERLY_CHAIN_IDENTIFIER"] + message = f' | [Tenderly](https://dashboard.tenderly.co/tx/{tenderly_str}/{r.txn_hash})\n\n' + df = pd.DataFrame(index=['']) + last_harvest_ts = Contract(r.vault_address).strategies(r.strategy_address, block_identifier=r.block-1).dict()["lastReport"] + if last_harvest_ts == 0: + time_since_last_report = "n/a" + else: + seconds_since_report = int(time.time() - last_harvest_ts) + time_since_last_report = "%dd, %dhr, %dm" % dhms_from_seconds(seconds_since_report) + df[r.vault_name + " " + r.vault_api] = r.vault_address + df["Strategy Address"] = r.strategy_address + df["Last Report"] = time_since_last_report + df["Gain"] = "{:,.2f}".format(r.gain) + " | " + "${:,.2f}".format(r.gain * r.want_price_at_block) + df["Loss"] = "{:,.2f}".format(r.loss) + " | " + "${:,.2f}".format(r.loss * r.want_price_at_block) + if r.vault_address in INVERSE_PRIVATE_VAULTS: + fees = r.gov_fee_in_want + r.strategist_fee_in_want + inverse_profit = r.gain - fees + df["Yearn Treasury Profit"] = "{:,.2f}".format(fees) + " | " + "${:,.2f}".format(fees * r.want_price_at_block) + df["Inverse Profit"] = "{:,.2f}".format(inverse_profit) + " | " + "${:,.2f}".format(inverse_profit * r.want_price_at_block) + + else: + df["Treasury Fee"] = "{:,.2f}".format(r.gov_fee_in_want) + " | " + "${:,.2f}".format(r.gov_fee_in_want * r.want_price_at_block) + if r.strategy_address == "0xd025b85db175EF1b175Af223BD37f330dB277786": + df["Strategist Fee"] = "{:,.2f}".format(r.strategist_fee_in_want) + " | " + "${:,.2f}".format(r.strategist_fee_in_want * r.want_price_at_block) + prefee = "n/a" + postfee = "n/a" + df["Debt Paid"] = "{:,.2f}".format(r.debt_paid) + " | " + "${:,.2f}".format(r.debt_paid * r.want_price_at_block) + df["Debt Added"] = "{:,.2f}".format(r.debt_added) + " | " + "${:,.2f}".format(r.debt_added * r.want_price_at_block) + df["Total Debt"] = "{:,.2f}".format(r.total_debt) + " | " + "${:,.2f}".format(r.total_debt * r.want_price_at_block) + df["Debt Ratio"] = r.debt_ratio + if chain.id == 1 and r.keep_crv > 0: + df["CRV Locked"] = "{:,.2f}".format(r.keep_crv) + " | " + "${:,.2f}".format(r.keep_crv_value_usd) + + if r.rough_apr_pre_fee is not None: + prefee = "{:.2%}".format(r.rough_apr_pre_fee) + if r.rough_apr_post_fee is not None: + postfee = "{:.2%}".format(r.rough_apr_post_fee) + df["Pre-fee APR"] = prefee + df["Post-fee APR"] = postfee + message2 = f"```{df.T.to_string()}\n```" + return message + message2 + +def dhms_from_seconds(seconds): + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + return (days, hours, minutes) + + +class StrategyReportedEvents(ProcessedEvents): + """We subclass ProcessedEvents so our event filter can do some extra stuff with (ie: 'process') the event before yielding it to us""" + topics: list = topics + is_v030: bool = False + def __init__(self, from_block: int, dynamically_find_multi_harvest: bool) -> None: + super().__init__(topics=self.topics, from_block=from_block) + self.dynamically_find_multi_harvest = dynamically_find_multi_harvest + def _process_event(self, strategy_report_event: _EventItem) -> Event: + e = Event(self.is_v030, strategy_report_event, strategy_report_event.transaction_hash) + if self.dynamically_find_multi_harvest: + # The code below is used to populate the "multi_harvest" property # + if e.txn_hash in transaction_hashes: + e.multi_harvest = True + for i in range(len(events_to_process)): + if e.txn_hash == events_to_process[i].txn_hash: + events_to_process[i].multi_harvest = True + else: + transaction_hashes.append(strategy_report_event.transaction_hash.hex()) + events_to_process.append(e) + return e + +class StrategyReportedEventsV030(StrategyReportedEvents): + """Tweak behavior slightly for v0.3.0""" + topics = topics_v030 + is_v030 = True + +def clear_finished_tasks(tasks: List[asyncio.Task]) -> None: + for t in tasks[:]: + if t.done(): + if e := t.exception(): + raise e + tasks.remove(t) diff --git a/scripts/dome_apy_previews.py b/scripts/dome_apy_previews.py new file mode 100644 index 000000000..025b39afc --- /dev/null +++ b/scripts/dome_apy_previews.py @@ -0,0 +1,211 @@ +import dataclasses +import json +import logging +import os +import re +import shutil +import traceback +from datetime import datetime +from time import sleep, time +from typing import List + +import boto3 +import requests +import sentry_sdk +from brownie import ZERO_ADDRESS, chain +from brownie.exceptions import ContractNotFound +from multicall.utils import await_awaitable +from y import Contract, Network, PriceError +from y.exceptions import ContractNotVerified + +from yearn.apy import Apy, ApyFees, ApyPoints, ApySamples, get_samples +from yearn.apy.curve.simple import Gauge, calculate_simple +from yearn.exceptions import EmptyS3Export + +logger = logging.getLogger(__name__) +sentry_sdk.set_tag('script','curve_apy_previews') + +chains = { + Network.Optimism: '0xC5be2c918EB04B091962fDF095A217A55CFA42C5', + Network.Base: '', +} + + +def main(): + gauges = _get_gauges() + data = _build_data(gauges) + _upload(data) + +def _build_data(gauges): + samples = get_samples() + data = [] + for name, values in gauges.items(): + m = re.match('^([^\(\)]+) \(.*\)', name) + gauge_name = m[1] + try: + gauge = _extract_gauge(values) + except ContractNotVerified as e: + data.append({ + "gauge_name": gauge_name, + "apy": dataclasses.asdict(Apy("error", 0, 0, ApyFees(0, 0), ApyPoints(0, 0, 0), error_reason=str(e))), + "updated": int(time()), + "block": samples.now, + }) + continue + if not gauge: + continue + + pool_coins = [] + for i in range(4): + try: + coin_address = gauge.pool.coins(i) + except ValueError as e: + # If the execution reverted, there is no coin with index i. + if str(e) == "execution reverted": + continue + raise + + # Sometimes the call returns the zero address instead of reverting. This means there is no coin with index i. + if coin_address == ZERO_ADDRESS: + continue + + try: + if coin_address == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": + name = "ETH" + else: + c = Contract(coin_address) + name = c.name() + + pool_coins.append({"name": name, "address": coin_address}) + except (ContractNotFound, ContractNotVerified, ValueError, AttributeError) as e: + pool_coins.append({"address": coin_address, "error": str(e)}) + logger.error(f"error for coins({i}) for pool {str(gauge.pool)}") + logger.error(e) + + apy_error = Apy("error", 0, 0, ApyFees(0, 0), ApyPoints(0, 0, 0)) + try: + if gauge.gauge_weight > 0: + apy = await_awaitable(calculate_simple(None, gauge, samples)) + else: + apy = Apy("zero_weight", 0, 0, ApyFees(0, 0), ApyPoints(0, 0, 0)) + except Exception as error: + apy_error.error_reason = ":".join(str(arg) for arg in error.args) + logger.error(error) + logger.error(gauge) + apy = apy_error + + object = { + "gauge_name": gauge_name, + "gauge_address": str(gauge.gauge), + "pool_address": str(gauge.pool), + "pool_coins": pool_coins, + "lp_token": str(Contract(gauge.lp_token)), + "weight": str(gauge.gauge_weight), + "inflation_rate": str(gauge.gauge_inflation_rate), + "working_supply": str(gauge.gauge_working_supply), + "apy": dataclasses.asdict(apy), + "updated": int(time()), + "block": samples.now, + } + data.append(object) + return data + +def _extract_gauge(v): + if v["side_chain"] or v["is_killed"]: + return None + + pool_address = v["swap"] + gauge_address = v["gauge"] + gauge_data = v["gauge_data"] + gauge_controller = v["gauge_controller"] + + lp_token = v["swap_token"] + pool = Contract(pool_address) + gauge = Contract(gauge_address) + weight = int(gauge_controller["gauge_relative_weight"]) + inflation_rate = int(gauge_data["inflation_rate"]) + working_supply = int(gauge_data["working_supply"]) + + return Gauge(lp_token, pool, gauge, weight, inflation_rate, working_supply) + + +def _get_gauges() -> List[str]: + if chain.id not in chains: + raise ValueError(f"can't get velo gauges for unsupported network: {chain.id}") + gauge_factory = Contract(chains[chain.id]) + return [pool for pool in pools] + + +def _upload(data): + print(json.dumps(data, sort_keys=True, indent=4)) + + file_name, s3_path = _get_export_paths("curve-factory") + with open(file_name, "w+") as f: + json.dump(data, f) + + if os.getenv("DEBUG", None): + return + + aws_bucket = os.environ.get("AWS_BUCKET") + + s3 = _get_s3() + s3.upload_file( + file_name, + aws_bucket, + s3_path, + ExtraArgs={'ContentType': "application/json", 'CacheControl': "max-age=1800"}, + ) + + +def _get_s3(): + aws_key = os.environ.get("AWS_ACCESS_KEY") + aws_secret = os.environ.get("AWS_ACCESS_SECRET") + + kwargs = {} + if aws_key is not None: + kwargs["aws_access_key_id"] = aws_key + if aws_secret is not None: + kwargs["aws_secret_access_key"] = aws_secret + + return boto3.client("s3", **kwargs) + + +def _get_export_paths(suffix): + out = "generated" + if os.path.isdir(out): + shutil.rmtree(out) + os.makedirs(out, exist_ok=True) + + api_path = os.path.join("v1", "chains", f"{chain.id}", "apy-previews") + + file_base_path = os.path.join(out, api_path) + os.makedirs(file_base_path, exist_ok=True) + + file_name = os.path.join(file_base_path, suffix) + s3_path = os.path.join(api_path, suffix) + return file_name, s3_path + +def with_monitoring(): + if os.getenv("DEBUG", None): + main() + return + from telegram.ext import Updater + + private_group = os.environ.get('TG_YFIREBOT_GROUP_INTERNAL') + public_group = os.environ.get('TG_YFIREBOT_GROUP_EXTERNAL') + updater = Updater(os.environ.get('TG_YFIREBOT')) + now = datetime.now() + message = f"`[{now}]`\n⚙️ Curve Previews API for {Network.name()} is updating..." + ping = updater.bot.send_message(chat_id=private_group, text=message, parse_mode="Markdown") + ping = ping.message_id + try: + main() + except Exception as error: + tb = traceback.format_exc() + now = datetime.now() + message = f"`[{now}]`\n🔥 Curve Previews API update for {Network.name()} failed!\n```\n{tb}\n```"[:4000] + updater.bot.send_message(chat_id=private_group, text=message, parse_mode="Markdown", reply_to_message_id=ping) + updater.bot.send_message(chat_id=public_group, text=message, parse_mode="Markdown") + raise error + message = f"✅ Curve Previews API update for {Network.name()} successful!" + updater.bot.send_message(chat_id=private_group, text=message, reply_to_message_id=ping) \ No newline at end of file diff --git a/scripts/drome_apy_previews.py b/scripts/drome_apy_previews.py index 375703f0e..87ea62d24 100644 --- a/scripts/drome_apy_previews.py +++ b/scripts/drome_apy_previews.py @@ -7,6 +7,7 @@ import dataclasses import logging import os +from decimal import InvalidOperation from pprint import pformat from time import time from typing import List, Optional @@ -30,6 +31,8 @@ logger = logging.getLogger(__name__) sentry_sdk.set_tag('script','curve_apy_previews') +logging.getLogger('y.utils.events').setLevel(logging.DEBUG) +logging.getLogger('y._db.common').setLevel(logging.DEBUG) class Drome(Struct): """Holds various params for a drome deployment""" @@ -85,7 +88,7 @@ async def _build_data(): async def _get_lps_with_vault_potential() -> List[dict]: sugar_oracle = await Contract.coroutine(drome.sugar) - current_vaults = Registry(watch_events_forever=False, include_experimental=False).vaults + current_vaults = await Registry(include_experimental=False).vaults current_underlyings = [str(vault.token) for vault in current_vaults] return [lp for lp in await sugar_oracle.all.coroutine(999999999999999999999, 0, ZERO_ADDRESS) if lp[0] not in current_underlyings and lp[11] != ZERO_ADDRESS] @@ -105,6 +108,8 @@ async def _build_data_for_lp(lp: dict, block: Optional[int] = None) -> Optional[ try: apy = await _staking_apy(lp, gauge.gauge, block=block) if gauge.gauge_weight > 0 else Apy("zero_weight", 0, 0, fees) except Exception as error: + if isinstance(error, InvalidOperation): + raise error logger.error(error) logger.error(gauge) apy = Apy("error", 0, 0, fees, error_reason=":".join(str(arg) for arg in error.args)) diff --git a/scripts/exporters/sms.py b/scripts/exporters/sms.py index d9380dce4..72dc8a134 100644 --- a/scripts/exporters/sms.py +++ b/scripts/exporters/sms.py @@ -2,7 +2,8 @@ import logging import sentry_sdk -from y.networks import Network +from brownie import chain +from y import Network from yearn import constants from yearn.helpers.exporter import Exporter @@ -25,4 +26,6 @@ ) def main(): + if chain.id == Network.Base: + return exporter.run() diff --git a/scripts/exporters/transactions.py b/scripts/exporters/transactions.py index 23752d034..5624598ba 100644 --- a/scripts/exporters/transactions.py +++ b/scripts/exporters/transactions.py @@ -5,31 +5,35 @@ from decimal import Decimal from typing import Optional +import dank_mids import pandas as pd import sentry_sdk +from async_lru import alru_cache from brownie import ZERO_ADDRESS, chain, web3 from brownie.exceptions import BrownieEnvironmentWarning +from brownie.network.event import _EventItem from multicall.utils import await_awaitable from pony.orm import db_session from web3._utils.abi import filter_by_name from web3._utils.events import construct_event_topic_set -from y.networks import Network +from y import ERC20, Network, get_block_timestamp_async from y.utils.events import get_logs_asap from yearn.entities import UserTx from yearn.events import decode_logs from yearn.exceptions import BatchSizeError -from yearn.outputs.postgres.utils import (cache_address, cache_chain, - cache_token, last_recorded_block) +from yearn.outputs.postgres.utils import (address_dbid, chain_dbid, cache_token, + last_recorded_block, token_dbid) from yearn.prices.magic import _get_price from yearn.typing import Block +from yearn.utils import threads from yearn.yearn import Yearn sentry_sdk.set_tag('script','transactions_exporter') warnings.simplefilter("ignore", BrownieEnvironmentWarning) -yearn = Yearn(load_strategies=False) +yearn = Yearn() logger = logging.getLogger('yearn.transactions_exporter') @@ -39,7 +43,7 @@ Network.Gnosis: 2_000_000, Network.Arbitrum: 1_500_000, Network.Optimism: 4_000_000, - Network.Base: 500_000, + Network.Base: 2_000_000, }[chain.id] FIRST_END_BLOCK = { @@ -54,15 +58,16 @@ def main(): _cached_thru_from_last_run = 0 while True: + start = time.time() cached_thru = last_recorded_block(UserTx) _check_for_infinite_loop(_cached_thru_from_last_run, cached_thru) - process_and_cache_user_txs(cached_thru) + await_awaitable(process_and_cache_user_txs(cached_thru)) _cached_thru_from_last_run = cached_thru - time.sleep(1) + # minimum 5m loop interval reduces node usage massively + if sleep_time := max(0, (start + 5 * 60) - time.time()): + time.sleep(sleep_time) - -@db_session -def process_and_cache_user_txs(last_saved_block=None): +async def process_and_cache_user_txs(last_saved_block=None): # NOTE: We look 50 blocks back to avoid uncles and reorgs max_block_to_cache = chain.height - 50 start_block = last_saved_block + 1 if last_saved_block else None @@ -73,38 +78,42 @@ def process_and_cache_user_txs(last_saved_block=None): ) if start_block and start_block > end_block: end_block = start_block - vaults = await_awaitable(yearn.active_vaults_at(end_block)) - df = pd.concat(await_awaitable(asyncio.gather(*[get_token_transfers(vault.vault, start_block, end_block) for vault in vaults]))) if vaults else pd.DataFrame() + vaults = await yearn.active_vaults_at(end_block) + df = pd.concat(await asyncio.gather(*[get_token_transfers(vault.vault, start_block, end_block) for vault in vaults])) if vaults else pd.DataFrame() if len(df): # NOTE: We want to insert txs in the order they took place, so wallet exporter # won't have issues in the event that transactions exporter fails mid-run. df = df.sort_values('block') - for index, row in df.iterrows(): - # this addresses one tx with a crazy price due to yvpbtc v1 pricePerFullShare bug. - price = row.price if len(str(round(row.price))) <= 20 else 99999999999999999999 - usd = row.value_usd if len(str(round(row.value_usd))) <= 20 else 99999999999999999999 - - UserTx( - chain=cache_chain(), - vault=cache_token(row.token), - timestamp=row.timestamp, - block=row.block, - hash=row.hash, - log_index=row.log_index, - type=row.type, - from_address=cache_address(row['from']), - to_address=cache_address(row['to']), - amount = row.amount, - price = price, - value_usd = usd, - gas_used = row.gas_used, - gas_price = row.gas_price - ) + await asyncio.gather(*(insert_user_tx(row) for index, row in df.iterrows())) if start_block == end_block: logger.info(f'{len(df)} user txs exported to postrges [block {start_block}]') else: logger.info(f'{len(df)} user txs exported to postrges [blocks {start_block}-{end_block}]') +async def insert_user_tx(row) -> None: + chain_pk = chain_dbid() + vault_dbid, from_address_dbid, to_address_dbid = await asyncio.gather(threads.run(token_dbid, row.token), threads.run(address_dbid, row['from']), threads.run(address_dbid, row['to'])) + # this addresses one tx with a crazy price due to yvpbtc v1 pricePerFullShare bug. + price = row.price if len(str(round(row.price))) <= 20 else 99999999999999999999 + usd = row.value_usd if len(str(round(row.value_usd))) <= 20 else 99999999999999999999 + + await threads.run( + db_session(UserTx), + chain=chain_pk, + vault=vault_dbid, + timestamp=row.timestamp, + block=row.block, + hash=row.hash, + log_index=row.log_index, + type=row.type, + from_address=from_address_dbid, + to_address=to_address_dbid, + amount = row.amount, + price = Decimal(price), + value_usd = Decimal(usd), + gas_used = row.gas_used, + gas_price = row.gas_price, + ) # Helper functions async def get_token_transfers(token, start_block, end_block) -> pd.DataFrame: @@ -112,50 +121,55 @@ async def get_token_transfers(token, start_block, end_block) -> pd.DataFrame: filter_by_name('Transfer', token.abi)[0], web3.codec, ) - events = decode_logs( - await get_logs_asap(token.address, topics, from_block=start_block, to_block=end_block, sync=False) - ) - token_entity = cache_token(token.address) - transfers = await asyncio.gather(*[_process_transfer_event(event, token_entity) for event in events]) + logs = await get_logs_asap(token.address, topics, from_block=start_block, to_block=end_block, sync=False) + transfers = await asyncio.gather(*[_process_transfer_event(event) for event in decode_logs(logs)]) return pd.DataFrame(transfers) -async def _process_transfer_event(event, token_entity) -> dict: +async def _process_transfer_event(event: _EventItem) -> dict: sender, receiver, amount = event.values() - cache_address(sender) - cache_address(receiver) - price = await get_price(token_entity.address.address, event.block_number) + txhash = event.transaction_hash.hex() + tx, tx_receipt, timestamp, token_dbid, price, scale = await asyncio.gather( + dank_mids.eth.get_transaction(txhash), + dank_mids.eth.get_transaction_receipt(txhash), + get_block_timestamp_async(event.block_number), + get_token_dbid(event.address), # NOTE: we don't use this output but we call this to ensure the token fk is in the db before we insert the transfer + get_price(event.address, event.block_number), + ERC20(event.address, asynchronous=True).scale, + ) if ( # NOTE magic.get_price() returns erroneous price due to erroneous ppfs - token_entity.address.address == '0x7F83935EcFe4729c4Ea592Ab2bC1A32588409797' + event.address == '0x7F83935EcFe4729c4Ea592Ab2bC1A32588409797' and event.block_number == 12869164 ): price = 99999 - txhash = event.transaction_hash.hex() return { 'chainid': chain.id, 'block': event.block_number, - 'timestamp': chain[event.block_number].timestamp, + 'timestamp': int(timestamp), 'hash': txhash, 'log_index': event.log_index, - 'token': token_entity.address.address, - 'type': _event_type(sender, receiver, token_entity.address.address), + 'token': event.address, + 'type': _event_type(sender, receiver, event.address), 'from': sender, 'to': receiver, - 'amount': Decimal(amount) / Decimal(10 ** token_entity.decimals), + 'amount': Decimal(amount) / Decimal(scale), 'price': price, - 'value_usd': Decimal(amount) / Decimal(10 ** token_entity.decimals) * Decimal(price), - 'gas_used': web3.eth.getTransactionReceipt(txhash).gasUsed, - 'gas_price': web3.eth.getTransaction(txhash).gasPrice + 'value_usd': Decimal(amount) / Decimal(scale) * Decimal(price), + 'gas_used': tx_receipt.gasUsed, + 'gas_price': tx.gasPrice } +@alru_cache(maxsize=None) +async def get_token_dbid(address: str) -> int: + return await threads.run(token_dbid, address) async def get_price(token_address, block): try: return await _get_price(token_address, block) except Exception as e: - logger.warn(f'{e.__class__.__name__}: {str(e)}') - logger.warn(f'vault: {token_address} block: {block}') + logger.warning(f'{e.__class__.__name__}: {str(e)}') + logger.warning(f'vault: {token_address} block: {block}') raise e diff --git a/scripts/exporters/treasury_transactions.py b/scripts/exporters/treasury_transactions.py index 8c5f48fd8..a7db783f1 100644 --- a/scripts/exporters/treasury_transactions.py +++ b/scripts/exporters/treasury_transactions.py @@ -17,10 +17,9 @@ from y.datatypes import Block from y.time import get_block_timestamp_async -from yearn.entities import TreasuryTx, db -from yearn.outputs.postgres.utils import (cache_address, cache_chain, - cache_token) -from yearn.treasury import accountant +from yearn.entities import TreasuryTx, deduplicate_internal_transfers +from yearn.outputs.postgres.utils import address_dbid, chain_dbid, token_dbid +from yearn.treasury import accountant, streams from yearn.treasury.treasury import YearnTreasury sentry_sdk.set_tag('script','treasury_transactions_exporter') @@ -29,6 +28,8 @@ logger = logging.getLogger('yearn.treasury_transactions_exporter') +GNOSIS_SINGLETON = "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552" + treasury = YearnTreasury(load_prices=True, asynchronous=True) @@ -45,23 +46,38 @@ def main() -> NoReturn: to_sort = load_new_txs(start_block, end_block) deduplicate_internal_transfers() if to_sort > 0: - sort() + pass + #sort() else: time.sleep(10) cached_thru = end_block + logger.info('batch done') @a_sync(default='sync') async def load_new_txs(start_block: Block, end_block: Block) -> int: - futs = [ - asyncio.create_task(insert_treasury_tx(entry)) - async for entry in treasury.ledger._get_and_yield(start_block, end_block) - if not isinstance(entry, _Done) and entry.value - ] + """returns: number of new txs""" + # NOTE: ensure stream loader task has been started + global _streams_task + if _streams_task is None: + _streams_task = asyncio.create_task(streams._get_coro()) + futs = [] + async for entry in treasury.ledger[start_block: end_block]: + if isinstance(entry, InternalTransfer) and entry.to_address == GNOSIS_SINGLETON: + # TODO: move this into eth-port + logger.debug("internal transfer to gnosis singleton, these are goofy and not real. ignoring %s", entry) + continue + if not entry.value: + logger.debug("zero value transfer, skipping %s", entry) + continue + futs.append(asyncio.create_task(insert_treasury_tx(entry))) + if not futs: + return 0 to_sort = sum(await tqdm_asyncio.gather(*futs, desc="Insert Txs to Postgres")) return to_sort + # NOTE: Things get sketchy when we bump these higher insert_thread = AsyncThreadPoolExecutor(1) sort_thread = AsyncThreadPoolExecutor(1) @@ -70,33 +86,33 @@ async def load_new_txs(start_block: Block, end_block: Block) -> int: sort_later = lambda entry: isinstance(entry, TokenTransfer) async def insert_treasury_tx(entry: LedgerEntry) -> int: - ts = await get_block_timestamp_async(entry.block_number) + ts = int(await get_block_timestamp_async(entry.block_number)) if txid := await insert_thread.run(insert_to_db, entry, ts): if sort_later(entry): return 1 - await sort_thread.run(accountant.sort_tx, txid) + return 1 + #await sort_thread.run(accountant.sort_tx, txid) return 0 - @db_session -def insert_to_db(entry: LedgerEntry, ts: int) -> bool: +def insert_to_db(entry: LedgerEntry, ts: int) -> int: if isinstance(entry, TokenTransfer): log_index = entry.log_index - token = cache_token(entry.token_address) + token = token_dbid(entry.token_address) gas = None else: log_index = None - token = cache_token(EEE_ADDRESS) + token = token_dbid(EEE_ADDRESS) gas = entry.gas try: entity = TreasuryTx( - chain=cache_chain(), + chain=chain_dbid(), block = entry.block_number, timestamp = ts, hash = entry.hash, log_index = log_index, - from_address = cache_address(entry.from_address), - to_address = cache_address(entry.to_address) if entry.to_address else None, + from_address = address_dbid(entry.from_address) if entry.from_address else None, + to_address = address_dbid(entry.to_address) if entry.to_address else None, token = token, amount = entry.value, price = entry.price, @@ -116,8 +132,14 @@ def insert_to_db(entry: LedgerEntry, ts: int) -> bool: @db_session def _validate_integrity_error(entry: LedgerEntry, log_index: int) -> None: ''' Raises AssertionError if existing object that causes a TransactionIntegrityError is not an EXACT MATCH to the attempted insert. ''' - existing_object = TreasuryTx.get(hash=entry.hash, log_index=log_index, chain=cache_chain()) - assert entry.to_address == existing_object.to_address.address, (entry.to_address,existing_object.to_address.address) + existing_object = TreasuryTx.get(hash=entry.hash, log_index=log_index, chain=chain_dbid()) + if existing_object is None: + existing_objects = list(TreasuryTx.select(lambda tx: tx.hash==entry.hash and tx.log_index==log_index and tx.chain==chain_dbid())) + raise ValueError(f'unable to `.get` due to multiple entries: {existing_objects}') + if entry.to_address: + assert entry.to_address == existing_object.to_address.address, (entry.to_address, existing_object.to_address.address) + else: + assert existing_object.to_address is None, (entry.to_address, existing_object.to_address) assert entry.from_address == existing_object.from_address.address, (entry.from_address, existing_object.from_address.address) assert entry.value == existing_object.amount or entry.value == -1 * existing_object.amount, (entry.value, existing_object.amount) assert entry.block_number == existing_object.block, (entry.block_number, existing_object.block) @@ -131,16 +153,4 @@ def _validate_integrity_error(entry: LedgerEntry, log_index: int) -> None: def sort() -> None: accountant.sort_txs(accountant.unsorted_txs()) -@db_session -def deduplicate_internal_transfers(): - db.execute( - """ - WITH counted AS ( - SELECT treasury_tx_id, ROW_NUMBER() over(partition BY CHAIN, TIMESTAMP, block, hash, log_index, token_id, "from", "to", amount, gas_used, gas_price ORDER BY treasury_tx_id ASC) number - FROM treasury_txs - ) - DELETE FROM treasury_txs WHERE treasury_tx_id IN ( - SELECT treasury_tx_id FROM counted WHERE NUMBER > 1 - ) - """ - ) \ No newline at end of file +_streams_task = None \ No newline at end of file diff --git a/scripts/exporters/wallets.py b/scripts/exporters/wallets.py index 6904cabd9..b3f7a09f0 100644 --- a/scripts/exporters/wallets.py +++ b/scripts/exporters/wallets.py @@ -8,19 +8,18 @@ from y import Network from y.time import closest_block_after_timestamp -from yearn import constants +from yearn import constants, utils from yearn.entities import UserTx from yearn.helpers.exporter import Exporter from yearn.outputs.postgres.utils import last_recorded_block from yearn.outputs.victoria.victoria import _post -from yearn.utils import run_in_thread from yearn.yearn import Yearn sentry_sdk.set_tag('script','wallet_exporter') logger = logging.getLogger('yearn.wallet_exporter') -yearn = Yearn(load_strategies=False, watch_events_forever=False) +yearn = Yearn() # start: 2020-02-12 first iearn deployment # start opti: 2022-01-01 an arbitrary start timestamp because the default start is < block 1 on opti and messes things up @@ -44,7 +43,7 @@ def postgres_ready(snapshot: datetime) -> bool: class WalletExporter(Exporter): async def export_historical_snapshot(self, snapshot: datetime) -> None: """ Override a method on Exporter so we can add an additional check. """ - if await run_in_thread(postgres_ready, snapshot): + if await utils.threads.run(postgres_ready, snapshot): return await super().export_historical_snapshot(snapshot) exporter = WalletExporter( diff --git a/scripts/gather.py b/scripts/gather.py new file mode 100644 index 000000000..c602f798f --- /dev/null +++ b/scripts/gather.py @@ -0,0 +1,132 @@ +import pandas as pd +import asyncio +import logging +import os +import time +from datetime import datetime +from brownie import convert +from eth_portfolio import Portfolio, SHITCOINS +from eth_portfolio.typing import PortfolioBalances +from y import Contract, get_block_at_timestamp +from brownie._config import CONFIG + +# type hints +start_portfolio: PortfolioBalances +end_portfolio: PortfolioBalances + +# settings +repull_data = True +CONFIG.settings["autofetch_sources"] = False +logging.basicConfig(level=getattr(logging, os.getenv("LOG_LEVEL", "INFO").upper())) + +month = '20240203' +begin_date = datetime(2023, 11, 1, 0, 0) +end_date = datetime(2024, 4, 1, 0, 0) + +treasury_wallets = { + convert.to_address(address) for address in [ + '0x5f0845101857d2A91627478e302357860b1598a1', + '0x93A62dA5a14C80f265DAbC077fCEE437B1a0Efde', + '0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52', + '0xb99a40fcE04cb740EB79fC04976CA15aF69AaaaE', + '0xC001d00d425Fa92C4F840baA8f1e0c27c4297a0B', + ] +} + +# shitcoins +SHITCOINS[1].update({ + "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", + "0x1d41cf24dF81E3134319BC11c308c5589A486166", + "0xD1E5b0FF1287aA9f9A268759062E4Ab08b9Dacbe", + "0xE256CF1C7caEff4383DabafEe6Dd53910F97213D", + "0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77", + "0xd7C9F0e536dC865Ae858b0C0453Fe76D13c3bEAc", + "0x7C07F7aBe10CE8e33DC6C5aD68FE033085256A84", + "0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD", + "0x73968b9a57c6E53d41345FD57a6E6ae27d6CDB2F", + "0x1fc4DB3e7f9124bAAFB7B34346dB7931Dad621C2", + "0x85D385244D41ac914484FD6fbBaB177c10A86e79", + "0x053D7DD4dde2B5e4F6146476B95EA8c62cd7c428", + "0xF3799CBAe9c028c1A590F4463dFF039b8F4986BE", + "0x3f6740b5898c5D3650ec6eAce9a649Ac791e44D7", + "0xD7aBCFd05a9ba3ACbc164624402fB2E95eC41be6", + "0xeaaa790591c646b0436f02f63e8Ca56209FEDE4E", + "0xeF81c2C98cb9718003A89908e6bd1a5fA8A098A3", + "0x01234567bac6fF94d7E4f0EE23119CF848F93245", +}) + +loggers_to_silence = [ + "a_sync.a_sync.abstract", + "a_sync.a_sync.function", + "a_sync.a_sync.method", + "a_sync.a_sync.property", + "a_sync.primitives.locks.prio_semaphore", + "a_sync.utils.iterators", + "eth_portfolio._ydb", +] + +for l in loggers_to_silence: + logging.getLogger(l).setLevel(logging.INFO) + +# startup +begin_block = get_block_at_timestamp(begin_date, sync=True) +end_block = get_block_at_timestamp(end_date, sync=True) +print(begin_block) +print(end_block) + +data_folder = os.path.join('.','data') +month_data_folder = os.path.join(data_folder, month) +if not os.path.exists(month_data_folder): + os.makedirs(month_data_folder) + + +if repull_data: + start_time = time.time() + + port = Portfolio(treasury_wallets, load_prices=False) + + coros = asyncio.gather(port.describe(begin_block, sync=False), port.describe(end_block, sync=False)) + start_portfolio, end_portfolio = asyncio.get_event_loop().run_until_complete(coros) + + # yay, easy! + start_df = start_portfolio.dataframe + end_df = end_portfolio.dataframe + + start_df.to_csv(os.path.join('data', month, 'balance_sheet_bom.csv')) + end_df.to_csv(os.path.join('data', month, 'balance_sheet_eom.csv')) + print("--- %s seconds ---" % (time.time() - start_time)) +else: + start_df = pd.read_csv(os.path.join('data', month, 'balance_sheet_bom.csv'), index_col = 0) + end_df = pd.read_csv(os.path.join('data', month, 'balance_sheet_eom.csv'), index_col = 0) + +print(start_df) +print(start_df.columns) + +s_summary = start_df.groupby(['category','token'])[['balance','usd_value']].sum().sort_values('usd_value') +e_summary = end_df.groupby(['category','token'])[['balance','usd_value']].sum().sort_values('usd_value') + +r_summary = pd.merge(e_summary, s_summary, how='outer', suffixes=('_e', '_s'), left_index = True, right_index=True) + +r_summary.fillna(0, inplace=True) + +def get_symbol(x): + try: + c = Contract(x) + return c.symbol() if hasattr(c, 'symbol') else x + except Exception as e: + print(x) + print(e) + return x + +r_summary['diff_balance'] = r_summary['balance_e'] - r_summary['balance_s'] +r_summary['diff_value_usd'] = r_summary['usd_value_e'] - r_summary['usd_value_s'] + + +r_summary + +r_summary.reset_index(inplace=True) +r_summary['symbol'] = r_summary['token'].apply(get_symbol) + + +r_summary.to_csv(os.path.join(data_folder,month,'balance_sheet.csv')) \ No newline at end of file diff --git a/scripts/historical_tvl.py b/scripts/historical_tvl.py index d65404746..eedde109b 100644 --- a/scripts/historical_tvl.py +++ b/scripts/historical_tvl.py @@ -29,7 +29,7 @@ def generate_snapshot_range(start, interval): def main(): - yearn = Yearn(load_strategies=False) + yearn = Yearn() start = START_DATE[chain.id] interval = timedelta(hours=24) diff --git a/scripts/load_streams.py b/scripts/load_streams.py index 12cd89d91..cd189ecbf 100644 --- a/scripts/load_streams.py +++ b/scripts/load_streams.py @@ -1,185 +1,14 @@ import asyncio -import typing -from datetime import date, datetime, timedelta -from decimal import Decimal -from functools import lru_cache -from typing import Tuple, Optional -from a_sync import AsyncThreadPoolExecutor import logging -from async_lru import alru_cache -from brownie import chain -from pony.orm import db_session -from tqdm import tqdm -from tqdm.asyncio import tqdm_asyncio -from y import Contract, get_price -from y.time import closest_block_after_timestamp_async -from yearn.entities import Stream, StreamedFunds, create_views -from yearn.treasury.constants import BUYER -from yearn.treasury.streams import YearnStreams -from yearn.utils import dates_generator +from yearn.entities import create_views +from yearn.treasury import streams logger = logging.getLogger(__name__) create_views() -streams = YearnStreams() -# TODO: Fit this better into the rest of the exporter so it runs in tandem with treasury txs exporter - -@db_session def main(): - #print('buybacks active stream(s)') - for stream in streams.buyback_streams(): - #stream.print() - pass - - team_dai_streams = [stream for stream in all_other_dai_streams() if int(stream.amount_per_second) == 385802469135802432] - - #print(f'{len(team_dai_streams)} team dai streams') - total_rate = 0 - for i, stream in enumerate(team_dai_streams): - total_rate += stream.amount_per_second - total_rate /= stream.scale - #print(f'team dai per second: {total_rate}') - #print(f'team dai per day: {total_rate * 60 * 60 * 24}') - - v3_multisig_i_think = "0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7" - v3_dai_streams = [stream for stream in all_other_dai_streams() if stream.to_address.address == v3_multisig_i_think] - - print(f'{len(v3_dai_streams)} v3 dai streams') - total_rate = 0 - for i, stream in enumerate(v3_dai_streams): - total_rate += stream.amount_per_second - total_rate /= stream.scale - print(f'v3 dai per second: {total_rate}') - print(f'v3 dai per day: {total_rate * 60 * 60 * 24}') - - misc_dai_streams = [stream for stream in all_other_dai_streams() if stream not in team_dai_streams and stream.to_address.address != v3_multisig_i_think] - - print('all other active streams') - total_rate = 0 - for i, stream in enumerate(misc_dai_streams): - print(f'stream {i}') - total_rate += stream.amount_per_second - #print(stream.amount_per_second) - stream.print() - total_rate /= stream.scale - print(f'all other streams dai per second: {total_rate}') - print(f'all other streams dai per day: {total_rate * 60 * 60 * 24}') - - yfi_streams = streams.yfi_streams() - - normal_daily_rate = Decimal(5.2950767511632e-07) * 60 * 60 * 24 - print(f'{len(yfi_streams)} active yfi streams') - total_rate = 0 - for i, stream in enumerate(yfi_streams): - rate = stream.amount_per_second - total_rate += rate - if rate != 5.2950767511632e-07: - print(f'stream {i}') - stream.print() - print(f'normal YFI per day: {normal_daily_rate}') - daily_rate = rate * 60 * 60 * 24 - daily_difference = daily_rate - normal_daily_rate - print(f'daily difference: {daily_difference} YFI') - print(f'monthly difference: {daily_difference * 30} YFI') - print(f'recipient: {stream.to_address.address}') - - print('') - print(f'total yfi per second: {total_rate}') - print(f'total yfi per day: {total_rate * 60 * 60 * 24}') - - asyncio.get_event_loop().run_until_complete(process_streams()) - -@db_session -def all_other_dai_streams(): - return [s for s in streams.dai_streams() if s.to_address.address != BUYER] - - -threads = AsyncThreadPoolExecutor(8) - -@lru_cache -@db_session -def _get_token_for_stream(stream_id): - return Stream[stream_id].token.address.address - -@db_session -def _get_stream_contract(stream_id: str) -> str: - return Stream[stream_id].contract.address - -@db_session -def get_start_date(stream_id: str) -> date: - return Stream[stream_id].start_date - -@db_session -def is_closed(stream_id: str) -> bool: - return bool(StreamedFunds.get(stream=Stream[stream_id], is_last_day=True)) - -@alru_cache -async def get_stream_contract(stream_id: str) -> Contract: - address = await threads.run(_get_stream_contract, stream_id) - return await Contract.coroutine(address) - -async def start_timestamp(stream_id: str, block: typing.Optional[int] = None) -> int: - contract = await get_stream_contract(stream_id) - return int(await contract.streamToStart.coroutine(f'0x{stream_id}', block_identifier=block)) - -ONE_DAY = 60 * 60 * 24 - -async def process_stream_for_date(stream_id: str, date: datetime) -> Optional[StreamedFunds]: - if entity := await threads.run(StreamedFunds.get_entity, stream_id, date): - return entity - - stream_token = _get_token_for_stream(stream_id) - check_at = date + timedelta(days=1) - timedelta(seconds=1) - block = await closest_block_after_timestamp_async(int(check_at.timestamp())) - price_fut = asyncio.create_task(get_price(stream_token, block, sync=False)) - _start_timestamp = await start_timestamp(stream_id, block) - if _start_timestamp == 0: - # If the stream was already closed, we can return `None`. - if await threads.run(is_closed, stream_id): - price_fut.cancel() - return None - - while _start_timestamp == 0: - # is active last block? - block -= 1 - _start_timestamp = await start_timestamp(stream_id, block) - - block_datetime = datetime.fromtimestamp(chain[block].timestamp) - assert block_datetime.date() == date.date() - seconds_active = (check_at - block_datetime).seconds - is_last_day = True - else: - seconds_active = int(check_at.timestamp()) - _start_timestamp - is_last_day = False - - # How many seconds was the stream active on `date`? - seconds_active_today = seconds_active if seconds_active < ONE_DAY else ONE_DAY - if seconds_active_today < ONE_DAY and not is_last_day: - if date.date() == await threads.run(get_start_date, stream_id): - logger.debug('stream started today, partial day accepted') - else: - seconds_active_today = ONE_DAY - logger.debug(F"active for {seconds_active_today} seconds on {date.date()}") - if is_last_day: - logger.debug('is last day') - - price = Decimal(await price_fut) - return await threads.run(StreamedFunds.create_entity, stream_id, date, price, seconds_active_today, is_last_day) - -def get_start_and_end(stream: Stream) -> Tuple[datetime, datetime]: - start_timestamp = datetime.fromtimestamp(chain[stream.start_block].timestamp) - end_timestamp = datetime.fromtimestamp(chain[stream.end_block].timestamp) if stream.end_block else datetime.utcnow() - return start_timestamp, end_timestamp - -async def process_stream(stream, run_forever: bool = False) -> None: - # NOTE: We need to go one-by-one for the math to be right - async for date in dates_generator(*get_start_and_end(stream), stop_at_today=not run_forever): - if await process_stream_for_date(stream.stream_id, date) is None: - return - -async def process_streams(run_forever: bool = False): - await tqdm_asyncio.gather(*[process_stream(stream, run_forever=run_forever) for stream in streams.streams(include_inactive=True)], desc='Loading streams') + asyncio.run(streams._get_coro()) diff --git a/scripts/print_strategies.py b/scripts/print_strategies.py index 1fd8b20d4..98c535682 100644 --- a/scripts/print_strategies.py +++ b/scripts/print_strategies.py @@ -2,6 +2,7 @@ import click import sentry_sdk +from multicall.utils import await_awaitable from brownie.utils.output import build_tree sentry_sdk.set_tag('script','print_strategies') @@ -11,7 +12,6 @@ def main(): from yearn.v2.registry import Registry registry = Registry() print(registry) - registry.load_strategies() tree = [] for vault in registry.vaults: transforms = { @@ -28,7 +28,7 @@ def main(): 'totalLoss': lambda tokens: f'{tokens / vault.scale}', } strategies = [] - for strategy in vault.strategies + vault.revoked_strategies: + for strategy in await_awaitable(vault.strategies) + await_awaitable(vault.revoked_strategies): config = vault.vault.strategies(strategy.strategy).dict() color = 'green' if strategy in vault.strategies else 'red' strategies.append([ diff --git a/scripts/revenues.py b/scripts/revenues.py index 59a382f46..5c71b7936 100644 --- a/scripts/revenues.py +++ b/scripts/revenues.py @@ -1,13 +1,12 @@ -import os -import psycopg2 import csv -import boto3 -import sentry_sdk import logging -import yearn +import os import traceback from datetime import datetime, timedelta -from y import Network + +import boto3 +import psycopg2 +import sentry_sdk sentry_sdk.set_tag('script','revenues') logger = logging.getLogger(__name__) @@ -21,6 +20,7 @@ def main(): file_name = f"revenues_{from_str}_{to_str}.csv" file_path = f"/tmp/{file_name}" + _validate_completeness(to_str) _export_data(from_str, to_str, file_path) if debug: @@ -47,7 +47,12 @@ def main(): os.remove(file_path) logger.info("deleted file '%s'", file_path) - +def _validate_completeness(to_str): + conn = _get_db_connection() + cur = conn.cursor() + cur.execute("SELECT max(timestamp::date) from treasury_txs") + assert cur.fetchone() > datetime.strptime(to_str) + def _export_data(from_str, to_str, file_path): sql = f"COPY (\ SELECT * FROM general_ledger \ diff --git a/scripts/s3.py b/scripts/s3.py index f3bb9a970..4790b992d 100644 --- a/scripts/s3.py +++ b/scripts/s3.py @@ -7,6 +7,7 @@ import shutil import traceback import warnings +from contextlib import suppress from datetime import datetime from decimal import Decimal from functools import lru_cache @@ -27,11 +28,9 @@ from yearn import logs from yearn.apy import (Apy, ApyBlocks, ApyFees, ApyPoints, ApySamples, get_samples) -from yearn.common import Tvl from yearn.exceptions import EmptyS3Export from yearn.graphite import send_metric from yearn.special import Backscratcher, YveCRVJar -from yearn.utils import chunks, contract from yearn.v1.registry import Registry as RegistryV1 from yearn.v1.vaults import VaultV1 from yearn.v2.registry import Registry as RegistryV2 @@ -74,7 +73,8 @@ async def wrap_vault( } ] else: - strategies = [{"address": str(strategy.strategy), "name": strategy.name} for strategy in vault.strategies] + strategies = await vault.strategies if isinstance(vault, VaultV2) else vault.strategies + strategies = [{"address": str(strategy.strategy), "name": strategy.name} for strategy in strategies] token_alias = aliases[str(vault.token)]["symbol"] if str(vault.token) in aliases else await ERC20(vault.token, asynchronous=True).symbol vault_alias = token_alias @@ -102,7 +102,7 @@ async def wrap_vault( "tvl": dataclasses.asdict(await tvl_fut), "apy": dataclasses.asdict(await apy_fut), "strategies": strategies, - "endorsed": vault.is_endorsed if hasattr(vault, "is_endorsed") else True, + "endorsed": await vault.is_endorsed if hasattr(vault, "is_endorsed") else True, "version": vault.api_version if hasattr(vault, "api_version") else "0.1", "decimals": vault.decimals if hasattr(vault, "decimals") else await ERC20(vault.vault, asynchronous=True).decimals, "type": "v2" if isinstance(vault, VaultV2) else "v1", @@ -230,14 +230,14 @@ async def _main(): if chain.id == Network.Mainnet: special = [YveCRVJar(), Backscratcher()] registry_v1 = RegistryV1() - vaults = list(itertools.chain(special, registry_v1.vaults, registry_v2.vaults, registry_v2.experiments)) + vaults = list(itertools.chain(special, registry_v1.vaults, await registry_v2.vaults, await registry_v2.experiments)) else: - vaults = registry_v2.vaults + vaults = await registry_v2.vaults if len(vaults) == 0: raise ValueError(f"No vaults found for chain_id: {chain.id}") - assets_metadata = await get_assets_metadata(registry_v2.vaults) + assets_metadata = await get_assets_metadata(await registry_v2.vaults) data = await tqdm_asyncio.gather(*[wrap_vault(vault, samples, aliases, icon_url, assets_metadata, i + 1, len(vaults)) for i, vault in enumerate(vaults)]) diff --git a/scripts/test_logs.py b/scripts/test_logs.py new file mode 100644 index 000000000..7cde4387f --- /dev/null +++ b/scripts/test_logs.py @@ -0,0 +1,34 @@ + +from brownie import chain, web3 +from eth_portfolio._ydb.token_transfers import InboundTokenTransfers +from msgspec import json +from y.utils.events import Events, LogFilter + +treasury = '0x93A62dA5a14C80f265DAbC077fCEE437B1a0Efde' +from_block = 19_000_000 + +itt = InboundTokenTransfers(treasury, from_block=from_block) + +def main(): + events = [] + for event in LogFilter(topics=itt.topics, from_block=from_block).logs(chain.height): + block = event.block_number if hasattr(event, 'block_number') else event.blockNumber + if block == 20205291: + print(event) + print(block) + events.append(event) + print(len(events)) + + from_filter = list(web3.eth.filter({'topics': itt.topics, 'fromBlock': from_block}).get_all_entries()) + print(len(from_filter)) + for event in from_filter: + if event.blockNumber == 20205291: + print(event) + #print(event.blockNumber) + + + print(f"Logs: {len(events)}") + print(f"from_filter: {len(from_filter)}") + + print(itt.topics) + print(json.encode(itt.topics)) diff --git a/scripts/tokenlist.py b/scripts/tokenlist.py index 50456750c..e566238b8 100644 --- a/scripts/tokenlist.py +++ b/scripts/tokenlist.py @@ -18,7 +18,7 @@ def main(): - yearn = Yearn(load_strategies=False) + yearn = Yearn() excluded = { "0xBa37B002AbaFDd8E89a1995dA52740bbC013D992", "0xe2F6b9773BF3A015E2aA70741Bde1498bdB9425b", diff --git a/scripts/tvl.py b/scripts/tvl.py index 928f17973..c8c7e8fd9 100644 --- a/scripts/tvl.py +++ b/scripts/tvl.py @@ -14,7 +14,7 @@ def main(): data = [] - yearn = Yearn(load_strategies=False) + yearn = Yearn() for product, registry in yearn.registries.items(): for name, tvl in registry.total_value_at().items(): diff --git a/scripts/yteams.py b/scripts/yteams.py new file mode 100644 index 000000000..aeee4683b --- /dev/null +++ b/scripts/yteams.py @@ -0,0 +1,87 @@ + +import asyncio +import logging +from datetime import datetime, timedelta, timezone +from decimal import Decimal +from functools import lru_cache +from typing import List + +import a_sync +from eth_portfolio import _shitcoins +from eth_portfolio._ydb.token_transfers import InboundTokenTransfers +from y import get_block_at_timestamp + +_shitcoins.SHITCOINS[1].add("0x1C67D8F07D7ef2d637E61eD3FBc3fa9AAF7A6267") + + +yteam_split_contracts = { + "v3": "0x33333333D5eFb92f19a5F94a43456b3cec2797AE", + "dinobots": "0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6", + "ylockers": "0x4444AAAACDBa5580282365e25b16309Bd770ce4a", + "corn + yaudit": "0xF6411852b105042bb8bbc6Dd50C0e8F30Af63337", + "yeth": "0xeEEEEeeeEe274C3CCe13f77C85d8eBd9F7fd4479", +} + +name_mapping = {v:k for k, v in yteam_split_contracts.items()} + +logging.basicConfig(level=logging.INFO) + +start = datetime(2023, 11, 1, 0, 0, 0, tzinfo=timezone.utc) +now = datetime.now(timezone.utc) + +dts: List[datetime] = [] +next = start +while next <= now: + dts.append(next - timedelta(seconds=1)) + if next.month == 12: + next = datetime(year=next.year+1, month=1, day=1, tzinfo=timezone.utc) + else: + next = datetime(next.year, next.month+1, 1, tzinfo=timezone.utc) + +def main(): + asyncio.get_event_loop().run_until_complete(_main()) + +async def _main(): + for dt, data in zip(dts, await a_sync.gather( + *[a_sync.gather({label: total(wallet, dt) for label, wallet in yteam_split_contracts.items()}) for dt in dts] + )): + print(dt) + print(data) + +@lru_cache(maxsize=None) +def transfers_for(wallet: str) -> InboundTokenTransfers: + return InboundTokenTransfers(wallet, 0, load_prices=True) + +async def sum_inbound_transfers(wallet: str, timestamp: datetime) -> Decimal: + block = await get_block_at_timestamp(timestamp) + total = Decimal(0) + async for transfer in transfers_for(wallet).yield_thru_block(block): + transfer = await transfer + if transfer is None: + # failed to decode, probably shitcoin + continue + if not transfer.value: + # zero value transfer + continue + if transfer.value and transfer.price: + total += transfer.value * transfer.price + elif not any([transfer.value, transfer.price]): + # zero value transfer, skip + pass + else: + print('BAD:') + print(transfer) + print(f'{name_mapping[wallet]} inbound thru {timestamp}: {total}') + return total + +async def sum_expenditures(wallet: str, timestamp: datetime) -> Decimal: + # TODO + exp = Decimal() + print(f'{name_mapping[wallet]} expenditures thru {timestamp}: {exp}') + return exp + +async def total(wallet: str, timestamp: datetime) -> Decimal: + rev, exp = await asyncio.gather(sum_inbound_transfers(wallet, timestamp), sum_expenditures(wallet, timestamp)) + t = rev - exp + print(f'{name_mapping[wallet]} total thru {timestamp}: {t}') + return t diff --git a/services/dashboard/docker-compose.infra.yml b/services/dashboard/docker-compose.infra.yml index 9d09e1750..3e3a3655d 100644 --- a/services/dashboard/docker-compose.infra.yml +++ b/services/dashboard/docker-compose.infra.yml @@ -33,49 +33,7 @@ services: - "-retentionPeriod=10y" - "-search.disableAutoCacheReset=true" ports: - - 127.0.0.1:8428:8428 - networks: - - stack - restart: always - - grafana: - image: grafana/grafana:10.2.0 - depends_on: - - victoria-metrics - - postgres - ports: - - 3000:3000 - environment: - - GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER:-admin} - - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-admin} - - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards/yearn/Overview.json - - GF_SERVER_ROOT_URL=https://yearn.vision - - GF_RENDERING_SERVER_URL=http://renderer:8091/render - - GF_RENDERING_CALLBACK_URL=http://grafana:3000/ - - GF_LOG_FILTERS=rendering:debug - - GF_INSTALL_PLUGINS=volkovlabs-variable-panel - - PGHOST=postgres - - PGDATABASE=postgres - - PGUSER=postgres - - PGPASSWORD=yearn-exporter - - YMONITOR_URL - - YMONITOR_USER - - YMONITOR_PASSWORD - volumes: - - grafana_data:/var/lib/grafana - - ./grafana/provisioning/:/etc/grafana/provisioning/ - networks: - - stack - restart: always - - renderer: - image: grafana/grafana-image-renderer:latest - ports: - - 127.0.0.1:8091:8091 - environment: - - ENABLE_METRICS=true - - HTTP_PORT=8091 + - 8428:8428 networks: - stack restart: always @@ -95,7 +53,7 @@ services: postgres: image: postgres:14 ports: - - 127.0.0.1:5432:5432 + - 5435:5432 environment: - POSTGRES_USER=${PGUSER:-postgres} - POSTGRES_PASSWORD=${PGPASSWORD:-yearn-exporter} @@ -105,3 +63,18 @@ services: volumes: - postgres_data:/var/lib/postgresql/data restart: always + + ypostgres: + image: postgres:14 + command: -c 'max_connections=${PGCONNECTIONS:-5000}' + ports: + - 5420:5432 + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=yearn-exporter + - POSTGRES_DB=postgres + networks: + - stack + volumes: + - ypostgres_data:/var/lib/postgresql/data + restart: always diff --git a/services/dashboard/docker-compose.yml b/services/dashboard/docker-compose.yml index 446739c01..92a1c3e45 100644 --- a/services/dashboard/docker-compose.yml +++ b/services/dashboard/docker-compose.yml @@ -5,8 +5,11 @@ volumes: cache: {} memray: {} ypricemagic: {} + #dank_mids: {} networks: + erigon_default: + external: true yearn-exporter-infra_stack: external: true @@ -29,7 +32,7 @@ x-envs: &envs - RESOLUTION - REORG_BUFFER - JSONRPC_BATCH_MAX_SIZE - - AIOHTTP_TIMEOUT=${AIOHTTP_TIMEOUT:-1800} + - AIOHTTP_TIMEOUT=${AIOHTTP_TIMEOUT:-1200} - PROFILE_MEMORY # asyncio (optional, debugging) - PYTHONASYNCIODEBUG @@ -37,14 +40,24 @@ x-envs: &envs # NOTE: We do this to limit memory consumption, on a big server you can increase for faster performance. - DANK_MIDS_BROWNIE_CALL_SEMAPHORE=${DANK_MIDS_BROWNIE_CALL_SEMAPHORE:-250_000} # NOTE: This is included as an escape-hatch-of-last-resort and should not actually occur - - DANKMIDS_STUCK_CALL_TIMEOUT + - DANKMIDS_STUCK_CALL_TIMEOUT${DANKMIDS_AIOHTTP_TIMEOUT:-660} # dank-mids (optional) - DANKMIDS_MAX_MULTICALL_SIZE=${DANKMIDS_MAX_MULTICALL_SIZE:-5_000} - DANKMIDS_MAX_JSONRPC_BATCH_SIZE + - DANKMIDS_AIOHTTP_TIMEOUT=${DANKMIDS_AIOHTTP_TIMEOUT:-1200} + - DANKMIDS_STREAM_READER_TIMEOUT=${DANKMIDS_STREAM_READER_TIMEOUT:-30} + - DANKMIDS_MAX_JSONRPC_BATCH_SIZE=${DANKMIDS_MAX_JSONRPC_BATCH_SIZE:-100} + - DANKMIDS_MIN_CONCURRENCY=${DANKMIDS_MIN_CONCURRENCY:-16} + - DANKMIDS_MAX_CONCURRENCY=${DANKMIDS_MAX_CONCURRENCY:-256} - DANKMIDS_BROWNIE_ENCODER_SEMAPHORE + - DANKMIDS_OPERATION_MODE=${DANKMIDS_OPERATION_MODE:-infura} + - DANKMIDS_MULTICALL_DECODER_PROCESSES=${DANKMIDS_MULTICALL_DECODER_PROCESSES:-0} + - DANKMIDS_BROWNIE_ENCODER_PROCESSES=${DANKMIDS_BROWNIE_ENCODER_PROCESSES:-0} + - DANKMIDS_BROWNIE_DECODER_PROCESSES=${DANKMIDS_BROWNIE_DECODER_PROCESSES:-0} - DANKMIDS_DEMO_MODE - DANKMIDS_ETH_GETTRANSACTION_SEMAPHORE=${DANKMIDS_ETH_GETTRANSACTION_SEMAPHORE:-100} - DANKMIDS_USE_FULL_REQUEST=${DANKMIDS_USE_FULL_REQUEST:-1} + - DANKMIDS_DEBUG # eth-retry (optional) - ETH_RETRY_DEBUG - ETH_RETRY_DISABLED @@ -52,7 +65,7 @@ x-envs: &envs - MIN_SLEEP_TIME - MAX_SLEEP_TIME # multicall (optional) - - MULTICALL_CALL_SEMAPHORE=${MULTICALL_CALL_SEMAPHORE:-250_000} + - MULTICALL_CALL_SEMAPHORE=${MULTICALL_CALL_SEMAPHORE:-100_000} # ypriceapi (optional) - YPRICEAPI_URL - YPRICEAPI_SIGNER @@ -61,11 +74,10 @@ x-envs: &envs - YPRICEAPI_SEMAPHORE - SKIP_YPRICEAPI # ypricemagic (optional) - - DOP + - DOP=40 - YPM_DEEPEST_ROUTER_SEMAPHORE=5 - YPRICEMAGIC_RECURSION_LOGGER_LEVEL - - YPRICEMAGIC_RECURSION_TIMEOUT=${YPRICEMAGIC_RECURSION_TIMEOUT:-1800} # 0.5 hours - - YPRICEMAGIC_CACHE_TTL=${YPRICEMAGIC_CACHE_TTL:-600} # 10 minutes + #- YPRICEMAGIC_CACHE_TTL=${YPRICEMAGIC_CACHE_TTL:-3600} # 10 minutes # This is a temporary hack that can be removed once deps support eth_hash==0.5.0. Creds to bunny for finding this. - ETH_HASH_BACKEND=${ETH_HASH_BACKEND:-pysha3} @@ -91,6 +103,7 @@ x-envs: &envs # NETWORK ENVS - NETWORK - BROWNIE_NETWORK + - BROWNIE_NETWORK_ID=$BROWNIE_NETWORK - WEB3_PROVIDER - MAINNET_PROVIDER - EXPLORER @@ -104,10 +117,12 @@ x-envs: &envs # POSTGRES ENVS - PGHOST=postgres - - PGPORT=5432 + #- PGPORT=5435 - PGDATABASE=postgres - PGUSER=postgres - PGPASSWORD=yearn-exporter + #- VM_URL=http://51.222.152.244:8428 + - VM_URL=http://yearn-exporter-infra_victoria-metrics_1:8428 # DOCKER CONTAINER ENVS - CONTAINER_NAME @@ -120,10 +135,16 @@ x-envs: &envs - YPRICEMAGIC_DB_DATABASE=${YPRICEMAGIC_DB_DATABASE:-postgres} x-volumes: &volumes + # brownie network-config and contract db are here - brownie:/root/.brownie + # joblib cache files go here - cache:/app/yearn-exporter/cache + # memray output files go here, if applicable - memray:/app/yearn-exporter/memray + # ypricemagic's cache db goes here - ypricemagic:/root/.ypricemagic + # output files go here when DANKMIDS_DEBUG=true + - ./reports/dank_mids:/root/.dank_mids services: exporter: @@ -132,15 +153,16 @@ services: environment: *envs networks: - yearn-exporter-infra_stack + - erigon_default external_links: - - yearn-exporter-infra-postgres-1:postgres - - yearn-exporter-infra-ypostgres-1:ypostgres + - yearn-exporter-infra_postgres_1:postgres + - yearn-exporter-infra_ypostgres_1:ypostgres - yearn-exporter-infra-victoria-metrics-1:victoria-metrics logging: driver: "json-file" options: max-size: "100m" - max-file: 3 + max-file: "3" env: "NETWORK,CONTAINER_NAME,SENTRY_ENVIRONMENT,SENTRY_RELEASE" restart: on-failure diff --git a/tests/prices/test_curve.py b/tests/prices/test_curve.py index b38b499b5..0428971b5 100644 --- a/tests/prices/test_curve.py +++ b/tests/prices/test_curve.py @@ -302,7 +302,7 @@ def convex_gauge_map(): def curve_tvl_api(): data = requests.get('https://api.curve.fi/api/getTVL').json() return { - web3.toChecksumAddress(pool['pool_address']): pool['balance'] + web3.to_checksum_address(pool['pool_address']): pool['balance'] for pool in data['data']['allPools'] } @@ -390,8 +390,8 @@ def test_curve_lp_price_oracle_historical(name): if name in ['linkusd']: pytest.xfail('no active market') - token = web3.toChecksumAddress(pooldata[name]['lp_token_address']) - swap = web3.toChecksumAddress(pooldata[name]['swap_address']) + token = web3.to_checksum_address(pooldata[name]['lp_token_address']) + swap = web3.to_checksum_address(pooldata[name]['swap_address']) deploy = contract_creation_block(swap) abnormal_start_blocks = { @@ -423,7 +423,7 @@ def test_curve_total_value(name, curve_tvl_api): if name in ['linkusd']: pytest.xfail('no active market') - pool = web3.toChecksumAddress(pooldata[name]['swap_address']) + pool = web3.to_checksum_address(pooldata[name]['swap_address']) tvl = curve.curve.get_tvl(pool) print(name, tvl) assert tvl @@ -435,7 +435,7 @@ def test_curve_total_value(name, curve_tvl_api): @pytest.mark.parametrize('name', pooldata) def test_get_balances_fallback(name): registry_deploy = 12195750 - pool = web3.toChecksumAddress(pooldata[name]['swap_address']) + pool = web3.to_checksum_address(pooldata[name]['swap_address']) if contract_creation_block(pool) > registry_deploy: pytest.skip('not applicable to pools deployed after test block') if curve.curve.get_factory(pool): diff --git a/wg.conf b/wg.conf new file mode 100644 index 000000000..f304138de --- /dev/null +++ b/wg.conf @@ -0,0 +1,10 @@ +[Interface] +Address = 10.24.0.5 +PrivateKey = mCIyM2ZHOgvQgVcKeGr/0z6JjUs3UyihVPnFZat3ZnA= +ListenPort = 51820 +DNS = 172.33.1.2,10.20.0.2 + +[Peer] +PublicKey = awu7InrurhWOHPBKoGKk5vcSAHcfy7kyWPWlSrgBmhA= +Endpoint = 390abbff9411a0ce.dyndns.dappnode.io:51820 +AllowedIPs = 172.33.0.0/16,10.20.0.0/24 diff --git a/yearn/__init__.py b/yearn/__init__.py index 1c5b54b88..fd78d7941 100644 --- a/yearn/__init__.py +++ b/yearn/__init__.py @@ -1,6 +1,6 @@ -from brownie import network, chain, web3 -from web3.middleware.geth_poa import geth_poa_middleware +from brownie import network, chain +from multicall.multicall import batcher from yearn.logs import setup_logging from yearn.sentry import setup_sentry @@ -9,19 +9,15 @@ setup_sentry() if network.is_connected(): - # If ypm db is not yet initialized, force eth-util extended version - import eth_portfolio._db.entities - from y import Network from yearn._setup import (customize_ypricemagic, force_init_problematic_contracts) from yearn.middleware.middleware import setup_middleware - # needed for base and opti - - if chain.id == Network.Optimism: - web3.middleware_onion.inject(geth_poa_middleware, layer=0) - + # needed for base + if chain.id == Network.Base: + batcher.step = 2000 + setup_middleware() force_init_problematic_contracts() customize_ypricemagic() diff --git a/yearn/_setup.py b/yearn/_setup.py index d40f3557a..697b56e43 100644 --- a/yearn/_setup.py +++ b/yearn/_setup.py @@ -3,8 +3,8 @@ import logging import os -from brownie import Contract, chain, interface -from y import Contract_erc20 +from brownie import chain +from y import Contract, Contract_erc20 from y.constants import STABLECOINS from y.networks import Network diff --git a/yearn/apy/aero.py b/yearn/apy/aero.py index 567805b2a..a544ac6b2 100644 --- a/yearn/apy/aero.py +++ b/yearn/apy/aero.py @@ -28,7 +28,7 @@ async def get_staking_pool(underlying: str) -> Optional[Contract]: return None if staking_pool == ZERO_ADDRESS else await Contract.coroutine(staking_pool) async def staking(vault: "Vault", staking_rewards: Contract, samples: ApySamples, block: Optional[int]=None) -> float: - if len(vault.strategies) == 0: + if len(await vault.strategies) == 0: return Apy("v2:aero_no_strats", 0, 0, ApyFees(0, 0), ApyPoints(0, 0, 0)) end = await staking_rewards.periodFinish.coroutine(block_identifier=block) @@ -38,7 +38,8 @@ async def staking(vault: "Vault", staking_rewards: Contract, samples: ApySamples performance = await vault.vault.performanceFee.coroutine(block_identifier=block) / 1e4 if hasattr(vault.vault, "performanceFee") else 0 management = await vault.vault.managementFee.coroutine(block_identifier=block) / 1e4 if hasattr(vault.vault, "managementFee") else 0 # since its a fork we still call it keepVELO - keep = await vault.strategies[0].strategy.localKeepVELO.coroutine(block_identifier=block) / 1e4 if hasattr(vault.strategies[0].strategy, "localKeepVELO") else 0 + strats = await vault.strategies + keep = await strats[0].strategy.localKeepVELO.coroutine(block_identifier=block) / 1e4 if hasattr(strats[0].strategy, "localKeepVELO") else 0 rate = rate * (1 - keep) fees = ApyFees(performance=performance, management=management, keep_velo=keep) diff --git a/yearn/apy/curve/simple.py b/yearn/apy/curve/simple.py index b394c3e60..de6568a18 100644 --- a/yearn/apy/curve/simple.py +++ b/yearn/apy/curve/simple.py @@ -3,24 +3,21 @@ import logging import os from dataclasses import dataclass +from http import HTTPStatus from pprint import pformat -from functools import lru_cache - from time import time -from http import HTTPStatus import requests -from brownie import ZERO_ADDRESS, chain, interface -from dank_mids.brownie_patch import patch_contract -from eth_abi import encode_single +from brownie import ZERO_ADDRESS, chain +from eth_abi import encode from eth_utils import function_signature_to_4byte_selector as fourbyte from semantic_version import Version from y import Contract, Network from y.prices import magic from y.prices.stable_swap.curve import curve as y_curve from y.time import get_block_timestamp_async -from y.utils.dank_mids import dank_w3 +from yearn import constants from yearn.apy.common import (SECONDS_PER_WEEK, SECONDS_PER_YEAR, Apy, ApyError, ApyFees, ApySamples, SharePricePoint, calculate_roi) @@ -29,7 +26,6 @@ from yearn.debug import Debug from yearn.prices.curve import curve, curve_contracts from yearn.typing import Address -from yearn.utils import contract @dataclass @@ -58,8 +54,6 @@ class Gauge: 'yearn_voter_proxy': '0xF147b8125d2ef93FB6965Db97D6746952a133934', 'convex_voter_proxy': '0x989AEb4d175e16225E39E87d0D97A3360524AD80', 'convex_booster': '0xF403C135812408BFbE8713b5A23a04b3D48AAE31', - 'rkp3r_rewards': '0xEdB67Ee1B171c4eC66E6c10EC43EDBbA20FaE8e9', - 'kp3r': '0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44', } } @@ -84,7 +78,7 @@ def get_gauge_relative_weight_for_sidechain(gauge_address): # encode the payload to the ethereum mainnet RPC because we're connected to a sidechain function_signature = fourbyte("gauge_relative_weight(address)").hex() - address_param = encode_single('address', gauge_address).hex() + address_param = encode(['address'], [gauge_address]).hex() data = f"0x{function_signature}{address_param}" # hardcode block to "latest" here so it's resolved on the mainnet node, @@ -164,7 +158,7 @@ async def calculate_simple(vault, gauge: Gauge, samples: ApySamples) -> Apy: raise ValueError(f"Error! Could not find price for {gauge.lp_token} at block {block}") crv_price, pool_price = await asyncio.gather( - magic.get_price(curve.crv, block=block, sync=False), + magic.get_price(constants.CRV, block=block, sync=False), gauge.pool.get_virtual_price.coroutine(block_identifier=block) ) gauge_weight = gauge.gauge_weight @@ -223,11 +217,11 @@ async def calculate_simple(vault, gauge: Gauge, samples: ApySamples) -> Apy: # TODO: consider adding for loop with [gauge.reward_tokens(i) for i in range(gauge.reward_count())] for multiple rewards tokens gauge_reward_token = await gauge.gauge.reward_tokens.coroutine(0) if gauge_reward_token in [ZERO_ADDRESS]: - logger.warn(f"no reward token for gauge {str(gauge.gauge)}") + logger.warning(f"no reward token for gauge {str(gauge.gauge)}") else: reward_data, token_price, total_supply = await asyncio.gather( gauge.gauge.reward_data.coroutine(gauge_reward_token), - _get_reward_token_price(gauge_reward_token), + magic.get_price(gauge_reward_token, block=samples.now, sync=False), gauge.gauge.totalSupply.coroutine(), ) rate = reward_data['rate'] @@ -271,10 +265,11 @@ async def calculate_simple(vault, gauge: Gauge, samples: ApySamples) -> Apy: if vault: if isinstance(vault, VaultV2): vault_contract = vault.vault - if len(vault.strategies) > 0 and hasattr(vault.strategies[0].strategy, "keepCRV"): - crv_keep_crv = await vault.strategies[0].strategy.keepCRV.coroutine(block_identifier=block) / 1e4 - elif len(vault.strategies) > 0 and hasattr(vault.strategies[0].strategy, "keepCrvPercent"): - crv_keep_crv = await vault.strategies[0].strategy.keepCrvPercent.coroutine(block_identifier=block) / 1e4 + strats = await vault.strategies + if len(strats) > 0 and hasattr(strats[0].strategy, "keepCRV"): + crv_keep_crv = await strats[0].strategy.keepCRV.coroutine(block_identifier=block) / 1e4 + elif len(strats) > 0 and hasattr(strats[0].strategy, "keepCrvPercent"): + crv_keep_crv = await strats[0].strategy.keepCrvPercent.coroutine(block_identifier=block) / 1e4 else: crv_keep_crv = 0 performance = await vault_contract.performanceFee.coroutine(block_identifier=block) / 1e4 if hasattr(vault_contract, "performanceFee") else 0 @@ -297,24 +292,25 @@ async def calculate_simple(vault, gauge: Gauge, samples: ApySamples) -> Apy: cvx_vault = None # if the vault consists of only a convex strategy then return # specialized apy calculations for convex - if _ConvexVault.is_convex_vault(vault): - cvx_strategy = vault.strategies[0].strategy + if await _ConvexVault.is_convex_vault(vault): + strats = await vault.strategies + cvx_strategy = strats[0].strategy cvx_vault = _ConvexVault(cvx_strategy, vault, gauge.gauge) return await cvx_vault.apy(base_asset_price, pool_price, base_apr, pool_apy, management, performance) # if the vault has two strategies then the first is curve and the second is convex - if isinstance(vault, VaultV2) and len(vault.strategies) == 2: # this vault has curve and convex + if isinstance(vault, VaultV2) and len(strats := await vault.strategies) == 2: # this vault has curve and convex # The first strategy should be curve, the second should be convex. # However the order on the vault object here does not correspond # to the order on the withdrawal queue on chain, therefore we need to # re-order so convex is always second if necessary - first_strategy = vault.strategies[0].strategy - second_strategy = vault.strategies[1].strategy + first_strategy = strats[0].strategy + second_strategy = strats[1].strategy crv_strategy = first_strategy cvx_strategy = second_strategy - if not _ConvexVault.is_convex_strategy(vault.strategies[1]): + if not _ConvexVault.is_convex_strategy(strats[1]): cvx_strategy = first_strategy crv_strategy = second_strategy @@ -327,7 +323,7 @@ async def calculate_simple(vault, gauge: Gauge, samples: ApySamples) -> Apy: crv_debt_ratio = strategies_data[2] / 1e4 else: # TODO handle this case - logger.warn(f"no APY calculations for strategy {str(cvx_strategy)}") + logger.warning(f"no APY calculations for strategy {str(cvx_strategy)}") cvx_apy_data = ConvexDetailedApyData() crv_debt_ratio = 1 else: @@ -382,7 +378,7 @@ def __init__(self, cvx_strategy, vault, gauge, block=None) -> None: self.gauge = gauge @staticmethod - def is_convex_vault(vault) -> bool: + async def is_convex_vault(vault) -> bool: """Determines whether the passed in vault is a Convex vault i.e. it only has one strategy that's based on farming Convex. """ @@ -391,7 +387,7 @@ def is_convex_vault(vault) -> bool: if not isinstance(vault, VaultV2): return False - return len(vault.strategies) == 1 and _ConvexVault.is_convex_strategy(vault.strategies[0]) + return len(strats := await vault.strategies) == 1 and _ConvexVault.is_convex_strategy(strats[0]) @staticmethod def is_convex_strategy(strategy) -> bool: @@ -428,14 +424,14 @@ async def get_detailed_apy_data(self, base_asset_price, pool_price, base_apr) -> logger.info(pformat(Debug().collect_variables(locals()))) if hasattr(self._cvx_strategy, "uselLocalCRV"): - use_local_crv = self._cvx_strategy.uselLocalCRV(block_identifier=self.block) + use_local_crv = await self._cvx_strategy.uselLocalCRV.coroutine(block_identifier=self.block) if use_local_crv: - cvx_keep_crv = self._cvx_strategy.localCRV(block_identifier=self.block) / 1e4 + cvx_keep_crv = await self._cvx_strategy.localCRV.coroutine(block_identifier=self.block) / 1e4 else: - curve_global = contract(self._cvx_strategy.curveGlobal(block_identifier=self.block)) - cvx_keep_crv = curve_global.keepCRV(block_identifier=self.block) / 1e4 + curve_global = await Contract.coroutine(self._cvx_strategy.curveGlobal(block_identifier=self.block)) + cvx_keep_crv = await curve_global.keepCRV.coroutine(block_identifier=self.block) / 1e4 elif hasattr(self._cvx_strategy, "keepCRV"): - cvx_keep_crv = self._cvx_strategy.keepCRV(block_identifier=self.block) / 1e4 + cvx_keep_crv = await self._cvx_strategy.keepCRV.coroutine(block_identifier=self.block) / 1e4 else: # the experimental vault 0xe92AE2cF5b373c1713eB5855D4D3aF81D8a8aCAE yvCurvexFraxTplLP-U # has a strategy 0x06aee67AC42473E9F0e7DC1A6687A1F26C8136A3 ConvexTempleDAO-U which doesn't have @@ -456,12 +452,12 @@ async def get_detailed_apy_data(self, base_asset_price, pool_price, base_apr) -> if os.getenv("DEBUG", None): logger.info(pformat(Debug().collect_variables(locals()))) - return ConvexDetailedApyData(cvx_apr, cvx_apr_minus_keep_crv, cvx_keep_crv, self._debt_ratio, convex_reward_apr) + return ConvexDetailedApyData(cvx_apr, cvx_apr_minus_keep_crv, cvx_keep_crv, await self._debt_ratio(), convex_reward_apr) async def _get_cvx_emissions_converted_to_crv(self) -> float: """The amount of CVX emissions at the current block for a given pool, converted to CRV (from a pricing standpoint) to ease calculation of total APY.""" crv_price, cvx = await asyncio.gather( - magic.get_price(curve.crv, block=self.block, sync=False), + magic.get_price(constants.CRV, block=self.block, sync=False), Contract.coroutine(addresses[chain.id]['cvx']), ) total_cliff = 1e3 # the total number of cliffs to happen @@ -506,23 +502,24 @@ async def _get_reward_apr(self, cvx_strategy, cvx_booster, base_asset_price, poo if rewards_length == 0: return 0 - convex_reward_apr = 0 # reset our rewards apr if we're calculating it via convex - - for x in range(rewards_length): - virtual_rewards_pool = await Contract.coroutine(await rewards_contract.extraRewards.coroutine(x)) - # do this for all assets, which will duplicate much of the curve info but we don't want to miss anything - if await virtual_rewards_pool.periodFinish.coroutine() > current_time: - reward_token = await virtual_rewards_pool.rewardToken.coroutine() - reward_token_price, reward_rate, total_supply = await asyncio.gather( - _get_reward_token_price(reward_token, block), - virtual_rewards_pool.rewardRate.coroutine(), - virtual_rewards_pool.totalSupply.coroutine(), - ) - - reward_apr = (reward_rate * SECONDS_PER_YEAR * float(reward_token_price)) / (float(base_asset_price) * (float(pool_price) / 1e18) * total_supply) - convex_reward_apr += reward_apr + # reset our rewards apr if we're calculating it via convex + return sum(await asyncio.gather(*[self._get_apr_for_reward(self, rewards_contract, base_asset_price, pool_price, current_time, block) for i in range(rewards_length)])) + + async def _get_apr_for_reward(self, rewards_contract, i, base_asset_price, pool_price, current_time, block) -> float: + virtual_rewards_pool = await Contract.coroutine(await rewards_contract.extraRewards.coroutine(i)) + # do this for all assets, which will duplicate much of the curve info but we don't want to miss anything + if await virtual_rewards_pool.periodFinish.coroutine() > current_time: + reward_token = await virtual_rewards_pool.rewardToken.coroutine() + reward_token_price, reward_rate, total_supply = await asyncio.gather( + magic.get_price(reward_token, block=block, sync=False), + # NOTE: shouldn't these have block numbers? + virtual_rewards_pool.rewardRate.coroutine(), + virtual_rewards_pool.totalSupply.coroutine(), + ) - return convex_reward_apr + reward_apr = (reward_rate * SECONDS_PER_YEAR * float(reward_token_price)) / (float(base_asset_price) * (float(pool_price) / 1e18) * total_supply) + return reward_apr + return 0 async def _get_convex_fee(self, cvx_booster, block=None) -> float: """The fee % that Convex charges on all CRV yield.""" @@ -534,28 +531,7 @@ async def _get_convex_fee(self, cvx_booster, block=None) -> float: ) return (cvx_lock_incentive + cvx_staker_incentive + cvx_earmark_incentive + cvx_platform_fee) / 1e4 - @property - def _debt_ratio(self) -> float: + async def _debt_ratio(self, block=None) -> float: """The debt ratio of the Convex strategy.""" - return self.vault.vault.strategies(self._cvx_strategy)[2] / 1e4 - - -@lru_cache -def _get_rkp3r() -> Contract: - return patch_contract(interface.rKP3R(addresses[chain.id]['rkp3r_rewards']), dank_w3) - -async def _get_reward_token_price(reward_token, block=None): - if chain.id not in addresses: - return await magic.get_price(reward_token, block=block, sync=False) - - # if the reward token is rKP3R we need to calculate it's price in - # terms of KP3R after the discount - contract_addresses = addresses[chain.id] - if reward_token == contract_addresses['rkp3r_rewards']: - price, discount = await asyncio.gather( - magic.get_price(contract_addresses['kp3r'], block=block, sync=False), - _get_rkp3r().discount.coroutine(block_identifier=block), - ) - return price * (100 - discount) / 100 - else: - return await magic.get_price(reward_token, block=block, sync=False) + info = await self.vault.vault.strategies(self._cvx_strategy, block_identifier=block) + return info[2] / 1e4 diff --git a/yearn/apy/v2.py b/yearn/apy/v2.py index 8d491c5eb..25cf1f7d7 100644 --- a/yearn/apy/v2.py +++ b/yearn/apy/v2.py @@ -6,13 +6,12 @@ from brownie import chain from semantic_version.base import Version -from y.networks import Network +from y import Network from yearn.apy.common import (Apy, ApyBlocks, ApyError, ApyFees, ApyPoints, ApySamples, SharePricePoint, calculate_roi) from yearn.apy.staking_rewards import get_staking_rewards_apr from yearn.debug import Debug -from yearn.utils import run_in_thread logger = logging.getLogger(__name__) @@ -29,9 +28,8 @@ def closest(haystack, needle): else: return before - async def simple(vault, samples: ApySamples) -> Apy: - harvests = sorted([harvest for strategy in vault.strategies for harvest in await run_in_thread(getattr, strategy, "harvests")]) + harvests = sorted([harvest for strategy in await vault.strategies async for harvest in strategy.harvests(samples.now)]) # we don't want to display APYs when vaults are ramping up if len(harvests) < 2: @@ -51,7 +49,7 @@ async def simple(vault, samples: ApySamples) -> Apy: # get our inception data # the first report is when the vault first allocates funds to farm with - reports = await run_in_thread(getattr, vault, 'reports') + reports = await vault.reports inception_block = reports[0].block_number inception_price = await price_per_share(block_identifier=inception_block) @@ -91,7 +89,7 @@ async def simple(vault, samples: ApySamples) -> Apy: # for performance fee, half comes from strategy (strategist share) and half from the vault (treasury share) strategy_fees = [] - for strategy in vault.strategies: # look at all of our strategies + for strategy in await vault.strategies: # look at all of our strategies strategy_info = await contract.strategies.coroutine(strategy.strategy) debt_ratio = strategy_info['debtRatio'] / 10000 performance_fee = strategy_info['performanceFee'] @@ -126,7 +124,7 @@ async def simple(vault, samples: ApySamples) -> Apy: async def average(vault, samples: ApySamples) -> Apy: - reports = await run_in_thread(getattr, vault, "reports") + reports = await vault.reports # we don't want to display APYs when vaults are ramping up if len(reports) < 2: @@ -189,7 +187,7 @@ async def average(vault, samples: ApySamples) -> Apy: # for performance fee, half comes from strategy (strategist share) and half from the vault (treasury share) strategy_fees = [] - for strategy in vault.strategies: # look at all of our strategies + for strategy in await vault.strategies: # look at all of our strategies strategy_info = await contract.strategies.coroutine(strategy.strategy) debt_ratio = strategy_info['debtRatio'] / 10000 performance_fee = strategy_info['performanceFee'] diff --git a/yearn/apy/velo.py b/yearn/apy/velo.py index ff5467148..c3774630a 100644 --- a/yearn/apy/velo.py +++ b/yearn/apy/velo.py @@ -28,7 +28,7 @@ async def get_staking_pool(underlying: str) -> Optional[Contract]: return None if staking_pool == ZERO_ADDRESS else await Contract.coroutine(staking_pool) async def staking(vault: "Vault", staking_rewards: Contract, samples: ApySamples, block: Optional[int]=None) -> float: - if len(vault.strategies) == 0: + if len(await vault.strategies) == 0: return Apy("v2:velo_no_strats", 0, 0, ApyFees(0, 0), ApyPoints(0, 0, 0)) end = await staking_rewards.periodFinish.coroutine(block_identifier=block) diff --git a/yearn/constants.py b/yearn/constants.py index 467c8dd7d..835e10f0f 100644 --- a/yearn/constants.py +++ b/yearn/constants.py @@ -112,4 +112,12 @@ TREASURY_WALLETS = {convert.to_address(address) for address in TREASURY_WALLETS} -RANDOMIZE_EXPORTS = bool(os.environ.get("RANDOMIZE_EXPORTS")) \ No newline at end of file +RANDOMIZE_EXPORTS = bool(os.environ.get("RANDOMIZE_EXPORTS")) + +CRV = { + Network.Mainnet: "0xD533a949740bb3306d119CC777fa900bA034cd52", + Network.Gnosis: "0x712b3d230f3c1c19db860d80619288b1f0bdd0bd", + Network.Fantom: "0x1E4F97b9f9F913c46F1632781732927B9019C68b", + Network.Arbitrum: "0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978", + Network.Optimism: "0x0994206dfE8De6Ec6920FF4D779B0d950605Fb53", +}.get(chain.id, None) diff --git a/yearn/db/models.py b/yearn/db/models.py index 99a6caf11..639385c74 100644 --- a/yearn/db/models.py +++ b/yearn/db/models.py @@ -33,11 +33,99 @@ class Snapshot(SQLModel, table=True): block_id: int = Field(foreign_key="block.id") block: Block = Relationship(back_populates="snapshots") + + +class Event: + isOldApi = False + event = None + txn_hash = "" + multi_harvest = False + def __init__(self, isOldApi, event, txn_hash): + self.isOldApi = isOldApi + self.event = event + self.txn_hash = txn_hash + +class Transactions(SQLModel, table=True): + txn_hash: str = Field(primary_key=True) + chain_id: int + # Transaction fields + block: int + txn_to: str + txn_from: str + txn_gas_used: int + txn_gas_price: int + eth_price_at_block: float + call_cost_usd: float + call_cost_eth: float + kp3r_price_at_block: float + kp3r_paid: int + kp3r_paid_usd: float + keeper_called: bool + # Date fields + date: datetime + date_string: str + timestamp: str + updated_timestamp: datetime + reports: List["Reports"] = Relationship(back_populates="txn") + + +class Reports(SQLModel, table=True): + id: int = Field(primary_key=True) + chain_id: int + # Transaction fields + block: int + txn_hash: str + txn_hash: str = Field(default=None, foreign_key="transactions.txn_hash") + txn: Transactions = Relationship(back_populates="reports") + # StrategyReported fields + vault_address: str + strategy_address: str + gain: int + loss: int + debt_paid: int + total_gain: int + total_loss: int + total_debt: int + debt_added: int + debt_ratio: int + # Looked-up fields + want_token: str + token_symbol: str + want_price_at_block: int + want_gain_usd: int + gov_fee_in_want: int + strategist_fee_in_want: int + gain_post_fees: int + rough_apr_pre_fee: float + rough_apr_post_fee: float + vault_api: str + vault_name: str + vault_symbol: str + vault_decimals: int + strategy_name: str + strategy_api: str + strategist: str + previous_report_id: int + multi_harvest: bool + # Date fields + date: datetime + date_string: str + timestamp: str + updated_timestamp: datetime + # KeepCRV + keep_crv: int + keep_crv_percent: int + crv_price_usd: int + keep_crv_value_usd: int + yvecrv_minted: int + + pguser = os.environ.get('PGUSER', 'postgres') pgpassword = os.environ.get('PGPASSWORD', 'yearn') pghost = os.environ.get('PGHOST', 'localhost') pgdatabase = os.environ.get('PGDATABASE', 'yearn') -dsn = f'postgresql://{pguser}:{pgpassword}@{pghost}:5432/{pgdatabase}' +pgport = int(os.environ.get('PGPORT', 5432)) +dsn = f'postgresql://{pguser}:{pgpassword}@{pghost}:{pgport}/{pgdatabase}' engine = create_engine(dsn, echo=False) diff --git a/yearn/decorators.py b/yearn/decorators.py index 3604394ae..fb7020bb5 100644 --- a/yearn/decorators.py +++ b/yearn/decorators.py @@ -1,5 +1,5 @@ import _thread -import signal +import asyncio import functools import logging import signal @@ -24,14 +24,31 @@ def wrap(self): def wait_or_exit_before(func): @functools.wraps(func) - def wrap(self): - self._done.wait() - if self._has_exception: - logger.error(self._exception) - _thread.interrupt_main(signal.SIGTERM) - return func(self) + async def wrap(self): + task: asyncio.Task = self._task + logger.debug("waiting for %s", self) + while not self._done.is_set() and not task.done(): + await asyncio.sleep(10) + logger.debug("%s not done", self) + logger.debug("loading %s complete", self) + if task.done() and (e := task.exception()): + logger.debug('task %s has exception %s, awaiting', task, e) + raise e + return await func(self) return wrap +_main_thread_loop = asyncio.get_event_loop() + +def set_exc(func): + @functools.wraps(func) + async def wrap(self): + # in case this loads in a diff thread + try: + return await func(self) + except Exception as e: + self._done.set() + raise e + return wrap def wait_or_exit_after(func): @functools.wraps(func) diff --git a/yearn/entities.py b/yearn/entities.py index af5d87b0a..ec61edf4a 100644 --- a/yearn/entities.py +++ b/yearn/entities.py @@ -3,12 +3,15 @@ from datetime import date, datetime, timedelta from decimal import Decimal from functools import cached_property, lru_cache +from typing import Union from brownie import chain from brownie.network.event import _EventItem from brownie.network.transaction import TransactionReceipt from cachetools.func import ttl_cache +from eth_portfolio.address import PortfolioAddress from pony.orm import * +from typing_extensions import Self from y import Contract, get_price from y.time import closest_block_after_timestamp from y.contracts import contract_creation_block @@ -17,6 +20,8 @@ from yearn.treasury.constants import BUYER from yearn.utils import dates_between +Comparable = Union[str, Contract, PortfolioAddress, Self, None] + db = Database() @@ -81,6 +86,22 @@ class Address(db.Entity): vesting_escrows = Set("VestingEscrow", reverse="address") vests_received = Set("VestingEscrow", reverse="recipient") vests_funded = Set("VestingEscrow", reverse="funder") + + @property + def contract(self) -> Contract: + return Contract(self.address) + + def __eq__(self, other: Comparable): + if isinstance(other, str): + return self.address == other + elif isinstance(other, (Contract, PortfolioAddress)): + return self.address == other.address + elif isinstance(other, Address): + return self.chain == other.chain and self.address == other.address + return False + + def __hash__(self): + return super().__hash__() class Token(db.Entity): @@ -98,10 +119,26 @@ class Token(db.Entity): address = Required(Address, column="address_id") streams = Set('Stream', reverse="token") vesting_escrows = Set("VestingEscrow", reverse="token") - + @property + def contract(self) -> Contract: + return Contract(self.address.address) + + @cached_property def scale(self) -> int: return 10 ** self.decimals + + def __eq__(self, other: Union[Comparable, Address]): + if other is None or isinstance(other, str): + return self.address == other + elif isinstance(other, (Contract, PortfolioAddress)): + return self.address == other.address + elif isinstance(other, (Address, Token)): + return self.chain == other.chain and self.address == other.address + raise TypeError(type(other)) + + def __hash__(self): + return super().__hash__() # Used for wallet exporter and other analysis @@ -119,11 +156,11 @@ class UserTx(db.Entity): type = Required(str, index=True) from_address = Required(Address, reverse="user_tx_from", column="from", index=True) to_address = Required(Address, reverse="user_tx_to", column="to", index=True) - amount = Required(Decimal,38,18) - price = Required(Decimal,38,18) - value_usd = Required(Decimal,38,18) - gas_used = Required(Decimal,38,1) - gas_price = Required(Decimal,38,1) + amount = Required(Decimal, 38, 18) + price = Required(Decimal, 38, 18) + value_usd = Required(Decimal, 38, 18) + gas_used = Required(Decimal, 38, 1) + gas_price = Required(Decimal, 38, 1) @@ -142,9 +179,7 @@ class TxGroup(db.Entity): @property def top_txgroup(self): - if self.parent_txgroup is None: - return self - return self.parent_txgroup.top_txgroup + return self.parent_txgroup.top_txgroup if self.parent_txgroup else self @property def full_string(self): @@ -172,13 +207,13 @@ class TreasuryTx(db.Entity): log_index = Optional(int) composite_key(hash, log_index) token = Required(Token, reverse="treasury_tx", column="token_id", index=True) - from_address = Required(Address, reverse="treasury_tx_from", column="from", index=True) + from_address = Optional(Address, reverse="treasury_tx_from", column="from", index=True) to_address = Optional(Address, reverse="treasury_tx_to", column="to", index=True) - amount = Required(Decimal,38,18) - price = Optional(Decimal,38,18) - value_usd = Optional(Decimal,38,18) - gas_used = Optional(Decimal,38,1) - gas_price = Optional(Decimal,38,1) + amount = Required(Decimal, 38, 18) + price = Optional(Decimal, 38, 18) + value_usd = Optional(Decimal, 38, 18) + gas_used = Optional(Decimal, 38, 1) + gas_price = Optional(Decimal, 38, 1) txgroup = Required(TxGroup, reverse="treasury_tx", column="txgroup_id", index=True) composite_index(chain,txgroup) @@ -192,10 +227,8 @@ def _transaction(self): return get_transaction(self.hash) @property - def _to_nickname(self): - if not self.to_address: - return None - return self.to_address.nickname + def _to_nickname(self) -> typing.Optional[str]: + return self.to_address.nickname if self.to_address else None @property def _from_nickname(self): @@ -273,8 +306,8 @@ def get_or_create_entity(cls, log: _EventItem) -> "Stream": try: return Stream[stream_id] except ObjectNotFound: - from yearn.outputs.postgres.utils import (cache_address, - cache_token, + from yearn.outputs.postgres.utils import (address_dbid, + token_dbid, cache_txgroup) txgroup = { @@ -283,10 +316,10 @@ def get_or_create_entity(cls, log: _EventItem) -> "Stream": }.get(to_address, "Other Grants") txgroup = cache_txgroup(txgroup) - stream_contract = cache_address(log.address) - token = cache_token(Contract(log.address).token()) - from_address = cache_address(from_address) - to_address = cache_address(to_address) + stream_contract = address_dbid(log.address) + token = token_dbid(Contract(log.address).token()) + from_address = address_dbid(from_address) + to_address = address_dbid(to_address) entity = Stream( stream_id = stream_id, @@ -430,18 +463,18 @@ def rugged_on(self) -> typing.Optional[date]: @staticmethod def get_or_create_entity(event: _EventItem) -> "VestingEscrow": - from yearn.outputs.postgres.utils import cache_address, cache_token + from yearn.outputs.postgres.utils import address_dbid, cache_token print(event) funder, token, recipient, escrow, amount, start, duration, cliff_length = event.values() - escrow_address_entity = cache_address(escrow) - escrow = VestingEscrow.get(address=escrow_address_entity) + escrow_address_dbid = address_dbid(escrow) + escrow = VestingEscrow.get(address=escrow_address_dbid) if escrow is None: token_entity = cache_token(token) escrow = VestingEscrow( - address = escrow_address_entity, - funder = cache_address(funder), - recipient = cache_address(recipient), + address = escrow_address_dbid, + funder = address_dbid(funder), + recipient = address_dbid(recipient), token = token_entity, amount = amount / token_entity.scale, start_timestamp = datetime.fromtimestamp(start), @@ -529,13 +562,13 @@ class PartnerHarvestEvent(db.Entity): block = Required(int) timestamp = Required(int) - balance = Required(Decimal,38,18) - total_supply = Required(Decimal,38,18) - vault_price = Required(Decimal,38,18) - balance_usd = Required(Decimal,38,18) - share = Required(Decimal,38,18) - payout_base = Required(Decimal,38,18) - protocol_fee = Required(Decimal,38,18) + balance = Required(Decimal, 38, 18) + total_supply = Required(Decimal, 38, 18) + vault_price = Required(Decimal, 38, 18) + balance_usd = Required(Decimal, 38, 18) + share = Required(Decimal, 38, 18) + payout_base = Required(Decimal, 38, 18) + protocol_fee = Required(Decimal, 38, 18) wrapper = Required(Address, reverse='partners_tx', index=True) # we use `Address` instead of `Token` because some partner wrappers are unverified vault = Required(Token, index=True) diff --git a/yearn/helpers/exporter.py b/yearn/helpers/exporter.py index 01bf79426..d5ba5d9c7 100644 --- a/yearn/helpers/exporter.py +++ b/yearn/helpers/exporter.py @@ -8,14 +8,14 @@ from typing import (Awaitable, Callable, Literal, NoReturn, Optional, TypeVar, overload) +import a_sync import eth_retry from brownie import chain from dank_mids.controller import instances from y.networks import Network from y.time import closest_block_after_timestamp_async -from yearn import constants -from yearn.helpers.snapshots import (RESOLUTION, +from yearn.helpers.snapshots import (RESOLUTION, Resolution, _generate_snapshot_range_forward, _generate_snapshot_range_historical, _get_intervals) @@ -41,7 +41,7 @@ def __init__( export_fn: Callable[[T], Awaitable[None]], start_block: int, concurrency: Optional[int] = 1, - resolution: Literal = RESOLUTION + resolution: Resolution = RESOLUTION ) -> None: """ Required Args: @@ -69,14 +69,14 @@ def __init__( self.start_block = start_block self.start_timestamp = datetime.fromtimestamp(chain[start_block].timestamp, tz=timezone.utc) if resolution != RESOLUTION: - logger.warn(f"{self.full_name}: Overriding resolution with custom value '{resolution}', env has '{RESOLUTION}' !") + logger.warning(f"{self.full_name}: Overriding resolution with custom value '{resolution}', env has '{RESOLUTION}' !") self._resolution = resolution self._data_fn = data_fn self._export_fn = eth_retry.auto_retry(export_fn) self._snapshots_fetched = 0 self._snapshots_exported = 0 - self._semaphore = asyncio.Semaphore(concurrency) + self._historical_queue = a_sync.ProcessingQueue(self.export_historical_snapshot, concurrency, return_data=False) @overload def run(self, direction: Literal["historical"] = "historical") -> None: @@ -114,14 +114,16 @@ async def export_future(self) -> NoReturn: # Bump forward to the next snapshot, as the historical coroutine will take care of this one. start = start + interval async for snapshot in _generate_snapshot_range_forward(start, interval): - asyncio.create_task(self.export_snapshot(snapshot)) + a_sync.create_task(self.export_snapshot(snapshot), skip_gc_until_done=True) async def export_history(self) -> None: """ Exports all historical data. This coroutine runs for a finite duration. """ intervals = _get_intervals(self._start_time, self._resolution) for resolution in intervals: - snapshot_generator = _generate_snapshot_range_historical(self.start_timestamp, resolution, intervals) - await asyncio.gather(*[self.export_historical_snapshot(snapshot) async for snapshot in snapshot_generator]) + async for snapshot in _generate_snapshot_range_historical(self.start_timestamp, resolution, intervals): + self._historical_queue(snapshot) + await self._historical_queue.join() + logger.info(f"historical {self.full_name} {resolution} export complete in {self._get_runtime(datetime.now(tz=timezone.utc))}") logger.info(f"historical {self.full_name} export complete in {self._get_runtime(datetime.now(tz=timezone.utc))}") @log_exceptions @@ -139,9 +141,8 @@ async def export_snapshot(self, snapshot: datetime) -> None: @log_exceptions async def export_historical_snapshot(self, snapshot: datetime) -> None: - async with self._semaphore: - if not await self._has_data(snapshot): - await self.export_snapshot(snapshot) + if not await self._has_data(snapshot): + await self.export_snapshot(snapshot) # Datastore Methods diff --git a/yearn/iearn.py b/yearn/iearn.py index c26e681e2..d3b1bab9d 100644 --- a/yearn/iearn.py +++ b/yearn/iearn.py @@ -68,7 +68,7 @@ async def describe(self, block=None) -> dict: "pooled balance": res["pool"] / vault.scale, "price per share": res['getPricePerFullShare'] / 1e18, "token price": price, - "tvl": res["pool"] / vault.scale * price, + "tvl": res["pool"] / vault.scale * float(price), "address": vault.vault, "version": "iearn", } diff --git a/yearn/logs.py b/yearn/logs.py index 82600cad6..7299ae331 100644 --- a/yearn/logs.py +++ b/yearn/logs.py @@ -40,6 +40,18 @@ def basicConfig(**kwargs) -> None: silence_logger('web3.providers.HTTPProvider') # we use AsyncHTTPProvider silence_logger('web3.RequestManager') # not really sure lol silence_logger('dank_mids.should_batch') # this is only really useful when optimizing dank_mids internals + silence_logger("y.prices.BASE") + silence_logger("y._db.common") + silence_logger("yearn.middleware") + silence_logger("dank_mids._requests") + silence_logger("y._db.utils.logs") + silence_logger("a_sync.primitives.locks.prio_semaphore") + silence_logger("a_sync._smart") + silence_logger("a_sync.a_sync.function[]") + silence_logger("a_sync.a_sync.method") + silence_logger("a_sync.utils.iterators") + silence_logger("eth_portfolio._ydb.token_transfers") + silence_logger("a_sync.executor.AsyncThreadPoolExecutor") def silence_logger(name: str): - logging.getLogger(name).setLevel(logging.CRITICAL) \ No newline at end of file + logging.getLogger(name).setLevel(logging.CRITICAL) diff --git a/yearn/middleware/middleware.py b/yearn/middleware/middleware.py index 85e5c47af..1a80c459e 100644 --- a/yearn/middleware/middleware.py +++ b/yearn/middleware/middleware.py @@ -56,7 +56,10 @@ def should_cache(method, params): def cache_middleware(make_request, w3): - make_request_cached = memory.cache(make_request) + @memory.cache + def make_request_cached(method, params): + return make_request(method, params) + #make_request_cached = memory.cache(make_request) def middleware(method, params): logger.debug("%s %s", method, params) if should_cache(method, params): diff --git a/yearn/multicall2.py b/yearn/multicall2.py index 649d6ea68..c6cf99240 100644 --- a/yearn/multicall2.py +++ b/yearn/multicall2.py @@ -7,15 +7,14 @@ import eth_retry import requests -from brownie import Contract, chain, web3 -from brownie.network.contract import _ContractMethod +from brownie import chain, web3 +from brownie.exceptions import VirtualMachineError +from dank_mids.brownie_patch.types import DankContractMethod from eth_abi.exceptions import InsufficientDataBytes -from y.contracts import contract_creation_block -from y.networks import Network +from y import Contract, Network, contract_creation_block from yearn.exceptions import MulticallError from yearn.typing import Block -from yearn.utils import contract JSONRPC_BATCH_MAX_SIZE = int(os.environ.get("JSONRPC_BATCH_MAX_SIZE", 10_000)) # Currently set arbitrarily, necessaary for certain node-as-a-service providers. MULTICALL_MAX_SIZE = int(os.environ.get("MULTICALL_MAX_SIZE", 500)) # Currently set arbitrarily @@ -27,7 +26,9 @@ Network.Optimism: '0xcA11bde05977b3631167028862bE2a173976CA11', # Multicall 3 Network.Base: '0xcA11bde05977b3631167028862bE2a173976CA11' # MC3 } -multicall2 = contract(MULTICALL2[chain.id]) +multicall2 = Contract(MULTICALL2[chain.id]) +# TODO: move this bug fix somewhere more appropriate +multicall2.tryAggregate.abi['outputs'] = [dict(x) for x in multicall2.tryAggregate.abi['outputs']] def fetch_multicall(*calls, block: Optional[Block] = None, require_success: bool = False) -> List[Any]: @@ -68,11 +69,21 @@ def fetch_multicall(*calls, block: Optional[Block] = None, require_success: bool False, multicall_input, block_identifier=block or 'latest' ) except ValueError as e: - if 'out of gas' in str(e) or 'execution aborted (timeout = 10s)' in str(e): + if 'out of gas' in str(e) or 'execution aborted (timeout = 10s)' in str(e) or ("unknown typed error" in str(e).lower() and len(calls) > 1): halfpoint = len(calls) // 2 batch0 = fetch_multicall(*calls[:halfpoint],block=block,require_success=require_success) batch1 = fetch_multicall(*calls[halfpoint:],block=block,require_success=require_success) return batch0 + batch1 + elif "unknown typed error" in str(e).lower() and len(calls) == 1: + contract_address, fn_name, *args = calls[0] + contract_call = getattr(Contract(contract_address), fn_name) + try: + return [contract_call(*args, block_identifier=block)] + except (VirtualMachineError, ValueError) as e: + if require_success: + #trying smth + raise MulticallError(e) from e + return [None] raise for fn, (ok, data) in zip(fn_list, result): @@ -81,7 +92,7 @@ def fetch_multicall(*calls, block: Optional[Block] = None, require_success: bool decoded.append(fn.decode_output(data)) except (AssertionError, InsufficientDataBytes): if require_success: - raise MulticallError() + raise MulticallError decoded.append(None) # NOTE this will only run if `require_success` is True @@ -123,7 +134,7 @@ async def fetch_multicall_async(*calls, block: Optional[Block] = None, require_s return results -def _get_fn(contract: Contract, fn_name: str, fn_inputs: Any) -> _ContractMethod: +def _get_fn(contract: Contract, fn_name: str, fn_inputs: Any) -> DankContractMethod: fn = getattr(contract, fn_name) # check that there aren't multiple functions with the same name if hasattr(fn, "_get_fn_from_args"): diff --git a/yearn/outputs/describers/vault.py b/yearn/outputs/describers/vault.py index 6832b2bd0..ecae030d6 100644 --- a/yearn/outputs/describers/vault.py +++ b/yearn/outputs/describers/vault.py @@ -29,8 +29,7 @@ async def describe_wallets(self, vault_address, block=None): wallet: { "token balance": bal, "usd balance": bal * price - } for wallet, bal in balances.items() - } + } for wallet, bal in balances.items() } } info['active wallets'] = sum(1 if balances['usd balance'] > ACTIVE_WALLET_USD_THRESHOLD else 0 for balances in info['wallet balances'].values()) diff --git a/yearn/outputs/postgres/utils.py b/yearn/outputs/postgres/utils.py index 9c1d7a622..fdc833cd1 100644 --- a/yearn/outputs/postgres/utils.py +++ b/yearn/outputs/postgres/utils.py @@ -1,10 +1,11 @@ import logging from decimal import Decimal +from functools import lru_cache from typing import Dict, Optional from brownie import ZERO_ADDRESS, chain, convert from brownie.convert.datatypes import HexString -from pony.orm import db_session, select +from pony.orm import IntegrityError, TransactionIntegrityError, db_session, commit, select from y import Contract, ContractNotVerified, Network from yearn.entities import (Address, Chain, Token, TreasuryTx, TxGroup, UserTx, @@ -18,90 +19,134 @@ Network.Mainnet: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", }.get(chain.id, 'not on this chain') +@lru_cache(maxsize=1) @db_session -def cache_chain(): - return Chain.get(chainid=chain.id) or Chain( +def cache_chain() -> Chain: + entity = Chain.get(chainid=chain.id) or Chain( chain_name=Network.name(), chainid=chain.id, victoria_metrics_label=Network.label(), ) + commit() + return entity + +@lru_cache(maxsize=1) +def chain_dbid() -> int: + return cache_chain().chain_dbid @db_session def cache_address(address: str) -> Address: address = convert.to_address(address) - chain = cache_chain() - address_entity = Address.get(address=address, chain=chain) - if not address_entity: - if is_contract(address): - try: - nickname = f"Contract: {Contract(address)._build['contractName']}" - except ContractNotVerified as e: - nickname = f"Non-Verified Contract: {address}" - address_entity = Address( - address=address, - chain=chain, - is_contract=True, - nickname=nickname, - ) - else: - address_entity = Address( - address=address, - chain=chain, - is_contract=False, - ) - return address_entity + chain = chain_dbid() + tries = 0 + while True: + if entity := Address.get(address=address, chain=chain): + return entity + try: + # if another thread beat us to the insert, we will get an exception that won't recurr next iteration + if is_contract(address): + try: + nickname = f"Contract: {Contract(address)._build['contractName']}" + except ContractNotVerified as e: + nickname = f"Non-Verified Contract: {address}" + entity = Address( + address=address, + chain=chain, + is_contract=True, + nickname=nickname, + ) + else: + entity = Address( + address=address, + chain=chain, + is_contract=False, + ) + commit() + break + except (IntegrityError, TransactionIntegrityError) as e: + if tries > 4: + raise e + logger.debug("%s for %s %s", e, address_dbid, address) + tries += 1 + return entity + +@lru_cache(maxsize=None) +def address_dbid(address: str) -> int: + return cache_address(address).address_id @db_session def cache_token(address: str) -> Token: + if token := Token.get(address=address_dbid(address)): + return token + + address = convert.to_address(address) + + # get token attributes + if address == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": + symbol, name = { + Network.Mainnet: ("ETH","Ethereum"), + Network.Fantom: ("FTM","Fantom"), + Network.Arbitrum: ("ETH", "Ethereum"), + Network.Optimism: ("ETH", "Ethereum"), + }[chain.id] + decimals = 18 + else: + token = Contract(address) + symbol, name, decimals = fetch_multicall([token,'symbol'], [token,'name'], [token,'decimals']) + + # MKR contract returns name and symbol as bytes32 which is converted to a brownie HexString + # try to decode it + if isinstance(name, HexString): + name = hex_to_string(name) + if isinstance(symbol, HexString): + symbol = hex_to_string(symbol) + + # update address nickname for token address_entity = cache_address(address) - token = Token.get(address=address_entity) - if not token: - address = convert.to_address(address) - if address == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": - symbol, name = { - Network.Mainnet: ("ETH","Ethereum"), - Network.Fantom: ("FTM","Fantom"), - Network.Arbitrum: ("ETH", "Ethereum"), - Network.Optimism: ("ETH", "Ethereum"), - }[chain.id] - decimals = 18 - else: - token = Contract(address) - symbol, name, decimals = fetch_multicall([token,'symbol'],[token,'name'],[token,'decimals']) - - # MKR contract returns name and symbol as bytes32 which is converted to a brownie HexString - # try to decode it - if isinstance(name, HexString): - name = hex_to_string(name) - if isinstance(symbol, HexString): - symbol = hex_to_string(symbol) - - if address_entity.nickname is None or address_entity.nickname.startswith("Contract: "): - # Don't overwrite any intentionally set nicknames, if applicable - address_entity.nickname = f"Token: {name}" - + if address_entity.nickname is None or address_entity.nickname.startswith("Contract: "): + # Don't overwrite any intentionally set nicknames, if applicable + address_entity.nickname = f"Token: {name}" + + # insert to db + tries = 0 + while True: + if token := Token.get(address=address_dbid(address)): + return token try: token = Token( - address=address_entity, + address=address_dbid(address), symbol=symbol, name=name, decimals= 0 if address == UNI_V3_POS or decimals is None else decimals, - chain=cache_chain() + chain=chain_dbid() ) + commit() + logger.info(f'token {symbol} added to postgres') + except (IntegrityError, TransactionIntegrityError) as e: + if tries >= 4: + raise e + tries += 1 + logger.warning("%s for %s %s", e, cache_token, address) except ValueError as e: """ Give us a little more info to help with debugging. """ raise ValueError(str(e), token.address, symbol, name, decimals) - logger.info(f'token {symbol} added to postgres') - return token + +@lru_cache(maxsize=None) +def token_dbid(address: str) -> int: + return cache_token(address).token_id @db_session def cache_txgroup(name: str, parent: Optional[TxGroup] = None) -> TxGroup: - _txgroup = TxGroup.get(name=name) - if not _txgroup: - _txgroup = TxGroup(name=name, parent_txgroup=parent) - logger.info(f'TxGroup {name} added to postgres') + while not (_txgroup := TxGroup.get(name=name)): + try: + TxGroup(name=name, parent_txgroup=parent) + commit() + logger.info(f'TxGroup {name} added to postgres') + except (IntegrityError, TransactionIntegrityError) as e: + logger.warning("%s for %s %s", e, cache_txgroup, name) if parent and parent != _txgroup.parent_txgroup: _txgroup.parent_txgroup = parent + commit() return _txgroup @db_session diff --git a/yearn/outputs/victoria/victoria.py b/yearn/outputs/victoria/victoria.py index 87d9ab2b1..89b9149e3 100644 --- a/yearn/outputs/victoria/victoria.py +++ b/yearn/outputs/victoria/victoria.py @@ -19,9 +19,8 @@ @eth_retry.auto_retry async def has_data(ts, data_query): - base_url = os.environ.get('VM_URL', 'http://victoria-metrics:8428') # query for a metric which should be present - url = f'{base_url}/api/v1/query?query={data_query}&time={int(ts)}' + url = f'{BASE_URL}/api/v1/query?query={data_query}&time={int(ts)}' headers = { 'Connection': 'close', } diff --git a/yearn/partners/snapshot.py b/yearn/partners/snapshot.py index f7ba30adc..da67627c7 100644 --- a/yearn/partners/snapshot.py +++ b/yearn/partners/snapshot.py @@ -9,8 +9,8 @@ from typing import (AsyncGenerator, Callable, Dict, List, Optional, Set, Tuple, Union) +import a_sync import pandas as pd -from a_sync import AsyncThreadPoolExecutor from async_lru import alru_cache from async_property import async_cached_property from brownie import chain, convert, web3 @@ -21,12 +21,10 @@ from tqdm.asyncio import tqdm_asyncio from web3._utils.abi import filter_by_name from web3._utils.events import construct_event_topic_set -from y import ERC20, Contract, get_price -from y.contracts import contract_creation_block, contract_creation_block_async +from y import Contract, ERC20, Network, contract_creation_block_async, get_price from y.exceptions import continue_if_call_reverted -from y.networks import Network from y.time import get_block_timestamp_async, last_block_on_date -from y.utils.events import BATCH_SIZE, get_logs_asap_generator +from y.utils.events import BATCH_SIZE, Events from yearn.constants import ERC20_TRANSFER_EVENT_HASH from yearn.events import decode_logs @@ -40,8 +38,8 @@ try: from yearn.entities import PartnerHarvestEvent - from yearn.outputs.postgres.utils import (cache_address, cache_chain, - cache_token) + from yearn.outputs.postgres.utils import (address_dbid, chain_dbid, + token_dbid) USE_POSTGRES_CACHE = True except OperationalError as e: if "Is the server running on that host and accepting TCP/IP connections?" in str(e): @@ -51,7 +49,7 @@ logger = logging.getLogger(__name__) -threads = AsyncThreadPoolExecutor(8) +threads = a_sync.AsyncThreadPoolExecutor(8) async def get_timestamps(blocks: Tuple[int,...]) -> DatetimeScalar: data = await asyncio.gather(*[get_block_timestamp_async(block) for block in blocks]) @@ -64,7 +62,8 @@ def __init__(self, name: str, vault: str, wrapper: str) -> None: self.name = name self.vault = convert.to_address(vault) self.wrapper = convert.to_address(wrapper) - self.semaphore = asyncio.Semaphore(1) + # NOTE: We need to use a queue here to ensure at most one active coroutine is populating the db for this wrapper + self.get_data = a_sync.ProcessingQueue(self._get_data, 1) @cached_property def _vault(self) -> Vault: @@ -78,35 +77,33 @@ def vault_contract(self) -> Contract: def scale(self) -> int: return self._vault.scale - async def get_data(self, partner: "Partner", use_postgres_cache: bool, verbose: bool = False) -> DataFrame: - # NOTE: We need to use a semaphore here to ensure at most one active coroutine is populating the db for this wrapper - async with self.semaphore: - if verbose: - logger.info(f'starting {partner.name} {self} {self.name} {self.wrapper} for {self.vault}') - if use_postgres_cache: - cache = await threads.run(self.read_cache) - try: - max_cached_block = int(cache['block'].max()) - logger.debug('%s %s is cached thru block %s', partner.name, self.name, max_cached_block) - start_block = max_cached_block + 1 if partner.start_block is None else max(partner.start_block, max_cached_block + 1) - except KeyError: - start_block = partner.start_block - logger.debug('no harvests cached for %s %s', partner.name, self.name) - logger.debug('start block: %s', start_block) - else: + async def _get_data(self, partner: "Partner", use_postgres_cache: bool, verbose: bool = False) -> DataFrame: + if verbose: + logger.info(f'starting {partner.name} {self} {self.name} {self.wrapper} for {self.vault}') + if use_postgres_cache: + cache = await threads.run(self.read_cache) + try: + max_cached_block = int(cache['block'].max()) + logger.debug('%s %s is cached thru block %s', partner.name, self.name, max_cached_block) + start_block = max_cached_block + 1 if partner.start_block is None else max(partner.start_block, max_cached_block + 1) + except KeyError: start_block = partner.start_block - - futs = [] - async for protocol_fees in self.protocol_fees(start_block=start_block): - futs.append(asyncio.create_task(process_harvests(self, protocol_fees))) - data = [data for data in await asyncio.gather(*futs) if data is not None] - data = pd.concat(data) if data else DataFrame() - - if use_postgres_cache: - await threads.run(cache_data, data) - data = pd.concat([cache, data]) - - return data + logger.debug('no harvests cached for %s %s', partner.name, self.name) + logger.debug('start block: %s', start_block) + else: + start_block = partner.start_block + + futs = [] + async for protocol_fees in self.protocol_fees(start_block=start_block): + futs.append(asyncio.create_task(process_harvests(self, protocol_fees))) + data = [data for data in await asyncio.gather(*futs) if data is not None] + data = pd.concat(data) if data else DataFrame() + + if use_postgres_cache: + await threads.run(cache_data, data) + data = pd.concat([cache, data]) + + return data @db_session def read_cache(self) -> DataFrame: @@ -159,9 +156,10 @@ async def protocol_fees(self, start_block: int = None) -> AsyncGenerator[Dict[in logs_start_block = await contract_creation_block_async(self.vault) while start_block and logs_start_block + BATCH_SIZE <= start_block: logs_start_block += BATCH_SIZE - - async for logs in get_logs_asap_generator(self.vault, topics, from_block=logs_start_block): - yield {log.block_number: Decimal(log['value']) / Decimal(vault.scale) for log in decode_logs(logs) if should_include(log)} + + async for event in Events(addresses=self.vault, topics=topics, from_block=logs_start_block): + if should_include(event): + yield {event.block_number: Decimal(event['value']) / Decimal(vault.scale)} async def balances(self, blocks: Tuple[int,...]) -> List[Decimal]: balances = await asyncio.gather(*[self.vault_contract.balanceOf.coroutine(self.wrapper, block_identifier=block) for block in blocks]) @@ -232,24 +230,24 @@ class WildcardWrapper: async def unwrap(self) -> List[Wrapper]: registry = Registry() wrappers = [self.wrapper] if isinstance(self.wrapper, str) else self.wrapper + vaults = await registry.vaults topics = construct_event_topic_set( - filter_by_name('Transfer', registry.vaults[0].vault.abi)[0], + filter_by_name('Transfer', vaults[0].vault.abi)[0], web3.codec, {'receiver': wrappers}, ) - addresses = [str(vault.vault) for vault in registry.vaults] - from_block = min(await asyncio.gather(*[threads.run(contract_creation_block, address) for address in addresses])) + addresses = [str(vault.vault) for vault in vaults] + from_block = min(await asyncio.gather(*[contract_creation_block_async(address) for address in addresses])) # wrapper -> {vaults} deposits = defaultdict(set) - async for logs in get_logs_asap_generator(addresses, topics, from_block): - for log in decode_logs(logs): - deposits[log['receiver']].add(log.address) + async for event in Events(addresses=addresses, topics=topics, from_block=from_block): + deposits[event['receiver']].add(event.address) return [ Wrapper(name=vault.name, vault=str(vault.vault), wrapper=wrapper) for wrapper in wrappers - for vault in registry.vaults + for vault in vaults if str(vault.vault) in deposits[wrapper] ] @@ -288,8 +286,8 @@ class GearboxWrapper(Wrapper): """ Use Gearbox CAs as wrappers. """ - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + def __post_init__(self): + self.get_balance = a_sync.ProcessingQueue(self._get_balance, 5000) def __hash__(self) -> int: return hash(self.vault + self.wrapper) @@ -322,20 +320,23 @@ async def count_credit_accounts(self, block: int) -> int: except Exception as e: continue_if_call_reverted(e) - async def get_balance(self, credit_account: Address, block: int) -> Decimal: - async with granular_balance_semaphore: - return await self.vault_contract.balanceOf.coroutine(credit_account, block_identifier=block) / Decimal(self.scale) + @async_cached_property + async def _transfers_container(self) -> Events: + from_block = await contract_creation_block_async(self.wrapper) + return Events(addresses=self.vault, topics=[ERC20_TRANSFER_EVENT_HASH], from_block=from_block) + + async def _get_balance(self, credit_account: Address, block: int) -> Decimal: + return await self.vault_contract.balanceOf.coroutine(credit_account, block_identifier=block) / Decimal(self.scale) async def get_vault_depositors(self, block: int) -> Set[Address]: - from_block = await contract_creation_block_async(self.wrapper) - return { - transfer['receiver'] - async for logs in get_logs_asap_generator(self.vault, [ERC20_TRANSFER_EVENT_HASH], from_block=from_block, to_block=block, chronological=False) - for transfer in decode_logs(logs) - } + transfers = await self._transfers_container + return {transfer['receiver'] async for transfer in transfers.events(to_block=block)} class InverseWrapper(Wrapper): + def __post_init__(self): + self.get_balance = a_sync.ProcessingQueue(self._get_balance, num_workers=5000) + def __hash__(self) -> int: return hash(self.vault + self.wrapper) @@ -344,19 +345,17 @@ async def balances(self, blocks) -> List[Decimal]: async def get_tvl(self, block: int) -> Decimal: # We use futs instead of gather here so we can process logs as they come in vs waiting for all of them before proceeding - futs = [asyncio.create_task(self.get_balance(escrow, block)) async for escrow in self.get_escrows(block)] + futs = [self.get_balance(escrow, block) async for escrow in self.get_escrows(block)] return sum(await asyncio.gather(*futs)) - async def get_balance(self, escrow: Address, block: int) -> Decimal: + async def _get_balance(self, escrow: Address, block: int) -> Decimal: escrow = await Contract.coroutine(escrow) - async with granular_balance_semaphore: - return await escrow.balance.coroutine(block_identifier=block) / Decimal(self.scale) + return await escrow.balance.coroutine(block_identifier=block) / Decimal(self.scale) async def get_escrows(self, block) -> AsyncGenerator[Address, None]: wrapper_contract = await Contract.coroutine(self.wrapper) - async for logs in get_logs_asap_generator(self.wrapper, [wrapper_contract.topics['CreateEscrow']], to_block=block, chronological=False): - for event in decode_logs(logs): - yield event['escrow'] + async for event in wrapper_contract.events.CreateEscrow.events(to_block=block): + yield event['escrow'] class DelegatedDepositWrapper(Wrapper): @@ -408,8 +407,6 @@ async def unwrap(self) -> List[Wrapper]: "qidao", "shapeshiftdao", "sturdy", "tempus", "wido", "yapeswap", "yieldster" ] -granular_balance_semaphore = asyncio.Semaphore(50_000) - @dataclass class Partner: name: str @@ -429,6 +426,8 @@ def __post_init__(self) -> None: self.start_block = last_block_on_date(self.start_date - timedelta(days=1)) + 1 else: raise TypeError("`start_date` must be a `datetime.date` object.") + if self.treasury: + self.treasury = convert.to_address(self.treasury) @async_cached_property @@ -436,10 +435,13 @@ async def flat_wrappers(self) -> List[Wrapper]: # unwrap wildcard wrappers to a flat list flat_wrappers = [] for wrapper in self.wrappers: + logger.info("loading %s wrapper %s", self, wrapper) if isinstance(wrapper, Wrapper): flat_wrappers.append(wrapper) + logger.info("loaded %s wrapper %s", self, wrapper) elif isinstance(wrapper, WildcardWrapper) or isinstance(wrapper, DelegatedDepositWildcardWrapper): flat_wrappers.extend(await wrapper.unwrap()) + logger.info("loaded %s wrapper %s", self, wrapper) return flat_wrappers async def process(self, use_postgres_cache: bool = USE_POSTGRES_CACHE, verbose: bool = False) -> Tuple[DataFrame,DataFrame]: @@ -542,9 +544,9 @@ def process_partners(partners: List[Partner], use_postgres_cache: bool = USE_POS total = 0 payouts = [] if not use_postgres_cache: - logger.warn('This script can run much faster for subsequent runs if you cache the data to postgres.') - logger.warn("Caching will be enabled by default if you run the yearn-exporter locally.") - logger.warn('To enable caching without running the exporter, run `make postgres` from project root.') + logger.warning('This script can run much faster for subsequent runs if you cache the data to postgres.') + logger.warning("Caching will be enabled by default if you run the yearn-exporter locally.") + logger.warning('To enable caching without running the exporter, run `make postgres` from project root.') partners_data: List[Tuple[DataFrame, DataFrame]] = asyncio.get_event_loop().run_until_complete( tqdm_asyncio.gather(*[partner.process(use_postgres_cache=use_postgres_cache, verbose=verbose) for partner in partners]) @@ -577,7 +579,7 @@ def cache_data(wrap: DataFrame) -> None: ''' for i, row in wrap.iterrows(): PartnerHarvestEvent( - chain=cache_chain(), + chain=chain_dbid(), block=row.block, timestamp=int(row.timestamp.timestamp()), balance=row.balance, @@ -588,8 +590,8 @@ def cache_data(wrap: DataFrame) -> None: payout_base=row.payout_base, protocol_fee=row.protocol_fee, # Use cache_address instead of cache_token because some wrappers aren't verified - wrapper=cache_address(row.wrapper), - vault=cache_token(row.vault), + wrapper=address_dbid(row.wrapper), + vault=token_dbid(row.vault), ) commit() diff --git a/yearn/prices/curve.py b/yearn/prices/curve.py index 74b528c16..0943464ff 100644 --- a/yearn/prices/curve.py +++ b/yearn/prices/curve.py @@ -23,17 +23,17 @@ from brownie import ZERO_ADDRESS, chain, convert, interface from brownie.convert import to_address from brownie.convert.datatypes import EthAddress -from cachetools.func import lru_cache, ttl_cache +from cachetools.func import lru_cache from y import Contract, Network, magic -from y.constants import EEE_ADDRESS -from y.exceptions import NodeNotSynced, PriceError +from y.exceptions import NodeNotSynced +from yearn import constants from yearn.decorators import sentry_catch_all, wait_or_exit_after from yearn.events import decode_logs, get_logs_asap from yearn.exceptions import UnsupportedNetwork from yearn.multicall2 import fetch_multicall, fetch_multicall_async from yearn.typing import Address, AddressOrContract, Block -from yearn.utils import Singleton, contract +from yearn.utils import Singleton logger = logging.getLogger(__name__) @@ -63,7 +63,6 @@ curve_contracts = { Network.Mainnet: { 'address_provider': ADDRESS_PROVIDER, - 'crv': '0xD533a949740bb3306d119CC777fa900bA034cd52', 'voting_escrow': '0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2', 'gauge_controller': '0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB', }, @@ -71,19 +70,15 @@ # Curve has not properly initialized the provider. contract(self.address_provider.get_address(5)) returns 0x0. # CurveRegistry class has extra handling to fetch registry in this case. 'address_provider': ADDRESS_PROVIDER, - 'crv': '0x712b3d230f3c1c19db860d80619288b1f0bdd0bd', }, Network.Fantom: { 'address_provider': ADDRESS_PROVIDER, - 'crv': '0x1E4F97b9f9F913c46F1632781732927B9019C68b', }, Network.Arbitrum: { 'address_provider': ADDRESS_PROVIDER, - 'crv': '0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978', }, Network.Optimism: { 'address_provider': ADDRESS_PROVIDER, - 'crv': '0x0994206dfE8De6Ec6920FF4D779B0d950605Fb53', } } @@ -103,7 +98,7 @@ class Ids(IntEnum): Curve_Tricrypto_Factory = 11 class CurveRegistry(metaclass=Singleton): - + # NOTE: before deprecating, figure out why this loads more pools than ypm @wait_or_exit_after def __init__(self) -> None: if chain.id not in curve_contracts: @@ -114,7 +109,6 @@ def __init__(self) -> None: self.voting_escrow = Contract(addrs['voting_escrow']) self.gauge_controller = Contract(addrs['gauge_controller']) - self.crv = contract(addrs['crv']) self.identifiers = defaultdict(list) # id -> versions self.registries = defaultdict(set) # registry -> pools self.factories = defaultdict(set) # factory -> pools @@ -124,8 +118,13 @@ def __init__(self) -> None: self._done = threading.Event() self._thread = threading.Thread(target=self.watch_events, daemon=True) - self._has_exception = False self._thread.start() + self._has_exception = False + + def ensure_loaded(self): + if not self._thread._started.is_set(): + logger.debug("starting thread") + self._thread.start() @sentry_catch_all def watch_events(self) -> None: @@ -239,6 +238,7 @@ def get_factory(self, pool: AddressOrContract) -> EthAddress: """ Get metapool factory that has spawned a pool. """ + self.ensure_loaded() try: return next( factory @@ -252,6 +252,7 @@ def get_registry(self, pool: AddressOrContract) -> EthAddress: """ Get registry containing a pool. """ + self.ensure_loaded() try: return next( registry @@ -269,6 +270,7 @@ def get_pool(self, token: AddressOrContract) -> EthAddress: """ Get Curve pool (swap) address by LP token address. Supports factory pools. """ + self.ensure_loaded() token = to_address(token) if token in self.token_to_pool: return self.token_to_pool[token] @@ -278,6 +280,7 @@ def get_gauge(self, pool: AddressOrContract, lp_token: AddressOrContract) -> Eth """ Get liquidity gauge address by pool or lp_token. """ + self.ensure_loaded() pool = to_address(pool) lp_token = to_address(lp_token) if chain.id == Network.Mainnet: @@ -299,7 +302,6 @@ def get_gauge(self, pool: AddressOrContract, lp_token: AddressOrContract) -> Eth if gauge != ZERO_ADDRESS: return gauge - @lru_cache(maxsize=None) def get_coins(self, pool: AddressOrContract) -> List[EthAddress]: """ @@ -319,182 +321,7 @@ def get_coins(self, pool: AddressOrContract) -> List[EthAddress]: return [coin for coin in coins if coin not in {None, ZERO_ADDRESS}] - @lru_cache(maxsize=None) - def get_underlying_coins(self, pool: AddressOrContract) -> List[EthAddress]: - pool = to_address(pool) - factory = self.get_factory(pool) - registry = self.get_registry(pool) - - if factory: - factory = contract(factory) - # new factory reverts for non-meta pools - if not hasattr(factory, 'is_meta') or factory.is_meta(pool): - if hasattr(factory, 'get_underlying_coins'): - coins = factory.get_underlying_coins(pool) - elif hasattr(factory, 'get_coins'): - coins = factory.get_coins(pool) - else: - coins = {ZERO_ADDRESS} - else: - coins = factory.get_coins(pool) - elif registry: - registry = contract(registry) - if hasattr(registry, 'get_underlying_coins'): - coins = registry.get_underlying_coins(pool) - elif hasattr(registry, 'get_coins'): - coins = registry.get_coins(pool) - - # pool not in registry, not checking for underlying_coins here - if set(coins) == {ZERO_ADDRESS}: - return self.get_coins(pool) - - return [coin for coin in coins if coin != ZERO_ADDRESS] - - @lru_cache(maxsize=None) - def get_decimals(self, pool: AddressOrContract) -> List[int]: - pool = to_address(pool) - factory = self.get_factory(pool) - registry = self.get_registry(pool) - source = contract(factory or registry) - decimals = source.get_decimals(pool) - - # pool not in registry - if not any(decimals): - coins = self.get_coins(pool) - decimals = fetch_multicall( - *[[contract(token), 'decimals'] for token in coins] - ) - - return [dec for dec in decimals if dec != 0] - - def get_balances(self, pool: AddressOrContract, block: Optional[Block] = None, should_raise_err: bool = True) -> Optional[Dict[EthAddress,float]]: - """ - Get {token: balance} of liquidity in the pool. - """ - pool = to_address(pool) - factory = self.get_factory(pool) - registry = self.get_registry(pool) - coins = self.get_coins(pool) - decimals = self.get_decimals(pool) - - try: - source = contract(factory or registry) - balances = source.get_balances(pool, block_identifier=block) - # fallback for historical queries - except ValueError as e: - if str(e) not in [ - 'execution reverted', - 'No data was returned - the call likely reverted' - ]: raise - - balances = fetch_multicall( - *[[contract(pool), 'balances', i] for i, _ in enumerate(coins)], - block=block - ) - - if not any(balances): - if should_raise_err: - raise ValueError(f'could not fetch balances {pool} at {block}') - return None - - return { - coin: balance / 10 ** dec - for coin, balance, dec in zip(coins, balances, decimals) - } - def get_virtual_price(self, pool: Address, block: Optional[Block] = None) -> Optional[float]: - pool = contract(pool) - try: - return pool.get_virtual_price(block_identifier=block) / 1e18 - except ValueError as e: - if str(e) == "execution reverted": - return None - raise - - def get_tvl(self, pool: AddressOrContract, block: Optional[Block] = None) -> float: - """ - Get total value in Curve pool. - """ - pool = to_address(pool) - balances = self.get_balances(pool, block=block) - - return sum( - amount * magic.get_price(coin, block=block) - for coin, amount in balances.items() - ) - - @ttl_cache(maxsize=None, ttl=600) - def get_price(self, token: AddressOrContract, block: Optional[Block] = None) -> Optional[float]: - token = to_address(token) - pool = self.get_pool(token) - # crypto pools can have different tokens, use slow method - try: - tvl = self.get_tvl(pool, block=block) - except ValueError: - tvl = 0 - supply = contract(token).totalSupply(block_identifier=block) / 1e18 - if supply == 0: - if tvl > 0: - raise ValueError('curve pool has balance but no supply') - return 0 - return tvl / supply - - def get_coin_price(self, token: AddressOrContract, block: Optional[Block] = None) -> Optional[float]: - - # Select the most appropriate pool - pools = self.coin_to_pools[token] - if not pools: - return - elif len(pools) == 1: - pool = pools[0] - else: - # We need to find the pool with the deepest liquidity - balances = [self.get_balances(pool, block, should_raise_err=False) for pool in pools] - deepest_pool, deepest_bal = None, 0 - for pool, pool_bals in zip(pools, balances): - if pool_bals is None: - continue - if isinstance(pool_bals, Exception): - if str(pool_bals).startswith("could not fetch balances"): - continue - raise pool_bals - for _token, bal in pool_bals.items(): - if _token == token and bal > deepest_bal: - deepest_pool = pool - deepest_bal = bal - pool = deepest_pool - - # Get the index for `token` - coins = self.get_coins(pool) - token_in_ix = [i for i, coin in enumerate(coins) if coin == token][0] - amount_in = 10 ** contract(str(token)).decimals() - if len(coins) == 2: - # this works for most typical metapools - token_out_ix = 0 if token_in_ix == 1 else 1 if token_in_ix == 0 else None - elif len(coins) == 3: - # We will just default to using token 0 until we have a reason to make this more flexible - token_out_ix = 0 if token_in_ix in [1, 2] else 1 if token_in_ix == 0 else None - else: - # TODO: handle this sitch if necessary - return None - - # Get the price for `token` using the selected pool. - try: - dy = contract(pool).get_dy(token_in_ix, token_out_ix, amount_in, block_identifier = block) - except: - return None - - if coins[token_out_ix] == EEE_ADDRESS: - token_out = EEE_ADDRESS - amount_out = dy / 10 ** 18 - else: - token_out = contract(coins[token_out_ix]) - amount_out = dy / 10 ** token_out.decimals() - try: - return amount_out * magic.get_price(token_out, block = block) - except PriceError: - return None - async def calculate_boost(self, gauge: Contract, addr: Address, block: Optional[Block] = None) -> Dict[str,float]: results = await fetch_multicall_async( [gauge, "balanceOf", addr], @@ -563,7 +390,7 @@ async def calculate_apy(self, gauge: Contract, lp_token: AddressOrContract, bloc block=block, ) crv_price, token_price, results = await asyncio.gather( - magic.get_price(self.crv, block=block, sync=False), + magic.get_price(constants.CRV, block=block, sync=False), magic.get_price(lp_token, block=block, sync=False), results ) diff --git a/yearn/prices/magic.py b/yearn/prices/magic.py index 749c25fa5..f2d5c25bc 100644 --- a/yearn/prices/magic.py +++ b/yearn/prices/magic.py @@ -9,7 +9,8 @@ from y.exceptions import PriceError from y.networks import Network -from yearn.prices import constants, curve +from yearn.constants import CRV +from yearn.prices import constants from yearn.prices.aave import aave from yearn.prices.balancer import balancer as bal from yearn.prices.band import band @@ -31,6 +32,7 @@ async def _get_price(token: AnyAddressType, block: Optional[Block]) -> Decimal: if chain.id == Network.Mainnet: # fixes circular import from yearn.special import Backscratcher + # no liquid market for yveCRV-DAO -> return CRV token price if token == Backscratcher().vault.address and block < 11786563: return Decimal(await _get_price("0xD533a949740bb3306d119CC777fa900bA034cd52", block)) @@ -122,8 +124,8 @@ def find_price( elif chain.id == Network.Mainnet: # no liquid market for yveCRV-DAO -> return CRV token price if token == Backscratcher().vault.address and block < 11786563: - if curve.curve and curve.curve.crv: - return get_price(curve.curve.crv, block=block) + if CRV: + return get_price(CRV, block=block) # no liquidity for curve pool (yvecrv-f) -> return 0 elif token == "0x7E46fd8a30869aa9ed55af031067Df666EfE87da" and block < 14987514: return 0 @@ -132,7 +134,6 @@ def find_price( return 0 markets = [ - curve.curve, compound, fixed_forex, generic_amm, @@ -158,10 +159,6 @@ def find_price( price, underlying = price logger.debug("peel %s %s", price, underlying) return price * get_price(underlying, block=block) - - if price is None and token in curve.curve.coin_to_pools: - logger.debug(f'Curve.get_coin_price -> {price}') - price = curve.curve.get_coin_price(token, block = block) if price is None and return_price_during_vault_downtime: for incident in INCIDENTS[token]: @@ -174,7 +171,7 @@ def find_price( raise PriceError(f'could not fetch price for {_describe_err(token, block)}') if price == 0: - logger.warn("Price is 0 for token %s at block %d", token, block) + logger.warning("Price is 0 for token %s at block %d", token, block) return price diff --git a/yearn/prices/uniswap/v3.py b/yearn/prices/uniswap/v3.py index 08aae9493..0f642da91 100644 --- a/yearn/prices/uniswap/v3.py +++ b/yearn/prices/uniswap/v3.py @@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union from brownie import Contract, chain, convert -from eth_abi.packed import encode_abi_packed +from eth_abi import encode from y.contracts import contract_creation_block from y.networks import Network @@ -75,7 +75,7 @@ def __contains__(self, asset: Any) -> bool: def encode_path(self, path: Path) -> bytes: types = [type for _, type in zip(path, cycle(['address', 'uint24']))] - return encode_abi_packed(types, path) + return encode(types, path) def undo_fees(self, path: Path) -> float: fees = [1 - fee / FEE_DENOMINATOR for fee in path if isinstance(fee, int)] diff --git a/yearn/special.py b/yearn/special.py index 447129a9b..c9b1d28d0 100644 --- a/yearn/special.py +++ b/yearn/special.py @@ -3,16 +3,18 @@ from time import time from typing import TYPE_CHECKING, Tuple +import dank_mids import eth_retry import requests -from brownie import chain -from y import Contract, magic +from y import ERC20, Contract, magic from y.contracts import contract_creation_block_async from y.exceptions import PriceError, yPriceMagicError +from yearn import constants from yearn.apy.common import (Apy, ApyBlocks, ApyError, ApyFees, ApyPoints, ApySamples) from yearn.common import Tvl +from yearn.prices.curve import curve from yearn.utils import Singleton if TYPE_CHECKING: @@ -47,7 +49,7 @@ async def apy(self, _: ApySamples) -> Apy: yvboost_eth_pool = [pool for pool in data if pool["identifier"] == "yvboost-eth"][0] apy = yvboost_eth_pool["apy"] / 100. points = ApyPoints(apy, apy, apy) - block = await dank_w3.eth.block_number + block = await dank_mids.eth.block_number inception_block = await contract_creation_block_async(str(self.vault)) blocks = ApyBlocks(block, block, block, inception_block) return Apy("yvecrv-jar", apy, apy, ApyFees(), points=points, blocks=blocks) @@ -69,10 +71,9 @@ def __init__(self): self.proxy = Contract("0xF147b8125d2ef93FB6965Db97D6746952a133934") async def _locked(self, block=None) -> Tuple[float,float]: - from yearn.prices.curve import curve crv_locked, crv_price = await asyncio.gather( curve.voting_escrow.balanceOf["address"].coroutine(self.proxy, block_identifier=block), - magic.get_price(curve.crv, block=block, sync=False), + magic.get_price(constants.CRV, block=block, sync=False), ) crv_locked /= 1e18 return crv_locked, crv_price @@ -135,12 +136,9 @@ async def tvl(self, block=None) -> Tvl: if not isinstance(e.exception, PriceError): raise e price = None - tvl = total_assets * price / 10 ** await self.vault.decimals.coroutine(block_identifier=block) if price else None + tvl = total_assets * price / await ERC20(self.vault, asynchronous=True).scale if price else None return Tvl(total_assets, price, tvl) - - - class Ygov(metaclass = Singleton): def __init__(self): self.name = "yGov" diff --git a/yearn/treasury/_setup.py b/yearn/treasury/_setup.py index 50a2c9c38..32195c62c 100644 --- a/yearn/treasury/_setup.py +++ b/yearn/treasury/_setup.py @@ -1,5 +1,6 @@ + +import eth_portfolio from brownie import chain -from eth_portfolio._shitcoins import SHITCOINS from y.networks import Network from y.prices.utils import ypriceapi @@ -30,6 +31,19 @@ "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", # Uni V3 NonfungiblePositionManager "0x3f6740b5898c5D3650ec6eAce9a649Ac791e44D7", # Uni V3 UniV3PairManager "0x3EF9181c9b96BAAafb3717A553E808Ccc72be37D", # MEMEPEPE + "0x0329b631464C43f4e8132df7B4ac29a2D89FFdC7", # YFI2.0 + "0x594DaaD7D77592a2b97b725A7AD59D7E188b5bFa", # APU + "0x05bAdF8A8e7fE5b43fae112108E26f2f663Bf1a2", # INUNOMICS + "0x927402ab67c0CDA3c187E9DFE34554AC581441f2", # SAITABIT + "0x875bf9be244970B8572DD042053508bF758371Ee", # fake SOLID + + # these arent shitcoins per se but we can't price them and dont expect to in the future, lets save cpu cycles + "0xD057B63f5E69CF1B929b356b579Cba08D7688048", # vCOW + "0x4c1317326fD8EFDeBdBE5e1cd052010D97723bd6", # deprecated yCRV + "0x8a0889d47f9Aa0Fac1cC718ba34E26b867437880", # deprecated st-yCRV + + # test token? + "0x372d5d02c6b4075bd58892f80300cA590e92d29E", # tOUSG }, Network.Arbitrum: { "0x89b0f9dB18FD079063cFA91F174B300C1ce0003C", # AIELON @@ -38,9 +52,7 @@ def customize_eth_portfolio() -> None: for token in skip_tokens.get(chain.id, []): - if chain.id not in SHITCOINS: - SHITCOINS[chain.id] = set() - SHITCOINS[chain.id].add(token) + eth_portfolio.SHITCOINS[chain.id].add(token) skip_ypriceapi = { diff --git a/yearn/treasury/accountant/__init__.py b/yearn/treasury/accountant/__init__.py index 6339fe665..84f27141e 100644 --- a/yearn/treasury/accountant/__init__.py +++ b/yearn/treasury/accountant/__init__.py @@ -1,13 +1,16 @@ +import logging + from brownie import Contract from brownie.exceptions import ContractNotFound from tqdm import tqdm -from y.networks import Network +from y import Contract, ContractNotVerified, Network from yearn.entities import Address from yearn.treasury.accountant.accountant import * from yearn.treasury.accountant.prepare_db import prepare_db -from yearn.utils import contract + +logger = logging.getLogger(__name__) __all__ = [ "all_txs", @@ -24,9 +27,9 @@ # Version 1.3.0 Network.Mainnet: "0xDaB5dc22350f9a6Aff03Cf3D9341aAD0ba42d2a6", Network.Fantom: "0xd9db270c1b5e3bd161e8c8503c55ceabee709552", -}.get(chain.id,None) +}.get(chain.id) -GNOSIS_ABI = contract(GNOSIS_IMPLEMENTATION).abi if GNOSIS_IMPLEMENTATION else None +GNOSIS_ABI = Contract(GNOSIS_IMPLEMENTATION).abi if GNOSIS_IMPLEMENTATION else None @db_session @@ -39,18 +42,13 @@ def __ensure_topics_are_known(addresses: List[Address]) -> None: no_topics = [] for address in tqdm(addresses): try: - if not contract(address.address).topics: - if not force_gnosis_safe_abi(address): - no_topics.append(address) + if not Contract(address.address).topics and not force_gnosis_safe_abi(address): + no_topics.append(address) except ContractNotFound: # This is MOST LIKELY unimportant and not Yearn related. - logger.debug(f"{address.address} self destructed") - except ValueError as e: - if str(e).startswith("Source for") and str(e).endswith("has not been verified"): - continue - if "Contract source code not verified" in str(e): - continue - raise + logger.debug("%s self destructed", address.address) + except ContractNotVerified: + logger.debug("%s is not verified", address.address) no_topics_with_nick = {address.nickname for address in no_topics if address.nickname} no_topics_no_nick = [address for address in no_topics if not address.nickname] @@ -69,9 +67,8 @@ def __ensure_signatures_are_known(addresses: List[Address]) -> None: no_sigs = [] for address in tqdm(addresses): try: - if not contract(address.address).signatures: - if not force_gnosis_safe_abi(address): - no_sigs.append(address) + if not Contract(address.address).signatures and not force_gnosis_safe_abi(address): + no_sigs.append(address) except ContractNotFound: # This is MOST LIKELY unimportant and not Yearn related. logger.debug("%s self destructed", address.address) diff --git a/yearn/treasury/accountant/classes.py b/yearn/treasury/accountant/classes.py index 81d782e74..e599e3504 100644 --- a/yearn/treasury/accountant/classes.py +++ b/yearn/treasury/accountant/classes.py @@ -1,8 +1,10 @@ import logging +from requests import HTTPError from typing import Any, Callable, Iterable, List, Optional, Tuple, Union import sentry_sdk +from pony.orm import TransactionError, TransactionIntegrityError from y import ContractNotVerified from yearn.entities import TreasuryTx, TxGroup @@ -18,8 +20,8 @@ def __init__(self, label: str) -> None: def sort(self, tx: TreasuryTx) -> Optional[TxGroup]: for child in self.children: - txgroup = child.sort(tx) - if txgroup: + if txgroup := child.sort(tx): + print(f'sorted {tx} to {self.label}') return txgroup def create_child(self, label: str, check: Optional[Callable] = None) -> "ChildTxGroup": @@ -56,11 +58,20 @@ def top(self): return self.parent.top def sort(self, tx: TreasuryTx) -> Optional[TxGroup]: + if not hasattr(self, 'check'): + return super().sort(tx) try: - if hasattr(self, 'check') and self.check(tx): + result = self.check(tx) + if not isinstance(result, bool): + raise TypeError(result, self, self.check) + if result: + print(f"sorted {tx} to {self.label}") return self.txgroup except ContractNotVerified: logger.info("ContractNotVerified when sorting %s with %s", tx, self.label) + except (AssertionError, AttributeError, TransactionError, HTTPError, NotImplementedError, ValueError, TypeError, NameError) as e: + logger.exception(e) + raise except Exception as e: logger.warning("%s when sorting %s with %s.", e.__repr__(), tx, self.label) sentry_sdk.capture_exception(e) diff --git a/yearn/treasury/accountant/cost_of_revenue/gas.py b/yearn/treasury/accountant/cost_of_revenue/gas.py index 7fb461495..2f128132f 100644 --- a/yearn/treasury/accountant/cost_of_revenue/gas.py +++ b/yearn/treasury/accountant/cost_of_revenue/gas.py @@ -6,70 +6,80 @@ from yearn.treasury.accountant.classes import Filter, HashMatcher +strategist_gas_hashes = { + Network.Mainnet: [ + '0x407d6ad61cbab79133ae53483bc2b2b12c2d0f4e1978558ed7c8073a3822caed', + '0x80f7fbef821811d80674a0c9a85c52f386b73fc21cb4efa987a53f422cdf8f08', + '0xd479074b20b9e48e403c84dd66da9f7aab1263c45c21da42b53cdfee03f2d523', + '0x0bd71c844a10cc1f12d547e83f6465cbdd2e8618eeab614781a18464b03168ad', + '0x3b30ead8fe9be3c7f5a32d1b5f3efd4a38c1410539db4c17aee49b725e9efd56', + '0x08499992f6f6bf5a11edce89af4259c941a1b257f79902fb4b8dca49fba5643d', + '0x10601dab5e982ca671135a283d91cef0388c16de3fbbb00c87f6226f5041f870', + '0x396117cd539920c38e6520dfe546bb07f9058ae82bb9db270af6fbec0505758e', + '0xe350027531bca82204118429b5f966a0596079d9d771ea166ad8f76ba1837334', + '0x70aa54bf69d8881fbecffe4d4c0107f25cbf460574a501f8deca008528c79b2e', + '0x8319cab95a1c74629a5dfc0eb5c6614311e65bdc7fa540e81212edbca57670a3', + '0x828808782814342418d24d284ee6338993120d9c4144b87b8f8199c078389221', + '0xbede300b44aca7a02f9b4738112c15089b5f81d6e34ee2c4e9d3348a8fecf73e', + '0x1bd363b2af4a7508d797bb6c9cc66da34d02aeb52da4f3e90c9e3ce9762360f7', + '0x43e48d3f6cacf6a27c4b382444ae1df09a33472ef46c3740cbf54ae48b1688e0', + '0xed5a29b64df4b6faba6c1c7bc4480eae9c537c4bdcad48516cfd78b1cf458b06', + '0x1157e684a387153c6d4fce716dda0d707c41474f93317a7c170975e576773bcf', + '0x570d7b194c4d47f2c7d2faba57847523e33f72d8361b9ff781d16c0898861e42', + '0x202d249537ca47864d29f6bca500c9161b77711e681fcb72b86ea2756f259dbf', + '0x446bb882cf7769cdddd1481deef432a0bf74e53e7db7653f098536e4a3d81f59', + '0xd6f577adaac039e72d3737b5aeeaca6d434d5140487fd2f414ef9e66589fefad', + '0x25ded1c0ed0af0ca386709fcb808ebe5bbf0eb675b4c136b47fb51c1adfb2650', + '0xa2d63dd5c8f80c0796ed6e0d3a06a60b85beced6273c2e8cc7816ca7a9514eae', + '0xe966e3a8bbc99f29c78e2c461f6c1e7ce1b64fa092bee364a27b1ad437507648', + '0xc9c4b4103751bef92a23e1d4671e392021a6464d61153473dcc9a14d74504e7e', + '0xc3bb4642c21a810a22d0bd4e17f12f920031a7bb960ab48faee3e7c549048d90', + '0xc76d45b2c4a94bbf45548732f61f4c656ae5a49a580bd2913ff16b04850a65ad', + '0x09561098e79641a2e3c0f118993eed0febfea9858138ddb3e45480de164f2210', + '0x46ea28a06892eeada4ae351f0776250bc4658299375cb35a9cba24b9807fbcb9', + '0xb5a946ee557b646580ff319c2e073f8d2a99e1cdc636362d62c4af5c104cf0f7', + '0x0dacd763125214bc7e38ab420522eabdc018a34e78fbf117346f21a2422cc662', + '0x3835de76c872b61973c4d957b6a06d222fc5c9766646f3e15693788d738d65ab', + '0xdcd38bd068e15f6b25de6602409206a9a87658bbbe4b6e1206aec47d9b907d82', + '0xf5e65bd0baf2c48280d4779629b6c6755c5f63ed38e1af6362e005a20ea5ce63', + '0xf2c05c9e9acd7c59c4b884592bc2a8e8a04bd97d23c5afe056b9a7fe063e3086', + '0xd786627bdc8ccfc1b103b4fada5176b6d3144b55874cfcb46c2a2e4941be2558', + '0x181a4b264bc193186097d195f1096b87b482d3cbcc0baab016ce910bd9745cc0', + '0xedbebe235ef62e126f2dc701badb77f3557a6f32a0fa362e2431715db71ddf4e', + '0x6afdc3367e28b1388f875fc5ddcc3536d983dfda4897c0f6b206a8b460205e2e', + '0xbc158f9efed2f1d9b1baa13a150a8eb734b9652b4fda842da4f3d2a2f5781f0c', + '0x67e11775ca149fd2f8d865a142237786f04ee75c18ad6ba7675c0ab2d33ac9cb', + '0xed1852fd4685eb81f756c7ff9dfa0b97a4372c3f71dfff95123d16b2a80bece3', + '0x8414887aa252dd1c9834189c502a8679452cba314f7ca1a8a817419031625e15', + '0x3c0799c6b0ca62ad3d868359eb00f95bfd54349c4f7422f1ab186f0e18ed3a52', + '0xa78a7efe4200c22ea20419f606f2cb280e2d4f7a32e92c0c7b8fbe02a96b3f75', + '0x6a9c40b8d78d9e09849a48be204f2c3072144c75cf6ca75cd39e3d78d2f4c352', + "0x6e32e36b13bce4c4838fc083516f1e780e303b55a26a45c0e79acf0c17e2b05f", + "0xd700344511719054e95d260f5494266cdd950825bf577160cf5acb02d87f5a63", + "0xb8c71e4491a692c8d293f13e37bf03aa8487ad5306f3db8fc4e83c406f8c0746", + "0x96be538314a6547063a5b81ded9bda38a067528d4bcfc558eee976a684e5b44a", + ["0xebfff9a2fd6103d73f417c675db2dc43742bdb6f496f04d3cfd1938046001d70", Filter("_symbol", "ETH")], + "0x269bcda1327da47fc0be53e044540f199ffb4c3f15146e0cb61348093b43b66e", + # NOTE: don't think we need these filters due to the if check above. + # TODO: refactor out + ["0x96728585c7b1720f2e94a3a2ff339ed6433bd6687cd680dd2534e6837271111a", Filter('_from_nickname', "Disperse.app")], + ["0xecbc1474610b31046124aa6323863f47d6e348385056ab80c3cc1f6b963f5d68", Filter('_from_nickname', "Disperse.app")], + ["0x5fb3320fdc41aba54743559cd4248e5cdfd8ffc67bd329c13cd3b66ce4976144", Filter('_from_nickname', "Disperse.app")], + ["0x7afceac28536b9b2c177302c3cfcba449e408b47ff2f0a8a3c4b0e668a4d5d4e", Filter('_from_nickname', "Disperse.app")], + ["0xb8bb3728fdfb49d7c86c08dba8e3586e3761f13d2c88fa6fab80227b6a3f4519", Filter('_from_nickname', "Disperse.app")], + ["0x76635bffdf8ee07736bf6953611cd93b5f6f10ef9a59f2504661047e834ea4d6", Filter('_from_nickname', "Disperse.app")], + "0x76cec8d40341ed064b636530a6ccfdb9fcc0855f6716b592d286e579d6eab433", + "0x9c9c342186bfc7232b28a11d3ecea32e6cb417b3c7b41e67e1f5c06ff32c0d9c", + "0x038aeb3351b762bc92c5e4274c01520ae08dc314e2282ececc2a19a033d994a8", + "0x49a930eeb2a709e162d05d29a5d19b6f71aa5fb63a369395ca869e1961a3bb27", + "0x6169de8578935f15e24b1dc0e22c5670c5240505425ab4d71283026d96538381", + "", + ], +}.get(chain.id, []) + def is_strategist_gas(tx: TreasuryTx) -> bool: if tx._from_nickname == "Disperse.app": - return tx in HashMatcher({ - Network.Mainnet: [ - '0x407d6ad61cbab79133ae53483bc2b2b12c2d0f4e1978558ed7c8073a3822caed', - '0x80f7fbef821811d80674a0c9a85c52f386b73fc21cb4efa987a53f422cdf8f08', - '0xd479074b20b9e48e403c84dd66da9f7aab1263c45c21da42b53cdfee03f2d523', - '0x0bd71c844a10cc1f12d547e83f6465cbdd2e8618eeab614781a18464b03168ad', - '0x3b30ead8fe9be3c7f5a32d1b5f3efd4a38c1410539db4c17aee49b725e9efd56', - '0x08499992f6f6bf5a11edce89af4259c941a1b257f79902fb4b8dca49fba5643d', - '0x10601dab5e982ca671135a283d91cef0388c16de3fbbb00c87f6226f5041f870', - '0x396117cd539920c38e6520dfe546bb07f9058ae82bb9db270af6fbec0505758e', - '0xe350027531bca82204118429b5f966a0596079d9d771ea166ad8f76ba1837334', - '0x70aa54bf69d8881fbecffe4d4c0107f25cbf460574a501f8deca008528c79b2e', - '0x8319cab95a1c74629a5dfc0eb5c6614311e65bdc7fa540e81212edbca57670a3', - '0x828808782814342418d24d284ee6338993120d9c4144b87b8f8199c078389221', - '0xbede300b44aca7a02f9b4738112c15089b5f81d6e34ee2c4e9d3348a8fecf73e', - '0x1bd363b2af4a7508d797bb6c9cc66da34d02aeb52da4f3e90c9e3ce9762360f7', - '0x43e48d3f6cacf6a27c4b382444ae1df09a33472ef46c3740cbf54ae48b1688e0', - '0xed5a29b64df4b6faba6c1c7bc4480eae9c537c4bdcad48516cfd78b1cf458b06', - '0x1157e684a387153c6d4fce716dda0d707c41474f93317a7c170975e576773bcf', - '0x570d7b194c4d47f2c7d2faba57847523e33f72d8361b9ff781d16c0898861e42', - '0x202d249537ca47864d29f6bca500c9161b77711e681fcb72b86ea2756f259dbf', - '0x446bb882cf7769cdddd1481deef432a0bf74e53e7db7653f098536e4a3d81f59', - '0xd6f577adaac039e72d3737b5aeeaca6d434d5140487fd2f414ef9e66589fefad', - '0x25ded1c0ed0af0ca386709fcb808ebe5bbf0eb675b4c136b47fb51c1adfb2650', - '0xa2d63dd5c8f80c0796ed6e0d3a06a60b85beced6273c2e8cc7816ca7a9514eae', - '0xe966e3a8bbc99f29c78e2c461f6c1e7ce1b64fa092bee364a27b1ad437507648', - '0xc9c4b4103751bef92a23e1d4671e392021a6464d61153473dcc9a14d74504e7e', - '0xc3bb4642c21a810a22d0bd4e17f12f920031a7bb960ab48faee3e7c549048d90', - '0xc76d45b2c4a94bbf45548732f61f4c656ae5a49a580bd2913ff16b04850a65ad', - '0x09561098e79641a2e3c0f118993eed0febfea9858138ddb3e45480de164f2210', - '0x46ea28a06892eeada4ae351f0776250bc4658299375cb35a9cba24b9807fbcb9', - '0xb5a946ee557b646580ff319c2e073f8d2a99e1cdc636362d62c4af5c104cf0f7', - '0x0dacd763125214bc7e38ab420522eabdc018a34e78fbf117346f21a2422cc662', - '0x3835de76c872b61973c4d957b6a06d222fc5c9766646f3e15693788d738d65ab', - '0xdcd38bd068e15f6b25de6602409206a9a87658bbbe4b6e1206aec47d9b907d82', - '0xf5e65bd0baf2c48280d4779629b6c6755c5f63ed38e1af6362e005a20ea5ce63', - '0xf2c05c9e9acd7c59c4b884592bc2a8e8a04bd97d23c5afe056b9a7fe063e3086', - '0xd786627bdc8ccfc1b103b4fada5176b6d3144b55874cfcb46c2a2e4941be2558', - '0x181a4b264bc193186097d195f1096b87b482d3cbcc0baab016ce910bd9745cc0', - '0xedbebe235ef62e126f2dc701badb77f3557a6f32a0fa362e2431715db71ddf4e', - '0x6afdc3367e28b1388f875fc5ddcc3536d983dfda4897c0f6b206a8b460205e2e', - '0xbc158f9efed2f1d9b1baa13a150a8eb734b9652b4fda842da4f3d2a2f5781f0c', - '0x67e11775ca149fd2f8d865a142237786f04ee75c18ad6ba7675c0ab2d33ac9cb', - '0xed1852fd4685eb81f756c7ff9dfa0b97a4372c3f71dfff95123d16b2a80bece3', - '0x8414887aa252dd1c9834189c502a8679452cba314f7ca1a8a817419031625e15', - '0x3c0799c6b0ca62ad3d868359eb00f95bfd54349c4f7422f1ab186f0e18ed3a52', - '0xa78a7efe4200c22ea20419f606f2cb280e2d4f7a32e92c0c7b8fbe02a96b3f75', - '0x6a9c40b8d78d9e09849a48be204f2c3072144c75cf6ca75cd39e3d78d2f4c352', - "0x6e32e36b13bce4c4838fc083516f1e780e303b55a26a45c0e79acf0c17e2b05f", - "0xd700344511719054e95d260f5494266cdd950825bf577160cf5acb02d87f5a63", - "0xb8c71e4491a692c8d293f13e37bf03aa8487ad5306f3db8fc4e83c406f8c0746", - "0x96be538314a6547063a5b81ded9bda38a067528d4bcfc558eee976a684e5b44a", - ["0xebfff9a2fd6103d73f417c675db2dc43742bdb6f496f04d3cfd1938046001d70", Filter("_symbol", "ETH")], - "0x269bcda1327da47fc0be53e044540f199ffb4c3f15146e0cb61348093b43b66e", - ["0x96728585c7b1720f2e94a3a2ff339ed6433bd6687cd680dd2534e6837271111a", Filter('_from_nickname', "Disperse.app")], - ["0xecbc1474610b31046124aa6323863f47d6e348385056ab80c3cc1f6b963f5d68", Filter('_from_nickname', "Disperse.app")], - ["0x5fb3320fdc41aba54743559cd4248e5cdfd8ffc67bd329c13cd3b66ce4976144", Filter('_from_nickname', "Disperse.app")], - ["0x7afceac28536b9b2c177302c3cfcba449e408b47ff2f0a8a3c4b0e668a4d5d4e", Filter('_from_nickname', "Disperse.app")], - ["0xb8bb3728fdfb49d7c86c08dba8e3586e3761f13d2c88fa6fab80227b6a3f4519", Filter('_from_nickname', "Disperse.app")], - ["0x76635bffdf8ee07736bf6953611cd93b5f6f10ef9a59f2504661047e834ea4d6", Filter('_from_nickname', "Disperse.app")], - ], - }.get(chain.id, [])) + return tx in HashMatcher(strategist_gas_hashes) # Returned gas if tx in HashMatcher({ @@ -79,6 +89,7 @@ def is_strategist_gas(tx: TreasuryTx) -> bool: '0xac2253f1d8f78680411b353d65135d58bc880cdf9507ea7848daf05925e1443f', '0xd27d4a732dd1a9ac93c7db1695a6d2aff40e007627d710da91f328b246be44bc', '0x5a828e5bde96cd8745223fe32daefaa9140a09acc69202c33f6f789228c8134b', + '0x110ef82ec16eb53bf71b073aca4a37d4fbfaa74166c687a726211392a02f0059', ], }.get(chain.id, [])): tx.amount *= -1 @@ -89,21 +100,20 @@ def is_strategist_gas(tx: TreasuryTx) -> bool: return tx in HashMatcher({ Network.Mainnet: [ "0x420cfbc7856f64e8949d4dd6d4ce9570f8270def1380ebf381376fbcd0b0d5bf", + "0xc7bca93dc6bfa37e45bebb030a2ce1198fc402176ba0b3a5553a0580299df5e7", ], }.get(chain.id, [])) def is_multisig_reimbursement(tx: TreasuryTx) -> bool: - if tx._symbol == "ETH": - return tx in HashMatcher([ - ["0x19bcb28cd113896fb06f17b2e5efa86bb8bf78c26e75c633d8f1a0e48b238a86", Filter('_from_nickname', 'Yearn yChad Multisig')] - ]) + return tx._symbol == "ETH" and tx in HashMatcher([ + ["0x19bcb28cd113896fb06f17b2e5efa86bb8bf78c26e75c633d8f1a0e48b238a86", Filter('_from_nickname', 'Yearn yChad Multisig')] + ]) def is_other_gas(tx: TreasuryTx) -> bool: - if tx._symbol == "ETH": - return tx in HashMatcher([ - # Reimbursement for testing - ["0x57bc99f6007989606bdd9d1adf91c99d198de51f61d29689ee13ccf440b244df", Filter('to_address.address', '0xB1d693B77232D88a3C9467eD5619FfE79E80BCCc')] - ]) + return tx._symbol == "ETH" and tx in HashMatcher([ + # Reimbursement for testing + ["0x57bc99f6007989606bdd9d1adf91c99d198de51f61d29689ee13ccf440b244df", Filter('to_address', '0xB1d693B77232D88a3C9467eD5619FfE79E80BCCc')] + ]) def is_yearn_harvest(tx: TreasuryTx) -> bool: # NOTE define hueristics for this if it occurs freuently diff --git a/yearn/treasury/accountant/cost_of_revenue/general.py b/yearn/treasury/accountant/cost_of_revenue/general.py index a55b63951..d2fcff7ec 100644 --- a/yearn/treasury/accountant/cost_of_revenue/general.py +++ b/yearn/treasury/accountant/cost_of_revenue/general.py @@ -1,9 +1,12 @@ import asyncio import logging +from functools import lru_cache +from typing import Tuple -from brownie import chain, convert +from brownie import chain from y import Network +from y.datatypes import Address from yearn import constants from yearn.entities import TreasuryTx @@ -27,7 +30,7 @@ 'ymechs': [ '0x1ab9ff3228cf25bf2a7c1eac596e836069f8c0adc46acd46d948eb77743fbb96', '0xe2a6bec23d0c73b35e969bc949072f8c1768767b06d57e5602b2b95eddf41a66', - ["0xeed864c87f01996ead5a8315cccd0b3f22f384ef3b4e272e4751065f909b4d3d", Filter('to_address.address', "0x966Fa7ACF1b6c732458e4d3264FD2393aec840bA")] + ["0xeed864c87f01996ead5a8315cccd0b3f22f384ef3b4e272e4751065f909b4d3d", Filter('to_address', "0x966Fa7ACF1b6c732458e4d3264FD2393aec840bA")] ], 'ykeeper': [ '0x1ab9ff3228cf25bf2a7c1eac596e836069f8c0adc46acd46d948eb77743fbb96', @@ -37,25 +40,30 @@ } }.get(chain.id, {}) + +@lru_cache(maxsize=None) def _get_flat_wrappers(partner: Partner): + loop = asyncio.get_event_loop() # A helper function so we can run this sync without either breaking the event loop in our main thread or making this module async - return asyncio.get_event_loop().run_until_complete(partner.flat_wrappers) + wrappers = [] if loop.is_running() else loop.run_until_complete(partner.flat_wrappers) + logger.info("loaded %s wrappers for %s", len(wrappers), partner) + return wrappers + +@lru_cache(maxsize=None) +def _relevant_partners(to_address: Address) -> Tuple[Partner]: + return tuple(partner for partner in partners if to_address in [partner.treasury, *partner.retired_treasuries]) def is_partner_fees(tx: TreasuryTx) -> bool: - if tx.from_address.address == constants.YCHAD_MULTISIG and tx.to_address: - for partner in partners: - if not ( - tx.to_address.address == convert.to_address(partner.treasury) or - (hasattr(partner, 'retired_treasuries') and tx.to_address.address in partner.retired_treasuries) - ): - continue - if any(tx.token.address.address == convert.to_address(wrapper.vault) for wrapper in _get_flat_wrappers(partner)): # gotta somehow async this without asyncing this - return True - else: - logger.warn(f'look at {tx}, seems odd') - + if tx.from_address == constants.YCHAD_MULTISIG and tx.to_address is not None: + for partner in _relevant_partners(tx.to_address.address): + # gotta somehow async this without asyncing this + if wrappers := _get_flat_wrappers(partner): + if any(tx.token == wrapper.vault for wrapper in wrappers): + return True + logger.warning('look at %s, seems odd', tx) + # DEV figure out why these weren't captured by the above - hashes = { + return tx in HashMatcher({ Network.Mainnet: [ # Thought we automated these... why aren't they sorting successfully? ["0x590b0cc67ba42dbc046b8cbfe2d314fbe8da82f11649ef21cdacc61bc9752d83", IterFilter('log_index',[275,276,278])], @@ -65,8 +73,4 @@ def is_partner_fees(tx: TreasuryTx) -> bool: ["0x9681276a8668f5870551908fc17be3553c82cf6a9fedbd2fdb43f1c05385dca1", Filter('log_index', 173)], ["0xa12c99e2f4e5ffec9d280528968d615ab3d58483b37e8b021865163655892ea0", IterFilter('log_index', [223, 228])] ], - }.get(chain.id, []) - - if tx in HashMatcher(hashes): - return True - return False + }.get(chain.id, [])) diff --git a/yearn/treasury/accountant/expenses/__init__.py b/yearn/treasury/accountant/expenses/__init__.py index b2be3ac29..1393166e6 100644 --- a/yearn/treasury/accountant/expenses/__init__.py +++ b/yearn/treasury/accountant/expenses/__init__.py @@ -72,7 +72,7 @@ grants.create_child("Worms", people.is_worms) grants.create_child("ySecurity 2 [BR#xxx]", people.is_ysecurity_2) grants.create_child("yBudget [BR#xxx]", people.is_ybudget) - grants.create_child("ySupport [BR#xxx]", people.is_ysupport) + #grants.create_child("ySupport [BR#xxx]", people.is_ysupport) grants.create_child("Rantom [BR#xxx]", people.is_rantom) grants.create_child("Dinobots [BR#xxx]", people.is_dinobots) grants.create_child("TxCreator [BR#xxx]", people.is_tx_creator) diff --git a/yearn/treasury/accountant/expenses/general.py b/yearn/treasury/accountant/expenses/general.py index 17b623539..8df1e2d9e 100644 --- a/yearn/treasury/accountant/expenses/general.py +++ b/yearn/treasury/accountant/expenses/general.py @@ -1,4 +1,6 @@ +from decimal import Decimal + from brownie import chain from y.networks import Network @@ -45,5 +47,4 @@ def is_travel_reimbursement(tx: TreasuryTx) -> bool: return tx in HashMatcher(hashes) def is_sms_discretionary_budget(tx: TreasuryTx) -> bool: - if tx.from_address.address in treasury.addresses and tx._to_nickname == "Yearn Strategist Multisig" and tx._symbol == "DAI" and tx.amount == 200_000.0: - return True + return tx.from_address.address in treasury.addresses and tx._to_nickname == "Yearn Strategist Multisig" and tx._symbol == "DAI" and tx.amount == Decimal(200_000) diff --git a/yearn/treasury/accountant/expenses/infrastructure.py b/yearn/treasury/accountant/expenses/infrastructure.py index 584acb396..73c432fea 100644 --- a/yearn/treasury/accountant/expenses/infrastructure.py +++ b/yearn/treasury/accountant/expenses/infrastructure.py @@ -11,14 +11,10 @@ def is_servers(tx: TreasuryTx) -> bool: ]) def is_tenderly(tx: TreasuryTx) -> bool: - if tx._symbol == "USDT" and tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address in ["0xF6060cE3fC3df2640F72E42441355f50F195D96a"]: - return True - return False + return tx._symbol == "USDT" and tx.from_address.address in treasury.addresses and tx.to_address in ["0xF6060cE3fC3df2640F72E42441355f50F195D96a"] def is_wonderland(tx: TreasuryTx) -> bool: - if tx._symbol == "DAI" and tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == '0x8bA72884984f669aBBc9a5a7b441AD8E3D9a4fD3': - return True - return False + return tx._symbol == "DAI" and tx.from_address.address in treasury.addresses and tx.to_address == '0x8bA72884984f669aBBc9a5a7b441AD8E3D9a4fD3' def is_generic(tx: TreasuryTx) -> bool: hashes = [ @@ -38,5 +34,7 @@ def is_generic(tx: TreasuryTx) -> bool: "0xb9b5eef15987d97448d6cae146231c47910ba937db7dc30cfb253d64cbc5515d", ["0xfc07ee04d44f8e481f58339b7b8c998d454e4ec427b8021c4e453c8eeee6a9b9", Filter('log_index', 207)], "0xf9ddee2db73fdf322caa10da88a178aca8308d79c2ad83324716c22878600e1c", + "0xaf303a94ca3949c44855dfe90848bef142f8afdaa7a524f51593c827c2d2d733", + ["0xc269f6fb016a48fe150f689231a73532b631877d1376608df639dad79514904b", Filter('_symbol', 'DAI')], ] return tx in HashMatcher(hashes) diff --git a/yearn/treasury/accountant/expenses/people.py b/yearn/treasury/accountant/expenses/people.py index 386650c13..06086b016 100644 --- a/yearn/treasury/accountant/expenses/people.py +++ b/yearn/treasury/accountant/expenses/people.py @@ -1,25 +1,41 @@ -from typing import List, Optional +from contextlib import suppress +from decimal import Decimal +from typing import Iterator, List, Optional from brownie import convert from msgspec import UNSET, Struct from pony.orm import commit +from y import Contract from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import Filter, HashMatcher, IterFilter +# TODO: add rejected BRs +_budget_requests = {} + class BudgetRequest(Struct): number: int = 0 dai: Optional[float] = UNSET yfi: Optional[float] = UNSET - # TODO: maybe refactor this somewhere else? its only here for non-comp usdc: Optional[float] = UNSET def __post_init__(self) -> None: if self.number: _budget_requests[self.number] = self + def __contains__(self, symbol: str) -> bool: + """Returns True if there is a portion of comp denominated in token `symbol` for this BR, False otherwise.""" + return hasattr(self, symbol.lower()) and getattr(self, symbol.lower()) != UNSET + + def __getitem__(self, symbol: str) -> float: + with suppress(AttributeError): + retval = getattr(self, symbol.lower()) + if retval != UNSET: + return retval + raise KeyError(symbol) + @property def url(self) -> str: if not self.number: @@ -27,10 +43,14 @@ def url(self) -> str: return f"https://github.com/yearn/budget/issues/{self.number}" +TransactionHash = str + class yTeam(Struct): label: str address: str brs: List[BudgetRequest] = [] + refunds: List[TransactionHash] = [] + """If a team sent funds back for any reason put the hash here""" def __post_init__(self) -> None: self.address = convert.to_address(self.address) @@ -39,14 +59,87 @@ def __post_init__(self) -> None: def txgroup_name(self) -> str: # TODO: BR numbers return f"{self.label} [BR#xxx]" + + def brs_for_token(self, symbol: str) -> Iterator[BudgetRequest]: + for br in self.brs: + if symbol in br: + yield br def is_team_comp(self, tx: TreasuryTx) -> bool: - token = tx._symbol.lower() - brs_for_token = [br for br in self.brs if hasattr(br, token)] - if not brs_for_token: + if tx.to_address == self.address: + # TODO: build hueristics for this if it keeps happening + if tx.hash == "0xa113223ee1cb2165b4cc01ff0cea0b98b94e3f93eec57b117ecfbac5eea47916": + return True + elif (brs_for_token := list(self.brs_for_token(tx._symbol))): + return any(float(tx.amount) == br[tx._symbol] for br in brs_for_token) + elif self.refunds and tx.from_address == self.address and tx in HashMatcher(self.refunds): + if tx.amount > 0: + tx.amount *= -1 + if tx.value_usd > 0: + tx.value_usd *= -1 + return True + return self.is_team_vest(tx) + + def is_team_vest(self, tx: TreasuryTx) -> bool: + "checks for vesting contracts deployed via 0x200C92Dd85730872Ab6A1e7d5E40A067066257cF" + + if "VestingEscrowCreated" not in tx._events: return False - amount = float(tx.amount) - return any(amount == getattr(br, token) for br in brs_for_token) and tx.to_address.address == self.address + + for vest in tx._events["VestingEscrowCreated"]: + if vest.address == "0x200C92Dd85730872Ab6A1e7d5E40A067066257cF": + funder, token, recipient, escrow, amount, *_ = vest.values() + if not ( + tx.from_address == funder + and recipient == self.address + and tx.to_address == escrow + and tx.token == token + ): + continue + elif vest.address == "0x850De8D7d65A7b7D5bc825ba29543f41B8E8aFd2": + # YFI Liquid Locker Vesting Escrow Factory + token = "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e" # YFI + funder, recipient, index, amount, *_ = vest.values() + print(f"funder: {funder}") + print(f"recipient: {recipient}") + print(f"amount: {amount}") + if not ( + tx.from_address == funder + and recipient == self.address + and tx.token == token + ): + continue + else: + continue + + token = Contract(token) + print(f"token: {token}") + if tx.amount != amount / Decimal(10 ** token.decimals()): + print('no match') + continue + + # token sent raw + if any(float(tx.amount) == br[tx._symbol] for br in self.brs_for_token(tx._symbol)): + return True + + # token sent in vault form + elif hasattr(token, 'asset'): # v3 + underlying = Contract(token.asset()) + underlying_amount = token.convertToAssets(amount, block_identifier=tx.block) / Decimal(10 ** underlying.decimals()) + elif hasattr(token, 'token'): # v2 + underlying = Contract(token.token()) + underlying_amount = amount * token.pricePerShare(block_identifier=tx.block) / Decimal(10 ** underlying.decimals()) + else: + return False + #raise NotImplementedError(token, tx, tx.__dict__, [b for b in self.brs_for_token(tx._symbol)], tx.amount, float(tx.amount)) + + symbol = underlying.symbol() + underlying_amount = float(underlying_amount) + for br in self.brs_for_token(symbol): + # TODO: should we debug this rounding? I think its fine + if underlying_amount * 0.999 <= br[symbol] <= underlying_amount * 1.001: + return True + return False # TODO: add BR numbers @@ -63,14 +156,47 @@ def is_team_comp(self, tx: TreasuryTx) -> bool: BudgetRequest(usdc=65_000), ] ), + yTeam("V3 Development", "0x33333333D5eFb92f19a5F94a43456b3cec2797AE", brs=[BudgetRequest(200, dai=120_000), BudgetRequest(222, dai=105_000)], refunds=["0xf0410c1eaf2048a9d5472151cf733be41ba9c995d0825bb5d1629f9062a16f85"]), yTeam("S2 Team", "0x00520049162aa47AdA264E2f77DA6749dfaa6218", brs=[BudgetRequest(dai=39_500, yfi=2.25)]), yTeam("yCreative", "0x402449F51afbFC864D44133A975980179C6cD24C", brs=[BudgetRequest(dai=15_500, yfi=1.65), BudgetRequest(dai=46_500, yfi=4.95)]), yTeam("Corn", "0xF6411852b105042bb8bbc6Dd50C0e8F30Af63337", brs=[BudgetRequest(dai=10_000, yfi=1.5), BudgetRequest(dai=30_000, yfi=4.5)]), + # NOTE: can I do this double? + yTeam("Corn", "0x8973B848775a87a0D5bcf262C555859b87E6F7dA", brs=[BudgetRequest(199, usdc=60_000), BudgetRequest(221, dai=60_000)]), yTeam("ySecurity", "0x4851C7C7163bdF04A22C9e12Ab77e184a5dB8F0E", brs=[BudgetRequest(dai=20_667, yfi=2.5)]), - yTeam("Zootroop", "0xBd5CA40C66226F53378AE06bc71784CAd6016087", brs=[BudgetRequest(dai=34_500, yfi=1.5), BudgetRequest(dai=36_500, yfi=2)]), + yTeam("Zootroop", "0xBd5CA40C66226F53378AE06bc71784CAd6016087", brs=[BudgetRequest(dai=34_500, yfi=1.5), BudgetRequest(dai=36_500, yfi=2), BudgetRequest(202, dai=172_500), BudgetRequest(224, dai=162_000)]), yTeam("yETH", "0xeEEEEeeeEe274C3CCe13f77C85d8eBd9F7fd4479", brs=[BudgetRequest(dai=20_000, yfi=4.5)]), - yTeam("yLockers", "0xAAAA00079535300dDba84A89eb92434250f83ea7", brs=[BudgetRequest(dai=20_600, yfi=2), BudgetRequest(dai=60_500, yfi=6)]), + yTeam("yLockers", "0xAAAA00079535300dDba84A89eb92434250f83ea7", brs=[BudgetRequest(dai=20_600, yfi=2), BudgetRequest(dai=60_500, yfi=6)], refunds=["0xa9613960b6c657b3ebc67798b5bb4b3b51c5d4b3bbbde2eb8f6a61a9ab4657c4"]), + yTeam("yLockers", "0x4444AAAACDBa5580282365e25b16309Bd770ce4a", brs=[BudgetRequest(195, dai=35_000), BudgetRequest(205, dai=35_000), BudgetRequest(206, dai=90_840), BudgetRequest(226, dai=120_840)]), yTeam("yDiscount", "0x54991866A907891c9B85478CC1Fb0560B17D2b1D", brs=[BudgetRequest(yfi=1)]), + yTeam("Dudesahn", "0x4444AAAACDBa5580282365e25b16309Bd770ce4a", brs=[BudgetRequest(179, dai=15_000), BudgetRequest(196, dai=15_000)]), + # NOTE: not sure if this works with the existing ySupport matcher... lets see... + # NOTE: it does not. do some refactoring here + yTeam( + "ySupport", + "0xbd7B3Bc2C4581Fd173362c830AE45fB9506B3dA5", + brs=[ + BudgetRequest(yfi=1.14), + BudgetRequest(194, dai=18_375), + BudgetRequest(212, dai=24_000), + # This was 1 br in 2 pmts + BudgetRequest(dai=2_000), + BudgetRequest(dai=4_000), + # These were actually the same BR but one payment got rounded + BudgetRequest(yfi=0.92307692), + BudgetRequest(yfi=0.923077), + ], + ), + yTeam("1Up", "0x572b0675b0A815d1970C1310fE4AA8884FEaaaCc", brs=[BudgetRequest(yfi=42)]), + yTeam("Korin", "0x66bDEfA7Abf210d1240C9EC00000AafcFc80a235", brs=[BudgetRequest(yfi=40)]), + yTeam("Schlag", "0x88a3354e5e7A34A7901e1b64557992E85Aa1B5eb", brs=[BudgetRequest(yfi=40)]), + yTeam("DevDocs", "0x88c868B1024ECAefDc648eb152e91C57DeA984d0", brs=[BudgetRequest(204, dai=7_500, yfi=0.3), BudgetRequest(223, dai=21_900, yfi=1.2)]), + yTeam("Tapir", "0x80c9aC867b2D36B7e8D74646E074c460a008C0cb", brs=[BudgetRequest(217, dai=36_000)]), + yTeam("yRoboTreasury", "0xABCDEF0028B6Cc3539C2397aCab017519f8744c8", brs=[BudgetRequest(215, 30_000)]), + yTeam("yReporting", "0x28eD70032Adc7575d45A0869CfDcCEcdE88C1a74", brs=[BudgetRequest(dai=63_000)]), + yTeam("CatHerder", "0x63E02F93622541CfE41aFedCF96a114DB71Ba4EE", brs=[BudgetRequest(213, dai=30_000)]), + yTeam("MOM", "0x789330A9F15bbC61B524714528C1cE72237dC731", brs=[BudgetRequest(225, dai=365_250)]), + yTeam("SAM", "0xe5e2Baf96198c56380dDD5E992D7d1ADa0e989c0", brs=[BudgetRequest(218, dai=78_000)]), + yTeam("veYFI", "0x555555550955A916D9bF6DbCeA0e874cDfE77c70", brs=[BudgetRequest(220, dai=10_000)]) ] # old @@ -145,9 +271,7 @@ def is_coordinape(tx: TreasuryTx) -> bool: def is_ygift_grant(tx: TreasuryTx) -> bool: """ Yearn used to use yGift to send team grants but that ended up being too expensive. """ - if tx._to_nickname == "Contract: yGift" and tx._symbol == "yyDAI+yUSDC+yUSDT+yTUSD": - return True - return False + return tx._to_nickname == "Contract: yGift" and tx._symbol == "yyDAI+yUSDC+yUSDT+yTUSD" def is_frontend_support(tx: TreasuryTx) -> bool: return tx in HashMatcher([ @@ -180,11 +304,12 @@ def is_other_grant(tx: TreasuryTx) -> bool: ["0x925d77f797779f087a44b2e871160fb194f9cdf949019b5c3c3617f86a0d97fb", IterFilter('log_index', [150, 151])], "0x4bda2a3b21ff999a2ef1cbbaffaa21a713293f8e175adcc4d67e862f0717d6ef", ] - disperse_hashes = [ - "0x1e411c81fc83abfe260f58072c74bfa91e38e27e0066da07ea06f75d9c8f4a00", - "0xfb2cd663228bb3823445b11e05c4b38b2fcd333230facdb388d403a9d1d8c869", - ] + if tx._from_nickname == "Disperse.app": + disperse_hashes = [ + "0x1e411c81fc83abfe260f58072c74bfa91e38e27e0066da07ea06f75d9c8f4a00", + "0xfb2cd663228bb3823445b11e05c4b38b2fcd333230facdb388d403a9d1d8c869", + ] return tx in HashMatcher(disperse_hashes) if tx in HashMatcher(hashes): return True @@ -201,6 +326,7 @@ def is_other_grant(tx: TreasuryTx) -> bool: tx.value_usd *= -1 commit() return True + return False def is_0_03_percent(tx: TreasuryTx) -> bool: return tx in HashMatcher([["0xe56521d79b0b87425e90c08ed3da5b4fa3329a40fe31597798806db07f68494e", Filter('_from_nickname', 'Disperse.app')]]) @@ -222,7 +348,7 @@ def is_docs_grant(tx: TreasuryTx) -> bool: ]) def is_yearn_exporter(tx: TreasuryTx) -> bool: - if tx.to_address.address == "0xcD63C69f08bdDa7Fe96a87A2Ca3f56f3a6990a75": + if tx.to_address == "0xcD63C69f08bdDa7Fe96a87A2Ca3f56f3a6990a75": if tx._symbol == "YFI" and tx.amount == 2.25: return True elif tx._symbol == "DAI" and tx.amount == 23_025: @@ -233,7 +359,7 @@ def is_yearn_exporter(tx: TreasuryTx) -> bool: return False def is_xopowo(tx: TreasuryTx) -> bool: - if tx.to_address.address == "0x4F909396A75FE9d59F584156A851B3770f3F438a": + if tx.to_address == "0x4F909396A75FE9d59F584156A851B3770f3F438a": if tx._symbol == "YFI": return float(tx.amount) == 5.1 or tx in HashMatcher([ # usual amount chunked in two parts @@ -248,38 +374,56 @@ def is_xopowo(tx: TreasuryTx) -> bool: # TODO: Refactor this whole thing def is_tapir(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0x80c9aC867b2D36B7e8D74646E074c460a008C0cb" and tx._symbol == "DAI" and tx.amount == 4_000 + return tx.to_address == "0x80c9aC867b2D36B7e8D74646E074c460a008C0cb" and tx._symbol == "DAI" and tx.amount == 4_000 def is_hipsterman(tx: TreasuryTx) -> bool: - if tx.to_address.address == "0xE53D3f2B99FE0Ed6C05977bC0547127836f0D78d" and tx._symbol == "DAI" and tx.amount in [3_500, 7000]: + if tx.to_address == "0xE53D3f2B99FE0Ed6C05977bC0547127836f0D78d" and tx._symbol == "DAI" and tx.amount in [3_500, 7000]: return True elif tx.hash == "0xfe0ce0947c405f22c99eab63f7e529d95eab4274f2c468deaa1da50adaeb4450": tx.value_usd *= -1 return True + return False def is_worms(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0xB1d693B77232D88a3C9467eD5619FfE79E80BCCc" + return tx.to_address == "0xB1d693B77232D88a3C9467eD5619FfE79E80BCCc" def is_ysecurity_2(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0x4851C7C7163bdF04A22C9e12Ab77e184a5dB8F0E" + return tx.to_address == "0x4851C7C7163bdF04A22C9e12Ab77e184a5dB8F0E" def is_ydiscount(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0x54991866A907891c9B85478CC1Fb0560B17D2b1D" + return tx.to_address == "0x54991866A907891c9B85478CC1Fb0560B17D2b1D" def is_ybudget(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0x5E97104F602648aDcB9f75F5F3B852CAc2Dc4576" + return tx.to_address == "0x5E97104F602648aDcB9f75F5F3B852CAc2Dc4576" or tx in HashMatcher([ + ["0xb01305e2d2c34f1da600d64aea79034b63248a76437e30de986445a9347e554f", IterFilter('log_index', [327, 328, 329, 330, 331, 332, 333])], + ]) +"""# TODO: delete this once we're sure it works def is_ysupport(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0xbd7B3Bc2C4581Fd173362c830AE45fB9506B3dA5" + '''BR #194 and some earlier stuff''' + return ( + tx.to_address == "0xbd7B3Bc2C4581Fd173362c830AE45fB9506B3dA5" + # also include payment to this vest contract for #194 + #or tx.to_address == "0x031a6Ae2a336cc838aab4501B32e5C08fA2b23BB" + )""" def is_rantom(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0x254b42CaCf7290e72e2C84c0337E36E645784Ce1" + return tx.to_address == "0x254b42CaCf7290e72e2C84c0337E36E645784Ce1" def is_tx_creator(tx: TreasuryTx) -> bool: - return tx.to_address.address == "0x4007c53A48DefaB0b9D2F05F34df7bd3088B3299" + return tx.to_address == "0x4007c53A48DefaB0b9D2F05F34df7bd3088B3299" and tx._symbol == "DAI" and tx.amount == 5_000 def is_dinobots(tx: TreasuryTx) -> bool: - return tx.token.symbol == "DAI" and tx._from_nickname == "Yearn yChad Multisig" and tx._to_nickname == "yMechs Multisig" and int(tx.amount) == 47_500 - -# TODO: add rejected BRs -_budget_requests = {} + # NOTE: refactor this out + if tx.token.symbol == "DAI" and tx._from_nickname == "Yearn yChad Multisig" and tx._to_nickname == "yMechs Multisig" and int(tx.amount) == 47_500: + return True + # br 198 + elif tx.hash == "0xb01305e2d2c34f1da600d64aea79034b63248a76437e30de986445a9347e554f" and tx.log_index == 307: + return True + # br 214 + elif tx.hash == "0xc269f6fb016a48fe150f689231a73532b631877d1376608df639dad79514904b" and tx.log_index == 349: + return True + # br 219? + elif tx.hash == "0xd7e7abe600aad4a3181a3a410bef2539389579d2ed28f3e75dbbf3a7d8613688" and tx.log_index == 547: + return True + return False diff --git a/yearn/treasury/accountant/expenses/security.py b/yearn/treasury/accountant/expenses/security.py index ed81fb030..b1eb776c2 100644 --- a/yearn/treasury/accountant/expenses/security.py +++ b/yearn/treasury/accountant/expenses/security.py @@ -7,19 +7,18 @@ def is_yacademy_audit(tx: TreasuryTx) -> bool: - hashes = [ - "0x48e05bff53a67304593a0bff5238fd2bed01c61074937706df879fb901e9e1ba", - "0xf3a31b7c162018f93c8485ad4e374a15e0053308148c7f9afe2f6d16b2013c19", - ["0x3e75d22250d87c183824c3b77ddb9cb11935db2061ce7f34df4f024d0646fcfb", Filter('log_index', 116)], - "0x7a7117d68adf198f295277ccabdecbca244eebe0d6c59200060f80a76406567e", - ["0x610941b6f2197408aabe5a8958ead79dfba44f7de39d2e9fd8a620420e0a0554", Filter('_symbol', 'USDT')], - ] - return tx in HashMatcher(hashes) + """Expense for an audit performed by yAcademy""" + # NOTE: the hash we're excluding was a one-time revshare tx before the splitter was set up. + return tx.to_address == "0x0E0aF03c405E17D0e486354fe709d3294d07EC44" and tx.hash != "0xdf3e6cf2e50052e4eeb57fb2562b5e1b02701014ce65b60e6c8a850c409b341a" def is_chainsec_audit(tx: TreasuryTx) -> bool: - return chain.id == Network.Mainnet and tx._symbol in ["USDC", "USDT"] and tx.to_address.address == "0x8bAf5eaF92E37CD9B1FcCD676918A9B3D4F87Dc7" + """Expense for an audit performed by chainsec""" + if chain.id == Network.Mainnet and tx._symbol in ["USDC", "USDT"] and tx.to_address == "0x8bAf5eaF92E37CD9B1FcCD676918A9B3D4F87Dc7": + return True + return tx in HashMatcher(["0x83ec212072f82f4aba4b512051d52c5f016de79a620a580622a0f051e3473a78"]) def is_debaub_audit(tx: TreasuryTx) -> bool: + """Expense for an audit performed by debaub""" return tx in HashMatcher([ "0xb2595246e8387b80e35784aaade3a92bd3111bf9059c3b563516886d1aefcf3f", ]) @@ -30,6 +29,7 @@ def is_decurity_audit(tx: TreasuryTx) -> bool: ]) def is_statemind_audit(tx: TreasuryTx) -> bool: + """Expense for an audit performed by statemind""" return tx in HashMatcher([ ["0xeb51cb5a3b4ae618be75bf3e23c2d8e333d93d5e81e869eca7f9612a30079822", Filter('log_index', 193)], ["0xcb79cbe5b68d04a1a3feab3360734277020ee0536380843a8c9db3e8356b81d6", Filter('log_index', 398)], @@ -37,6 +37,7 @@ def is_statemind_audit(tx: TreasuryTx) -> bool: ]) def is_mixbytes_audit(tx: TreasuryTx) -> bool: + """Expense for an audit performed by mixbytes""" return tx in HashMatcher([ ["0xcb79cbe5b68d04a1a3feab3360734277020ee0536380843a8c9db3e8356b81d6", Filter('log_index', 399)], ["0xca61496c32806ba34f0deb331c32969eda11c947fdd6235173e6fa13d9a1c288", Filter('_symbol', 'USDC')], diff --git a/yearn/treasury/accountant/ignore/__init__.py b/yearn/treasury/accountant/ignore/__init__.py index d37874e3f..c7bcbc7ff 100644 --- a/yearn/treasury/accountant/ignore/__init__.py +++ b/yearn/treasury/accountant/ignore/__init__.py @@ -2,10 +2,12 @@ from decimal import Decimal from brownie import chain -from y.networks import Network +from pony.orm import commit +from y import Contract, ContractNotVerified, Network +from y.contracts import build_name -from yearn.entities import TreasuryTx -from yearn.treasury.accountant.classes import HashMatcher, TopLevelTxGroup +from yearn.entities import Address, TreasuryTx +from yearn.treasury.accountant.classes import HashMatcher, IterFilter, TopLevelTxGroup from yearn.treasury.accountant.ignore import (general, maker, passthru, rescue_missions, staking, vaults, ygov) @@ -15,78 +17,75 @@ robovault, synthetix, uniswap, unwrapper, woofy, ycrv, yla) -from yearn.utils import contract IGNORE_LABEL = "Ignore" ignore_txgroup = TopLevelTxGroup(IGNORE_LABEL) -def is_kp3r(tx: TreasuryTx) -> bool: - contract_names = [ - 'Keep3rEscrow', - 'OracleBondedKeeper', - 'Keep3rLiquidityManager', - ] +_rkpr_contract_names = ['Keep3rEscrow', 'OracleBondedKeeper', 'Keep3rLiquidityManager'] - hashes = [ - '0x3efaafc34054dbc50871abef5e90a040688fbddc51ec4c8c45691fb2f21fd495' - ] +def _is_kp3r_related(address: Address): + return address.is_contract and build_name(address.address) in _rkpr_contract_names +def is_kp3r(tx: TreasuryTx) -> bool: if tx._symbol == "kLP-KP3R/WETH" and tx._to_nickname == "Contract: Keep3r" and "LiquidityAddition" in tx._events: for event in tx._events['LiquidityAddition']: _, _, _, amount = event.values() if Decimal(amount) / tx.token.scale == tx.amount: return True - + + if tx.to_address and tx.to_address.token and tx.to_address.token.symbol == 'KP3R': + return True + try: - return ( - ( - tx.to_address and tx.to_address.token and tx.to_address.token.symbol == 'KP3R' - ) - or ( - tx.to_address and tx.to_address.is_contract - and contract(tx.to_address.address)._build['contractName'] in contract_names - ) - or ( - tx.from_address.is_contract - and contract(tx.from_address.address)._build['contractName'] in contract_names - ) - or HashMatcher(hashes).contains(tx) - ) - except ValueError as e: - if not str(e).endswith('has not been verified') and "Contract source code not verified" not in str(e): - raise + if (tx.to_address and _is_kp3r_related(tx.to_address)) or _is_kp3r_related(tx.from_address): + return True + except ContractNotVerified: return False + + extra_kp3r_hashes = [ + '0x3efaafc34054dbc50871abef5e90a040688fbddc51ec4c8c45691fb2f21fd495' + ] + return tx in HashMatcher(extra_kp3r_hashes) def is_bridged(tx: TreasuryTx) -> bool: """ Cross-chain bridging """ + + # NOTE: for some reason we need to flush pony cache + commit() + + # Anyswap out - anyToken part if tx._symbol and tx._symbol.startswith("any") and "LogAnySwapOut" in tx._events: for event in tx._events["LogAnySwapOut"]: token, sender, receiver, amount, from_chainid, to_chainid = event.values() - if from_chainid == chain.id and tx.token.address.address == token and Decimal(amount) / tx.token.scale == tx.amount: + if from_chainid == chain.id and tx.token == token and Decimal(amount) / tx.token.scale == tx.amount: return True # Anyswap out - token part elif tx.to_address and tx.to_address.token and tx.to_address.token.symbol and tx.to_address.token.symbol.startswith("any") and "LogAnySwapOut" in tx._events: for event in tx._events["LogAnySwapOut"]: token, sender, receiver, amount, from_chainid, to_chainid = event.values() - if from_chainid == chain.id and sender == tx.from_address.address and tx.token.address.address == contract(token).underlying() and Decimal(amount) / tx.token.scale == tx.amount: + if from_chainid == chain.id and tx.from_address == sender and tx.token == Contract(token).underlying() and Decimal(amount) / tx.token.scale == tx.amount: return True # Anyswap in - anyToken part elif tx._symbol and tx._symbol.startswith("any") and "LogAnySwapIn" in tx._events: for event in tx._events["LogAnySwapIn"]: txhash, token, receiver, amount, from_chainid, to_chainid = event.values() - if to_chainid == chain.id and tx.token.address.address == token and Decimal(amount) / tx.token.scale == tx.amount: + if to_chainid == chain.id and tx.token == token and Decimal(amount) / tx.token.scale == tx.amount: return True # Anyswap in - token part elif tx.from_address and tx.from_address.token and tx.from_address.token.symbol and tx.from_address.token.symbol.startswith("any") and "LogAnySwapIn" in tx._events: for event in tx._events["LogAnySwapIn"]: txhash, token, receiver, amount, from_chainid, to_chainid = event.values() - if to_chainid == chain.id and receiver == tx.to_address.address and tx.token.address.address == contract(token).underlying() and Decimal(amount) / tx.token.scale == tx.amount: + if to_chainid == chain.id and tx.to_address == receiver and tx.token == Contract(token).underlying() and Decimal(amount) / tx.token.scale == tx.amount: return True + + # Bridge to Base for veFarming @ multisig 0xcf9fDe11a7Ab556184529442f9fCA37FB6220970 + if chain.id == Network.Mainnet and tx in HashMatcher([["0x2dd0ed2fdbec7d6eccfda46fc8df710aec94a2add5baab37565c87c1eb5f1e2f", IterFilter('log_index', [440, 449])]]): + return True return tx in HashMatcher({ Network.Mainnet: [ @@ -138,6 +137,7 @@ def is_keep_crv(tx: TreasuryTx) -> bool: ignore_txgroup.create_child("keepCRV", is_keep_crv) # Vaults +ignore_txgroup.create_child("IEarn Withdrawal", vaults.is_iearn_withdrawal) ignore_txgroup.create_child("Vault Deposit", vaults.is_vault_deposit) ignore_txgroup.create_child("Vault Withdrawal", vaults.is_vault_withdrawal) @@ -153,6 +153,8 @@ def is_keep_crv(tx: TreasuryTx) -> bool: ignore_txgroup.create_child("Transfer to yGov (Deprecated)", ygov.is_sent_to_ygov) ignore_txgroup.create_child("Maker CDP Deposit", maker.is_yfi_cdp_deposit) ignore_txgroup.create_child("Maker CDP Withdrawal", maker.is_yfi_cdp_withdrawal) + ignore_txgroup.create_child("Maker USDC CDP Deposit", maker.is_usdc_cdp_deposit) + ignore_txgroup.create_child("Maker USDC CDP Withdrawal", maker.is_usdc_cdp_withdrawal) ignore_txgroup.create_child("Minting DAI", maker.is_dai) ignore_txgroup.create_child("Replenish Streams", general.is_stream_replenishment) ignore_txgroup.create_child("Clawback Vesting Packages", general.is_reclaim_locked_vest) @@ -170,7 +172,7 @@ def is_keep_crv(tx: TreasuryTx) -> bool: passthru_txgroup = ignore_txgroup.create_child("Pass-Thru to Vaults", passthru.is_pass_thru) passthru_txgroup.create_child("Curve Bribes for yveCRV", passthru.is_curve_bribe) passthru_txgroup.create_child("Sent to dinobots to dump", passthru.is_sent_to_dinoswap) -passthru_txgroup.create_child("Factory Vault Yield", passthru.is_factory_yield) +passthru_txgroup.create_child("Factory Vault Yield", passthru.is_factory_vault_yield) if chain.id == Network.Mainnet: passthru_txgroup.create_child("Cowswap Migration", passthru.is_cowswap_migration) passthru_txgroup.create_child("Single Sided IB", passthru.is_single_sided_ib) @@ -185,6 +187,7 @@ def is_keep_crv(tx: TreasuryTx) -> bool: passthru_txgroup.create_child("StrategyAuraUSDClonable", passthru.is_aura) passthru_txgroup.create_child("Bribes for yCRV", passthru.is_ycrv) passthru_txgroup.create_child("BAL Rewards", passthru.is_bal) + passthru_txgroup.create_child("yPrisma Strategy Migration", passthru.is_yprisma_migration) elif chain.id == Network.Fantom: passthru_txgroup.create_child("IB", passthru.is_ib) @@ -220,6 +223,14 @@ def is_keep_crv(tx: TreasuryTx) -> bool: swaps_txgroup.create_child("WOOFY", woofy.is_woofy) swaps_txgroup.create_child("OTC", otc.is_otc) +def other(tx: TreasuryTx) -> bool: + # TODO: put this somewhere else + return tx in HashMatcher([ + ["0x898ab224087ec7127435a33ee114e6b392e51cdc82a4409fb9db67775bd1edca", IterFilter('log_index', [100, 129])], + ]) + +swaps_txgroup.create_child("Misc Swaps", other) + if chain.id == Network.Mainnet: swaps_txgroup.create_child("Gearbox Deposit", gearbox.is_gearbox_deposit) swaps_txgroup.create_child("Gearbox Withdrawal", gearbox.is_gearbox_withdrawal) @@ -242,3 +253,4 @@ def is_keep_crv(tx: TreasuryTx) -> bool: buying_yfi_txgroup.create_child("Swap", HashMatcher(buying_yfi.non_otc_hashes).contains) buying_yfi_txgroup.create_child("Top-up Buyer Contract", buying_yfi.is_buyer_top_up) buying_yfi_txgroup.create_child("Buyer Contract", buying_yfi.is_buying_with_buyer) +buying_yfi_txgroup.create_child("Buyback Auction Contract", buying_yfi.is_buying_with_auction) diff --git a/yearn/treasury/accountant/ignore/general.py b/yearn/treasury/accountant/ignore/general.py index f4c7e744d..a1aa0a4d9 100644 --- a/yearn/treasury/accountant/ignore/general.py +++ b/yearn/treasury/accountant/ignore/general.py @@ -5,19 +5,19 @@ from brownie import ZERO_ADDRESS, chain from brownie.exceptions import RPCRequestError from pony.orm import commit, select +from requests import HTTPError +from y import ContractNotVerified, Network, get_price from y.networks import Network -from y.prices import magic from yearn.constants import ERC20_TRANSFER_EVENT_HASH, TREASURY_WALLETS from yearn.entities import TreasuryTx from yearn.events import decode_logs, get_logs_asap -from yearn.outputs.postgres.utils import (cache_address, cache_chain, - cache_token, cache_txgroup) +from yearn.outputs.postgres.utils import (address_dbid, chain_dbid, + cache_txgroup, token_dbid) from yearn.prices import constants from yearn.treasury.accountant.classes import Filter, HashMatcher from yearn.treasury.accountant.constants import (DISPERSE_APP, PENDING_LABEL, treasury) -from yearn.utils import contract logger = logging.getLogger(__name__) @@ -26,122 +26,132 @@ def is_internal_transfer(tx: TreasuryTx) -> bool: if chain.id == Network.Mainnet and tx.block > 17162286 and "yMechs Multisig" in [tx._from_nickname, tx._to_nickname]: # as of may 1 2023, ymechs wallet split from treasury return False - return tx.to_address and tx.to_address.address in treasury.addresses and tx.from_address.address in treasury.addresses + return tx.to_address.address in treasury.addresses and tx.from_address.address in treasury.addresses def has_amount_zero(tx: TreasuryTx) -> bool: return tx.amount == 0 def is_disperse_dot_app(tx: TreasuryTx) -> bool: - if tx._to_nickname == "Disperse.app": - eee_address = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - # Make sure the other side of disperse.app txs are in the pg db. - query = select(t for t in TreasuryTx if t.hash == tx.hash and t._from_nickname == "Disperse.app" and t.token.address.address == tx.token.address.address) - if len(query) == 0 and "Transfer" in tx._events and tx.token.address.address != eee_address: - # find transfer events and add txs to pg - transfers = [ - transfer - for transfer in get_logs_asap(None, [ERC20_TRANSFER_EVENT_HASH], tx.block, tx.block) - if transfer.transactionHash.hex() == tx.hash - ] - print(f'len logs: {len(tx._events["Transfer"])}') - for transfer in transfers: + if tx._to_nickname != "Disperse.app": + return False + eee_address = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + # Make sure the other side of disperse.app txs are in the pg db. + query = select(t for t in TreasuryTx if t.hash == tx.hash and t._from_nickname == "Disperse.app" and t.token == tx.token) + if len(query) == 0 and "Transfer" in tx._events and tx.token != eee_address: + # find transfer events and add txs to pg + transfers = [ + transfer + for transfer in get_logs_asap(None, [ERC20_TRANSFER_EVENT_HASH], tx.block, tx.block) + if transfer.transactionHash.hex() == tx.hash + ] + print(f'len logs: {len(tx._events["Transfer"])}') + for transfer in transfers: + if tx.token == transfer.address: sender, receiver, amount = decode_logs([transfer])["Transfer"][0].values() - if sender == DISPERSE_APP and transfer.address == tx.token.address.address: + if sender == DISPERSE_APP: amount /= Decimal(tx.token.scale) - price = Decimal(magic.get_price(transfer.address, block=tx.block)) + price = Decimal(get_price(transfer.address, block=tx.block)) TreasuryTx( - chain = cache_chain(), + chain = chain_dbid(), timestamp = chain[tx.block].timestamp, block = tx.block, hash = tx.hash, log_index = transfer.logIndex, - token = cache_token(transfer.address), + # NOTE: We pass the entity pk instead of the entity obj so we can avoid the occasional + # `TransactionError("An attempt to mix objects belonging to different transactions")` + # when processing never-before-seen entities. + token = token_dbid(transfer.address), from_address = tx.to_address, - to_address = cache_address(receiver), + to_address = address_dbid(receiver), amount = amount, price = round(price, 18), value_usd = round(amount * price, 18), - txgroup = cache_txgroup(PENDING_LABEL), + txgroup = cache_txgroup(PENDING_LABEL).txgroup_id, ) - commit() + commit() - # Only sort the input tx once we are sure we have the output txs - # NOTE this only works for ERC20s - if len(query) == len(transfers) and len(query) > 0: - return True - - if len(query) == 0 and tx.token.address.address == eee_address and tx.to_address: - # find internal txs and add to pg + # Only sort the input tx once we are sure we have the output txs + # NOTE this only works for ERC20s + if len(query) == len(transfers) and len(query) > 0: + return True + + if len(query) == 0 and tx.token == eee_address and tx.to_address: + # find internal txs and add to pg + try: for int_tx in chain.get_transaction(tx.hash).internal_transfers: - if int_tx['from'] == tx.to_address.address: + if tx.to_address == int_tx['from']: amount = int_tx['value'] / Decimal(tx.token.scale) - price = Decimal(magic.get_price(eee_address, tx.block)) + price = Decimal(get_price(eee_address, tx.block)) TreasuryTx( - chain = cache_chain(), + chain = chain_dbid(), timestamp = chain[tx.block].timestamp, block = tx.block, hash = tx.hash, log_index = None, - token = cache_token("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + token = token_dbid("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), from_address = tx.to_address, - to_address = cache_address(int_tx['to']), + to_address = address_dbid(int_tx['to']), amount = amount, price = round(price, 18), value_usd = round(amount * price, 18), - txgroup = cache_txgroup(PENDING_LABEL), + txgroup = cache_txgroup(PENDING_LABEL).txgroup_id, ) commit() - - # Did we already insert the outputs for this disperse tx / token? - if len(query) > 0: - return True + except HTTPError as e: + print(e.response.__dict__) + raise + + # Did we already insert the outputs for this disperse tx / token? + return len(query) > 0 def is_gnosis_execution(tx: TreasuryTx) -> bool: - if ( - tx.amount == 0 - and tx.to_address - and tx.to_address.address in treasury.addresses - ): - try: - con = contract(tx.to_address.address) - except ValueError as e: - if "contract source code not verified" in str(e).lower() or str(e).endswith('has not been verified'): - return False - raise + if tx.amount != 0 or tx.to_address.address not in treasury.addresses: + return False + + try: + con = tx.to_address.contract + except ContractNotVerified: + return False - if con._build['contractName'] != "GnosisSafeProxy": - return False - - if tx._transaction.status == 0: # Reverted - return True - try: - events = tx._events - except RPCRequestError: - return False - if "ExecutionSuccess" in events: - return True + if con._build['contractName'] != "GnosisSafeProxy": + return False + + if tx._transaction.status == 0: # Reverted + return True + try: + events = tx._events + except RPCRequestError: + return False + return "ExecutionSuccess" in events def is_weth(tx: TreasuryTx) -> bool: # Withdrawal - if tx.from_address.address == ZERO_ADDRESS and tx.to_address and tx.to_address.address in treasury.addresses and tx.token.address.address == constants.weth: + if tx.from_address == ZERO_ADDRESS and tx.to_address.address in treasury.addresses and tx.token == constants.weth: return True - if tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == constants.weth and tx.token.address.address == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": + if tx.from_address.address in treasury.addresses and tx.to_address == constants.weth and tx.token == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": return True - if tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == ZERO_ADDRESS and tx.token.address.address == constants.weth: + if tx.from_address.address in treasury.addresses and tx.to_address == ZERO_ADDRESS and tx.token == constants.weth: return True - if tx.from_address.address == constants.weth and tx.to_address and tx.to_address.address in treasury.addresses and tx.token.address.address == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": + if tx.from_address == constants.weth and tx.to_address.address in treasury.addresses and tx.token == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": return True + return False def is_stream_replenishment(tx: TreasuryTx) -> bool: if tx._to_nickname in ["Contract: LlamaPay", "Vesting Escrow Factory"]: return True - # Puling unused funds back from vesting escrow - return tx in HashMatcher({ + # Puling unused funds back from vesting escrow / llamapay + elif tx in HashMatcher({ Network.Mainnet: [ ["0x1621ba5c9b57930c97cc43d5d6d401ee9c69fed435b0b458ee031544a10bfa75", Filter('log_index', 487)], ], - }.get(chain.id, [])) + }.get(chain.id, [])) or tx._from_nickname == "Contract: LlamaPay": + if tx.amount > 0: + tx.amount *= -1 + if tx.value_usd > 0: + tx.value_usd *= -1 + return True + return False def is_scam_airdrop(tx: TreasuryTx) -> bool: hashes = { @@ -151,11 +161,11 @@ def is_scam_airdrop(tx: TreasuryTx) -> bool: }.get(chain.id, []) return tx in HashMatcher(hashes) +_OTC_TRADER_ADDRESS = "0xfa42022707f02cFfC80557B166d625D52346dd6d" + def is_otc_trader(tx: TreasuryTx) -> bool: """ Yearn supplied liquidity so people could convert from YFI <> WOOFY on Fantom. """ - OTC_TRADER_ADDRESS = "0xfa42022707f02cFfC80557B166d625D52346dd6d" - if tx.to_address: - return OTC_TRADER_ADDRESS in [tx.from_address.address, tx.to_address.address] + return tx.to_address is not None and _OTC_TRADER_ADDRESS in [tx.from_address.address, tx.to_address.address] def is_reclaim_locked_vest(tx: TreasuryTx) -> bool: """Unvested portion of vesting packages clawed back to prepare for veYFI""" @@ -179,15 +189,16 @@ def is_ycrv_for_testing(tx: TreasuryTx) -> bool: ["0x85dc73fca1a8ec500bc46cd18782b8bba4c811714597fbdaaa209ac9f0c7f253", Filter('log_index', 279)], ]) +VESTING_FACTORY = "0x98d3872b4025ABE58C4667216047Fe549378d90f" + def is_vest_factory(tx: TreasuryTx) -> bool: - VESTING_FACTORY = "0x98d3872b4025ABE58C4667216047Fe549378d90f" - return tx.to_address.address == VESTING_FACTORY + return tx.to_address == VESTING_FACTORY def is_ignore_ymechs(tx: TreasuryTx) -> bool: """After may 1 2023 ymechs wallet separated from yearn treasury""" - if tx.block > 17162286: - if tx._from_nickname == "yMechs Multisig" and tx.to_address.address not in TREASURY_WALLETS: - return True - if tx._to_nickname == "yMechs Multisig" and tx.from_address.address not in TREASURY_WALLETS: - return True - return False + if tx.block <= 17162286: + return False + return ( + (tx._from_nickname == "yMechs Multisig" and tx.to_address not in TREASURY_WALLETS) + or (tx._to_nickname == "yMechs Multisig" and tx.from_address not in TREASURY_WALLETS) + ) diff --git a/yearn/treasury/accountant/ignore/maker.py b/yearn/treasury/accountant/ignore/maker.py index e21e27963..f3bfaf47e 100644 --- a/yearn/treasury/accountant/ignore/maker.py +++ b/yearn/treasury/accountant/ignore/maker.py @@ -1,4 +1,6 @@ +from decimal import Decimal + from brownie import ZERO_ADDRESS from yearn.entities import TreasuryTx @@ -12,20 +14,34 @@ def is_yfi_cdp_deposit(tx: TreasuryTx) -> bool: for event in tx._events['slip']: if all(arg in event for arg in DEPOSIT_EVENT_ARGS) and round(event['wad'] / 1e18, 15) == round(float(tx.amount), 15): return True + return False def is_yfi_cdp_withdrawal(tx: TreasuryTx) -> bool: - if tx._symbol == 'YFI' and tx.to_address and tx.to_address.address in treasury.addresses and 'flux' in tx._events: + if tx._symbol == 'YFI' and tx.to_address.address in treasury.addresses and 'flux' in tx._events: for event in tx._events['flux']: if all(arg in event for arg in WITHDRAWAL_EVENT_ARGS) and round(event['wad'] / 1e18, 15) == round(float(tx.amount), 15): return True + return False + +def is_usdc_cdp_deposit(tx: TreasuryTx) -> bool: + if tx._symbol == 'USDC' and tx.from_address.address in treasury.addresses and 'slip' in tx._events: + for event in tx._events['slip']: + if all(arg in event for arg in DEPOSIT_EVENT_ARGS) and Decimal(event['wad']) / 10**18 == tx.amount: + return True + return False + +def is_usdc_cdp_withdrawal(tx: TreasuryTx) -> bool: + if tx._symbol == 'USDC' and tx.to_address.address in treasury.addresses and 'flux' in tx._events: + for event in tx._events['flux']: + if all(arg in event for arg in WITHDRAWAL_EVENT_ARGS) and Decimal(event['wad']) / 10**18 == tx.amount: + return True + return False + +DSPROXY = "0xd42e1Cb8b98382df7Db43e0F09dFE57365659D16" def is_dai(tx: TreasuryTx) -> bool: """ Returns True when minting or burning DAI. """ - if tx._symbol == "DAI": - if tx.from_address.address == ZERO_ADDRESS: - return True - elif tx.to_address.address == "0xd42e1Cb8b98382df7Db43e0F09dFE57365659D16": # DSProxy - return True + return tx._symbol == "DAI" and (tx.from_address == ZERO_ADDRESS or tx.to_address == DSPROXY) def is_dsr(tx: TreasuryTx) -> bool: """sending DAI to or receiving DAI back from Maker's DSR module""" diff --git a/yearn/treasury/accountant/ignore/passthru.py b/yearn/treasury/accountant/ignore/passthru.py index 870717f71..d8df1aa57 100644 --- a/yearn/treasury/accountant/ignore/passthru.py +++ b/yearn/treasury/accountant/ignore/passthru.py @@ -1,68 +1,72 @@ +from decimal import Decimal + from brownie import ZERO_ADDRESS, chain -from y.networks import Network +from y import Network from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import Filter, HashMatcher, IterFilter from yearn.treasury.accountant.constants import treasury +_pass_thru_hashes = { + Network.Mainnet: [ + "0xf662c68817c56a64b801181a3175c8a7e7a5add45f8242990c695d418651e50d", + ], + Network.Fantom: [ + "0x411d0aff42c3862d06a0b04b5ffd91f4593a9a8b2685d554fe1fbe5dc7e4fc04", + "0xa347da365286cc912e4590fc71e97a5bcba9e258c98a301f85918826538aa021", + ], +}.get(chain.id, []) + def is_pass_thru(tx: TreasuryTx) -> bool: - pass_thru_hashes = { - Network.Mainnet: [ - "0xf662c68817c56a64b801181a3175c8a7e7a5add45f8242990c695d418651e50d", - ], - Network.Fantom: [ - "0x411d0aff42c3862d06a0b04b5ffd91f4593a9a8b2685d554fe1fbe5dc7e4fc04", - "0xa347da365286cc912e4590fc71e97a5bcba9e258c98a301f85918826538aa021", - ], - }.get(chain.id, []) # skipped the hashmatcher to do strange things... there is probably a better way to do this - if tx.hash in pass_thru_hashes and str(tx.log_index).lower() != "nan": + if tx.hash in _pass_thru_hashes and str(tx.log_index).lower() != "nan": return True # Passing thru to yvWFTM - if chain.id == Network.Fantom and tx._symbol == 'WFTM' and tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == "0x0DEC85e74A92c52b7F708c4B10207D9560CEFaf0": + if chain.id == Network.Fantom and tx._symbol == 'WFTM' and tx.from_address.address in treasury.addresses and tx.to_address == "0x0DEC85e74A92c52b7F708c4B10207D9560CEFaf0": # dont want to accidenally sort a vault deposit here is_deposit = None for event in tx._events['Transfer']: sender, receiver, _ = event.values() - if event.address == tx.to_address.address and sender == ZERO_ADDRESS and receiver == tx.from_address.address: + if tx.to_address == event.address and sender == ZERO_ADDRESS and tx.from_address == receiver: is_deposit = True if not is_deposit: return True - - pass_thru_hashes = { + + return tx in HashMatcher({ Network.Mainnet: [ ["0x51baf41f9daa68ac7be8024125852f1e21a3bb954ea32e686ac25a72903a1c8e", IterFilter('_symbol',['CRV','CVX'])], ["0xdc4e0045901cfd5ef4c6327b846a8bd229abdbf289547cd0e969874b47124342", IterFilter('log_index',[28,29,30,31])], "0xae6797ad466de75731117df46ccea5c263265dd6258d596b9d6d8cf3a7b1e3c2", "0x2a6191ba8426d3ae77e2a6c91de10a6e76d1abdb2d0f831c6c5aad52be3d6246", "0x25b54e113e58a3a4bbffc011cdfcb8c07a0424f33b0dbda921803d82b88f1429", # https://github.com/yearn/chief-multisig-officer/pull/924 + "0xcb000dd2b623f9924fe0234831800950a3269b2d412ce9eeabb0ec65cd737059", + ["0xd782e3923961ea7462584d61e0e37cf10289158a8cc338adb77b3ad38c72c459", Filter("_symbol", "COW")], ], Network.Fantom: [ "0x14faeac8ee0734875611e68ce0614eaf39db94a5ffb5bc6f9739da6daf58282a", ], - }.get(chain.id, []) + }.get(chain.id, [])) - if tx in HashMatcher(pass_thru_hashes): - return True +_cvx_hashes = [ + ["0xf6f04b4832b70b09b089884a749115d4e77691b94625c38b227eb74ffc882121", IterFilter('_symbol', ['CVX','CRV'])], + ["0xdc552229f5bd25c411b1bf51d19f5b40809094306b8790e34ba5ad3ef28be56c", IterFilter('_symbol', ['CVX','CRV'])], +] def is_cvx(tx: TreasuryTx) -> bool: - hashes = [ - ["0xf6f04b4832b70b09b089884a749115d4e77691b94625c38b227eb74ffc882121", IterFilter('_symbol', ['CVX','CRV'])], - ["0xdc552229f5bd25c411b1bf51d19f5b40809094306b8790e34ba5ad3ef28be56c", IterFilter('_symbol', ['CVX','CRV'])], - ] - return tx in HashMatcher(hashes) + return tx in HashMatcher(_cvx_hashes) + +_ib_hashes = [ + ["0x71daf54660d038c5c4ed047c8e6c4bfda7e798fbb0628903e4810b39b57260b2", Filter('_symbol', 'IB')], + ["0x7a21623b630e2429715cf3af0732bef79098f9354983c936a41a1831dab71306", Filter('_symbol', 'IB')], + ["0x8fb5bb391c47a3c45b36562ffbce03d76edf11795477cae45e5a7393aac71bec", Filter('_symbol', 'IB')], + ["0x773037a85ddafc5e30b62097932c3a35232e3d055cd1acdf5ef63dc2ce6f2c7c", Filter('_symbol', 'IB')], +] def is_ib(tx: TreasuryTx) -> bool: - hashes = [ - ["0x71daf54660d038c5c4ed047c8e6c4bfda7e798fbb0628903e4810b39b57260b2", Filter('_symbol', 'IB')], - ["0x7a21623b630e2429715cf3af0732bef79098f9354983c936a41a1831dab71306", Filter('_symbol', 'IB')], - ["0x8fb5bb391c47a3c45b36562ffbce03d76edf11795477cae45e5a7393aac71bec", Filter('_symbol', 'IB')], - ["0x773037a85ddafc5e30b62097932c3a35232e3d055cd1acdf5ef63dc2ce6f2c7c", Filter('_symbol', 'IB')], - ] - return tx in HashMatcher(hashes) + return tx in HashMatcher(_ib_hashes) def is_curve_bribe(tx: TreasuryTx) -> bool: """ All present and future curve bribes are committed to yveCRV holders. """ @@ -86,53 +90,50 @@ def is_curve_bribe(tx: TreasuryTx) -> bool: "0xce45da7e3a7616ed0c0d356d6dfa8a784606c9a8034bae9faa40abf7b52be114", ]) +_yvboost_hashes = [ + "0x9eabdf110efbfb44aab7a50eb4fe187f68deae7c8f28d78753c355029f2658d3", + "0x5a80f5ff90fc6f4f4597290b2432adbb62ab4154ead68b515accdf19b01c1086", + "0x848b4d629e137ad8d8eefe5db40eab895c9959b9c210d0ae0fef16a04bfaaee1", + "0x896663aa9e2633b5d152028bdf84d7f4b1137dd27a8e61daca3863db16bebc4f", + "0xd8aa1e5d093a89515530b7267a9fd216b97fddb6478b3027b2f5c1d53070cd5f", + "0x169aab84b408fce76e0b776ebf412c796240300c5610f0263d5c09d0d3f1b062", + "0xe6fefbf061f4489cd967cdff6aa8aca616f0c709e08c3696f12b0027e9e166c9", + "0x10be8a3345660f3c51b695e8716f758b1a91628bd612093784f0516a604f79c1", +] + def is_buying_yvboost(tx: TreasuryTx) -> bool: """ Bought back yvBoost is unwrapped and sent back to holders. """ yswap = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41' - if tx._symbol == 'SPELL' and tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == yswap: + if tx._symbol == 'SPELL' and tx.from_address.address in treasury.addresses and tx.to_address == yswap: return True - elif tx._symbol == "yveCRV-DAO" and tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address in ["0xd7240B32d24B814fE52946cD44d94a2e3532E63d","0x7fe508eE30316e3261079e2C81f4451E0445103b"]: + elif tx._symbol == "yveCRV-DAO" and tx.from_address.address in treasury.addresses and tx.to_address in ["0xd7240B32d24B814fE52946cD44d94a2e3532E63d","0x7fe508eE30316e3261079e2C81f4451E0445103b"]: return True - elif tx._symbol == "3Crv" and tx.from_address.address == "0xd7240B32d24B814fE52946cD44d94a2e3532E63d" and tx.to_address and tx.to_address.address in treasury.addresses: + elif tx._symbol == "3Crv" and tx.from_address == "0xd7240B32d24B814fE52946cD44d94a2e3532E63d" and tx.to_address.address in treasury.addresses: return True # SPELL bribe handling elif tx._symbol == "SPELL": - if tx._to_nickname == "Abracadabra Treasury": + if tx._to_nickname in ["Abracadabra Treasury", "Contract: BribeSplitter"]: return True - elif tx._to_nickname == "Contract: BribeSplitter": - return True - - hashes = [ - "0x9eabdf110efbfb44aab7a50eb4fe187f68deae7c8f28d78753c355029f2658d3", - "0x5a80f5ff90fc6f4f4597290b2432adbb62ab4154ead68b515accdf19b01c1086", - "0x848b4d629e137ad8d8eefe5db40eab895c9959b9c210d0ae0fef16a04bfaaee1", - "0x896663aa9e2633b5d152028bdf84d7f4b1137dd27a8e61daca3863db16bebc4f", - "0xd8aa1e5d093a89515530b7267a9fd216b97fddb6478b3027b2f5c1d53070cd5f", - "0x169aab84b408fce76e0b776ebf412c796240300c5610f0263d5c09d0d3f1b062", - "0xe6fefbf061f4489cd967cdff6aa8aca616f0c709e08c3696f12b0027e9e166c9", - "0x10be8a3345660f3c51b695e8716f758b1a91628bd612093784f0516a604f79c1", - ] - - if tx in HashMatcher(hashes): - return True + + return tx in HashMatcher(_yvboost_hashes) + +_more_yvboost_hashes = [ + "0x9366b851b5d84f898962fce62356f1d020f3220ec794476eb19cd8106ca08283", + ["0x47bcb48367b5c724780b40a19eed7ba4f623de619e98c30807f52be934d28faf", Filter('log_index', 285)], + "0x17e2744e2959ba380f45383bcce11ec18e0a6bdd959d09cacdc7bb34008b14aa", + ["0x40352e7166bf5196aa1160302cfcc157facf99731af0e11741b8729dd84e131c", Filter('log_index', 125)], + "0xa025624820105a9f6914a13d5b50bd42e599b2093c8edb105321a43a86cfeb38", +] def is_yvboost_from_elsewhere(tx: TreasuryTx) -> bool: """ where is this from? who knows, doesn't matter yet. MUST INVESTIGATE """ - hashes = [ - "0x9366b851b5d84f898962fce62356f1d020f3220ec794476eb19cd8106ca08283", - ["0x47bcb48367b5c724780b40a19eed7ba4f623de619e98c30807f52be934d28faf", Filter('log_index', 285)], - "0x17e2744e2959ba380f45383bcce11ec18e0a6bdd959d09cacdc7bb34008b14aa", - ["0x40352e7166bf5196aa1160302cfcc157facf99731af0e11741b8729dd84e131c", Filter('log_index', 125)], - "0xa025624820105a9f6914a13d5b50bd42e599b2093c8edb105321a43a86cfeb38", - ] - return tx in HashMatcher(hashes) + return tx in HashMatcher(_more_yvboost_hashes) def is_inverse_fees_from_yearn_fed(tx: TreasuryTx) -> bool: - if tx._symbol == "yvDOLA-U" and tx.from_address.address in treasury.addresses and tx._to_nickname == "Contract: YearnFed": - return True + return tx._symbol == "yvDOLA-U" and tx.from_address.address in treasury.addresses and tx._to_nickname == "Contract: YearnFed" def is_stkaave(tx: TreasuryTx) -> bool: """ stkAAVE is sent from a strategy to ychad, then to sms for unwrapping. """ @@ -141,6 +142,7 @@ def is_stkaave(tx: TreasuryTx) -> bool: return True elif tx._from_nickname == "Yearn yChad Multisig" and tx._to_nickname == "Yearn Strategist Multisig": return True + return False def is_single_sided_ib(tx: TreasuryTx) -> bool: return tx in HashMatcher([ @@ -205,14 +207,16 @@ def is_ycrv(tx: TreasuryTx) -> bool: """ These are routed thru cowswap with dai as the purchase token. """ yswaps = "0x7d2aB9CA511EBD6F03971Fb417d3492aA82513f0" ymechs = "0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6" - if (tx.from_address.address == yswaps and tx._symbol == "DAI") or (tx.from_address.address == ymechs and tx._symbol == "3Crv"): - print("check for Trade event") - if tx.to_address.address == cowswap_router and "Trade" in tx._events: - for trade in tx._events["Trade"]: - print(trade) - owner, sell_token, buy_token, sell_amount, buy_amount, fee_amount, order_uid = trade.values() - print(owner, sell_token, buy_token) - return owner == tx.from_address.address and sell_token == tx.token.address.address and buy_token == ycrv and sell_amount / 10 ** 18 == tx.amount + if (tx.from_address == yswaps and tx._symbol == "DAI") or (tx.from_address == ymechs and tx._symbol == "3Crv"): + if tx.to_address == cowswap_router: + if "Trade" in tx._events: + for trade in tx._events["Trade"]: + owner, sell_token, buy_token, sell_amount, buy_amount, fee_amount, order_uid = trade.values() + if tx.from_address == owner and tx.token == sell_token and buy_token == ycrv and Decimal(sell_amount) / 10 ** 18 == tx.amount: + return True + print(f"Trade: {trade}") + else: + print(f"no Trade event in {tx.hash}") elif tx in HashMatcher([ # one off exception case to correct accounting mix-up @@ -224,33 +228,37 @@ def is_ycrv(tx: TreasuryTx) -> bool: "0xd2c0a137d03811c5e4c27be19c7893f7fdd5851bdd6f825ee7301f3634033035", ]): return True - else: - return is_dola_bribe(tx) + return is_dola_bribe(tx) def is_sent_to_dinoswap(tx: TreasuryTx) -> bool: """ These tokens are dumpped and the proceeds sent back to the origin strategy. """ - if chain.id == Network.Mainnet and tx._from_nickname == "Contract: Strategy" and tx._to_nickname == "yMechs Multisig": - return True + return chain.id == Network.Mainnet and tx._from_nickname == "Contract: Strategy" and tx._to_nickname == "yMechs Multisig" def is_dola_bribe(tx: TreasuryTx) -> bool: - if tx._from_nickname == "ySwap Multisig" and tx._to_nickname == "Contract: GPv2Settlement" and tx._symbol == "DOLA": - return True + return tx._from_nickname == "ySwap Multisig" and tx._to_nickname == "Contract: GPv2Settlement" and tx._symbol == "DOLA" def is_bal(tx: TreasuryTx) -> bool: return tx._symbol == "BAL" and tx in HashMatcher([ "0xf4677cce1a08ecd54272cdc1b23bc64693450f8bb5d6de59b8e58e288ec3b2a7", ]) -def is_factory_yield(tx: TreasuryTx) -> bool: - factory_strats = { - "Contract: StrategyConvexFactoryClonable", - "Contract: StrategyCurveBoostedFactoryClonable", - } - if tx._from_nickname in factory_strats and tx._to_nickname == "yMechs Multisig": +def is_yprisma_migration(tx: TreasuryTx) -> bool: + return tx in HashMatcher(["0xed39b66c01e25b053117778c80e544c985d962522233b49ce6f7fe136b1a4474"]) + +_factory_strat_to_yield_tokens = { + "Contract: StrategyCurveBoostedFactoryClonable": ["CRV", "LDO"], + "Contract: StrategyConvexFactoryClonable": ["CRV", "CVX"], + "Contract: StrategyConvexFraxFactoryClonable": ["CRV", "CVX", "FXS"], +} + +def is_factory_vault_yield(tx: TreasuryTx) -> bool: + if tx._to_nickname == "yMechs Multisig" and tx._symbol in _factory_strat_to_yield_tokens.get(tx._from_nickname, []): return True return tx in HashMatcher({ + # TODO: figure out why these didn't match and update the sort rule Network.Mainnet: [ "0xefea7be3abc943d0aa0eedfbc9e3db4677e1bd92511265ad0cb619bea1763d14", "0x2f9fefebde546c00a5c519e370e1205058aad8a3881d0bbd2b3d433ed9da6cb3", + "0x3d0624e984904f9a77ad83453ab01841e870804bfd96fadaced62fcad6fc1507", ], - }.get(chain.id, [])) \ No newline at end of file + }.get(chain.id, [])) diff --git a/yearn/treasury/accountant/ignore/staking.py b/yearn/treasury/accountant/ignore/staking.py index fa737a59a..a92ab609c 100644 --- a/yearn/treasury/accountant/ignore/staking.py +++ b/yearn/treasury/accountant/ignore/staking.py @@ -1,11 +1,10 @@ from brownie import ZERO_ADDRESS, chain -from y.networks import Network +from y import Network from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import HashMatcher from yearn.treasury.accountant.constants import treasury -from yearn.utils import contract def is_solidex_staking(tx: TreasuryTx) -> bool: @@ -15,32 +14,32 @@ def is_solidex_staking(tx: TreasuryTx) -> bool: # STAKING # Step 1: Stake your tokens - if tx.from_address.address in treasury.addresses and tx.to_address.address == lp_depositor and "Deposited" in tx._events: + if tx.from_address.address in treasury.addresses and tx.to_address == lp_depositor and "Deposited" in tx._events: for event in tx._events["Deposited"]: - if event.address == lp_depositor and 'user' in event and 'pool' in event and event['user'] == tx.from_address.address and event['pool'] == tx.token.address.address: + if event.address == lp_depositor and 'user' in event and 'pool' in event and tx.from_address == event['user'] and tx.token == event['pool']: return True # Step 2: Get your claim tokens - elif tx.from_address.address == ZERO_ADDRESS and tx.to_address and tx.to_address.address in treasury.addresses and "Deposited" in tx._events: + elif tx.from_address == ZERO_ADDRESS and tx.to_address.address in treasury.addresses and "Deposited" in tx._events: for event in tx._events["Deposited"]: - pool = contract(tx.token.address.address).pool() - if event.address == lp_depositor and 'user' in event and 'pool' in event and event['user'] == tx.to_address.address and event['pool'] == pool: + pool = tx.token.contract.pool() + if event.address == lp_depositor and 'user' in event and 'pool' in event and tx.to_address == event['user'] and event['pool'] == pool: return True # UNSTAKING # Step 1: Burn your claim tokens - elif tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == ZERO_ADDRESS and "Withdrawn" in tx._events: - token = contract(tx.token.address.address) + elif tx.from_address.address in treasury.addresses and tx.to_address == ZERO_ADDRESS and "Withdrawn" in tx._events: + token = tx.token.contract if hasattr(token, 'pool'): pool = token.pool() for event in tx._events["Withdrawn"]: - if event.address == lp_depositor and 'user' in event and 'pool' in event and event['user'] == tx.from_address.address and event['pool'] == pool: + if event.address == lp_depositor and 'user' in event and 'pool' in event and tx.from_address == event['user'] and event['pool'] == pool: return True # Step 2: Unstake your tokens - elif tx.from_address.address == lp_depositor and tx.to_address and tx.to_address.address in treasury.addresses and "Withdrawn" in tx._events: + elif tx.from_address == lp_depositor and tx.to_address.address in treasury.addresses and "Withdrawn" in tx._events: for event in tx._events["Withdrawn"]: - if event.address == lp_depositor and 'user' in event and 'pool' in event and event['user'] == tx.to_address.address and event['pool'] == tx.token.address.address: + if event.address == lp_depositor and 'user' in event and 'pool' in event and tx.to_address == event['user'] and tx.token == event['pool']: return True return False diff --git a/yearn/treasury/accountant/ignore/swaps/aave.py b/yearn/treasury/accountant/ignore/swaps/aave.py index 5a49616fe..4a5b5e776 100644 --- a/yearn/treasury/accountant/ignore/swaps/aave.py +++ b/yearn/treasury/accountant/ignore/swaps/aave.py @@ -1,38 +1,36 @@ +from decimal import Decimal + from brownie import ZERO_ADDRESS from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import HashMatcher from yearn.treasury.accountant.constants import treasury -from yearn.utils import contract def is_aave_deposit(tx: TreasuryTx) -> bool: # Atoken side # Underlying side - pass + # TODO we didnt need this historically?? + return False def is_aave_withdrawal(tx: TreasuryTx) -> bool: # Atoken side - if tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == ZERO_ADDRESS and "RedeemUnderlying" in tx._events and hasattr(contract(tx.token.address.address), 'underlyingAssetAddress'): + if tx.from_address.address in treasury.addresses and tx.to_address == ZERO_ADDRESS and "RedeemUnderlying" in tx._events and hasattr(tx.token.contract, 'underlyingAssetAddress'): for event in tx._events['RedeemUnderlying']: if ( - event['_user'] == tx.from_address.address and - contract(tx.token.address.address).underlyingAssetAddress() == event['_reserve'] and - round(event['_amount'] / tx.token.scale, 15) == round(float(tx.amount), 15) + tx.from_address == event['_user'] and + tx.token.contract.underlyingAssetAddress() == event['_reserve'] and + Decimal(event['_amount']) / tx.token.scale == tx.amount ): return True # Underlying side - if tx.to_address and tx.to_address.address in treasury.addresses and "RedeemUnderlying" in tx._events: + if tx.to_address.address in treasury.addresses and "RedeemUnderlying" in tx._events: for event in tx._events['RedeemUnderlying']: - if ( - tx.token.address.address == event['_reserve'] and - event['_user'] == tx.to_address.address and - round(event['_amount'] / tx.token.scale, 15) == round(float(tx.amount), 15) - ): + if tx.token == event['_reserve'] and tx.to_address == event['_user'] and Decimal(event['_amount']) / tx.token.scale == tx.amount: return True # TODO: If these end up becoming more frequent, figure out sorting hueristics. diff --git a/yearn/treasury/accountant/ignore/swaps/buying_yfi.py b/yearn/treasury/accountant/ignore/swaps/buying_yfi.py index 8e27c8256..51ef2d6e2 100644 --- a/yearn/treasury/accountant/ignore/swaps/buying_yfi.py +++ b/yearn/treasury/accountant/ignore/swaps/buying_yfi.py @@ -1,5 +1,8 @@ +from decimal import Decimal + from brownie import chain +from y.constants import WRAPPED_GAS_COIN from y.networks import Network from yearn.constants import YCHAD_MULTISIG @@ -29,27 +32,46 @@ ] def is_buyer_top_up(tx: TreasuryTx) -> bool: - if chain.id == Network.Mainnet and tx._symbol == "DAI" and tx.to_address and tx.to_address.address in VYPER_BUYERS: - return True + return chain.id == Network.Mainnet and tx._symbol == "DAI" and tx.to_address in VYPER_BUYERS def is_buying_with_buyer(tx: TreasuryTx) -> bool: - if chain.id == Network.Mainnet and tx._symbol == "YFI" and tx.to_address and tx.to_address.address == YCHAD_MULTISIG: + if chain.id == Network.Mainnet and tx._symbol == "YFI" and tx.to_address == YCHAD_MULTISIG: event_args = {"buyer", "yfi", "dai"} if "Buyback" in tx._events and all(arg in tx._events["Buyback"] for arg in event_args): - amount_from_chain = round(tx._events["Buyback"]["yfi"] / 1e18, 15) - amount_from_db = round(float(tx.amount), 15) - if amount_from_chain == amount_from_db: + if tx.amount == tx._events["Buyback"]["yfi"] / Decimal(10**18): + return True + # These dont match due to decimal <> float discrepancy. Later we will use decimals only. For now this works. + # NOTE it also seems like buybacks made via a gnosis safe may have issues too. Potential to automate these in the future. + # NOTE: i think we can delete this extra elif now that the changes on line 43 have been made. test. + elif tx in HashMatcher([ + "0xced6de44e15b827b39cc365de3b319a30d0d23dd8d10e2d9860948d4038e178c", + "0xa8832faf0146d7ccaac7558b86042aa79cb6fdebd6d8988ec6f5c7533066ded9", + "0x25364b36e6dbb74db53d5aef7dfdad3c879da19743456260e8a89a3e8ad3d9ae", + "0xdbcee8ca9beba7ec800b0b2f289358bf42fd0c1e2ea5d7650de74a5127813247", + "0x5b950f3a341e38fe22a82d5040a9a3161b3c8d09b70f06ed1671495b02b01d6e", + "0x4c797977155093a80265a167256575578cef4b34f54e4003c88d0e28c69df215", + ]): + return True + raise ValueError(f'from node: {tx._events["Buyback"]["yfi"] / Decimal(10**18)} from db: {tx.amount} diff: {tx._events["Buyback"]["yfi"] / Decimal(10**18) - tx.amount}') + return False + +YFI_BUYBACK_AUCTIONS = "0x4349ed200029e6Cf38F1455B9dA88981F1806df3" + +def is_buying_with_auction(tx: TreasuryTx) -> bool: + if tx._symbol != 'YFI' or tx.to_address != YCHAD_MULTISIG or "AuctionTaken" not in tx._events: + return False + auctions_taken = tx._events['AuctionTaken'] + if len(auctions_taken) > 1: + raise NotImplementedError + event = auctions_taken[0] + if event.address != YFI_BUYBACK_AUCTIONS: + raise ValueError(event.address, event) + # did the auction contract send weth to tx.sender? + for transfer in tx._events['Transfer']: + if transfer.address == WRAPPED_GAS_COIN: + sender, receiver, amount = transfer.values() + if sender == YFI_BUYBACK_AUCTIONS and tx.from_address == receiver and amount == event['taken']: return True - else: - # These dont match due to decimal <> float discrepancy. Later we will use decimals only. For now this works. - # NOTE it also seems like buybacks made via a gnosis safe may have issues too. Potential to automate these in the future. - if tx in HashMatcher([ - "0xced6de44e15b827b39cc365de3b319a30d0d23dd8d10e2d9860948d4038e178c", - "0xa8832faf0146d7ccaac7558b86042aa79cb6fdebd6d8988ec6f5c7533066ded9", - "0x25364b36e6dbb74db53d5aef7dfdad3c879da19743456260e8a89a3e8ad3d9ae", - "0xdbcee8ca9beba7ec800b0b2f289358bf42fd0c1e2ea5d7650de74a5127813247", - "0x5b950f3a341e38fe22a82d5040a9a3161b3c8d09b70f06ed1671495b02b01d6e", - "0x4c797977155093a80265a167256575578cef4b34f54e4003c88d0e28c69df215", - ]): - return True - raise ValueError(f'from node: {amount_from_chain} from db: {amount_from_db} diff: {amount_from_chain - amount_from_db}') + print(f"AuctionTaken: {event}") + print(f"Transfer: {transfer}") + return False diff --git a/yearn/treasury/accountant/ignore/swaps/compound.py b/yearn/treasury/accountant/ignore/swaps/compound.py index b434c79eb..00fdbe10e 100644 --- a/yearn/treasury/accountant/ignore/swaps/compound.py +++ b/yearn/treasury/accountant/ignore/swaps/compound.py @@ -1,4 +1,6 @@ +from decimal import Decimal + from yearn.entities import TreasuryTx @@ -7,19 +9,21 @@ def is_compound_deposit(tx: TreasuryTx) -> bool: for event in tx._events["Mint"]: if all(arg in event for arg in {'minter', 'mintTokens', 'mintAmount'}): # cToken side - if event.address == tx.token.address.address == tx.from_address.address and tx.to_address and tx.to_address.address == event['minter'] and round(event['mintTokens']/tx.token.scale, 15) == round(float(tx.amount), 15): + if tx.token == tx.from_address == event.address and tx.to_address == event['minter'] and Decimal(event['mintTokens']) / tx.token.scale == tx.amount: return True # underlying side - elif tx.to_address and tx.to_address.address == event.address and tx.from_address.address == event['minter'] and round(event['mintAmount']/tx.token.scale, 15) == round(float(tx.amount), 15): + elif tx.to_address == event.address and tx.from_address == event['minter'] and Decimal(event['mintAmount']) / tx.token.scale == tx.amount: return True + return False def is_compound_withdrawal(tx: TreasuryTx) -> bool: if "Redeem" in tx._events: for event in tx._events['Redeem']: if all(arg in event for arg in {'redeemer', 'redeemTokens', 'redeemAmount'}): # cToken side - if event.address == tx.token.address.address and tx.from_address.address == event['redeemer'] and round(event['redeemTokens']/tx.token.scale, 15) == round(float(tx.amount), 15): + if event.address == tx.token and tx.from_address == event['redeemer'] and Decimal(event['redeemTokens']) / tx.token.scale == tx.amount: return True # underlying side - elif tx.to_address and tx.to_address.address == event['redeemer'] and tx.from_address.address == event.address and round(event['redeemAmount']/tx.token.scale, 15) == round(float(tx.amount), 15): + elif tx.to_address == event['redeemer'] and tx.from_address == event.address and Decimal(event['redeemAmount']) / tx.token.scale == tx.amount: return True + return False diff --git a/yearn/treasury/accountant/ignore/swaps/cowswap.py b/yearn/treasury/accountant/ignore/swaps/cowswap.py index e314f740e..729645620 100644 --- a/yearn/treasury/accountant/ignore/swaps/cowswap.py +++ b/yearn/treasury/accountant/ignore/swaps/cowswap.py @@ -1,4 +1,6 @@ +from decimal import Decimal + from pony.orm import select from yearn.entities import TreasuryTx @@ -17,11 +19,10 @@ def is_cowswap_swap(tx: TreasuryTx) -> bool: for trade in tx._events["Trade"]: if trade.address == YSWAPS and trade["owner"] in treasury.addresses and trade['buyToken'] not in SKIP_TOKENS: # buy side - if tx.token.address.address == trade["buyToken"] and tx.to_address.address in treasury.addresses and round(float(tx.amount), 15) == round(trade['buyAmount'] / tx.token.scale, 15): + if tx.token == trade["buyToken"] and tx.to_address.address in treasury.addresses and tx.amount == Decimal(trade['buyAmount']) / tx.token.scale: return True - # sell side - elif tx.token.address.address == trade["sellToken"] and trade['owner'] == tx.from_address.address and round(float(tx.amount), 15) == round(trade['sellAmount'] / tx.token.scale, 15): + elif tx.token == trade["sellToken"] and tx.from_address == trade['owner'] and tx.amount == Decimal(trade['sellAmount']) / tx.token.scale: # Did Yearn actually receive the other side of the trade? for address in treasury.addresses: other_side_query = select( @@ -29,12 +30,25 @@ def is_cowswap_swap(tx: TreasuryTx) -> bool: if t.hash == tx.hash and t.token.address.address == trade['buyToken'] and t.from_address.address == YSWAPS - and t.to_address.address == address + and t.to_address.address == address.address ) if len(other_side_query) > 0: return True + # made with some help return tx in HashMatcher([ "0xd41e40a0e9b49c4f06e1956066006de901a4ed8c856a43c31ac1cbd344ff0ccf", + "0x94e1a85fa25e433976962449588e522ce0f2a81ae3d4b67ae199e458dfce4e39", + "0x579056f777c1f05a18e566dd329ea85f5c747fd6e7246411b5e016c8bebe8742", + "0xd007d04560fc42df93da0fd25ac3942f89f7f5458eb492872b3d87be91d7a571", + "0x2eb9a897ea48c9802f0129674d0203835a236e2f41c6db8edb017a4c315b84f4", + "0xce2338b61c8c5875ce4e19f9d5993895a4d5d9eb81b78e1b33279436d2c2c047", + "0x6dd14144594e7b19fdc4529682eb1cf554132ae318d8ba5b238cdeb3e694d52a", + "0xb0d430e1ec4d6fa7bd4e56f86e6fefd7f71946549710ca0b9f39de14c04d02ed", + "0x0b1b00f0a29f787b44421461a3f9444081fe94ca29958c98cd2c23e271f3f69a", + "0x69eeeb5d7cb0f23ee98ef23f38d02ff0a695d75a1da1fa010abaa7a5aab947ff", + "0xdfbe76c0af4ec68021c1603d4f9a6b5f20734e70af86d32fbd57fa87461064f2", + "0x60cfe7c2c19e1fc3010e441ed369fc414bd10dbb7ba88fb4f490d96e15eb2f26", + "0x1ddddfff1f02c66ef07817144e9e30d6f734432c2736f7859236d3199d4b23fb", ]) \ No newline at end of file diff --git a/yearn/treasury/accountant/ignore/swaps/curve.py b/yearn/treasury/accountant/ignore/swaps/curve.py index 35254924e..cd0c58931 100644 --- a/yearn/treasury/accountant/ignore/swaps/curve.py +++ b/yearn/treasury/accountant/ignore/swaps/curve.py @@ -1,67 +1,61 @@ +from decimal import Decimal + from brownie import ZERO_ADDRESS, chain -from y.networks import Network +from y import Contract, Network from yearn.entities import TreasuryTx from yearn.multicall2 import fetch_multicall from yearn.treasury.accountant.classes import Filter, HashMatcher, IterFilter from yearn.treasury.accountant.constants import treasury -from yearn.utils import contract +# curve helpers +_scale = lambda amount, tx: Decimal(amount) / tx.token.scale +_is_old_style = lambda tx, pool: hasattr(pool, 'lp_token') and tx.token == pool.lp_token() +_is_new_style = lambda tx, pool: hasattr(pool, 'totalSupply') and tx.token == pool.address +def _token_is_curvey(tx: TreasuryTx) -> bool: + return 'crv' in tx._symbol.lower() or 'curve' in tx.token.name.lower() + def is_curve_deposit(tx: TreasuryTx) -> bool: if 'AddLiquidity' in tx._events: for event in tx._events['AddLiquidity']: - pool = contract(event.address) # LP Token Side - if tx.from_address.address == ZERO_ADDRESS and ('crv' in tx._symbol.lower() or 'curve' in tx.token.name.lower()) and tx.to_address and tx.to_address.address in treasury.addresses and ((hasattr(pool, 'lp_token') and pool.lp_token() == tx.token.address.address) or (hasattr(pool, 'totalSupply') and pool.address == tx.token.address.address)): - return True + if tx.from_address == ZERO_ADDRESS and _token_is_curvey(tx): + pool = Contract(event.address) + if _is_old_style(tx, pool) or _is_new_style(tx, pool): + return True # Tokens sent - elif tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == event.address: - print(event) + elif tx.to_address == event.address: + print(f"AddLiquidity: {event}") for i, amount in enumerate(event["token_amounts"]): - if tx.token.address.address == pool.coins(i) and round(float(tx.amount), 15) == round(amount/tx.token.scale, 15): - return True + if tx.amount == _scale(amount, tx): + pool = Contract(event.address) + if tx.token == pool.coins(i): + return True # What if a 3crv deposit was needed before the real deposit? - elif tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == "0xA79828DF1850E8a3A3064576f380D90aECDD3359" and event.address == "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7": - print(event) + elif tx.from_address.address in treasury.addresses and tx.to_address == "0xA79828DF1850E8a3A3064576f380D90aECDD3359" and event.address == "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7": + print(f"AddLiquidity-3crv: {event}") for i, amount in enumerate(event["token_amounts"]): - if tx.token.address.address == pool.coins(i) and round(float(tx.amount), 15) == round(amount/tx.token.scale, 15): - return True + if tx.amount == _scale(amount, tx): + pool = Contract(event.address) + if tx.token == pool.coins(i): + return True + # TODO: see if we can remove these with latest hueristics return tx in HashMatcher([ "0x567d2ebc1a336185950432b8f8b010e1116936f9e6c061634f5aba65bdb1e188", "0x17e2d7a40697204b3e726d40725082fec5f152f65f400df850f13ef4a4f6c827", ]) + def is_curve_withdrawal(tx: TreasuryTx) -> bool: - if 'RemoveLiquidityOne' in tx._events: - for event in tx._events['RemoveLiquidityOne']: - # LP Token Side - if tx.to_address and tx.to_address.address == ZERO_ADDRESS and ('crv' in tx._symbol.lower() or 'curve' in tx.token.name.lower()) and round(float(tx.amount), 15) == round(event['token_amount'] / tx.token.scale, 15): - return True - - # Tokens rec'd - elif tx.from_address.address == event.address and tx.to_address and tx.to_address.address in treasury.addresses and round(float(tx.amount), 15) == round(event['coin_amount'] / tx.token.scale, 15): - return True - - if 'RemoveLiquidity' in tx._events: - for event in tx._events['RemoveLiquidity']: - pool = contract(event.address) - # LP Token side - if tx.to_address and tx.to_address.address == ZERO_ADDRESS and ('crv' in tx._symbol.lower() or 'curve' in tx.token.name.lower()) and ((hasattr(pool, 'lp_token') and pool.lp_token() == tx.token.address.address) or (hasattr(pool, 'totalSupply') and pool.address == tx.token.address.address)): - return True - - # Tokens rec'd - elif tx.from_address.address == event.address and tx.to_address and tx.to_address.address in treasury.addresses: - for i, amount in enumerate(event['token_amounts']): - if tx.token.address.address == pool.coins(i) and round(amount/tx.token.scale, 15) == round(float(tx.amount), 15): - return True - if hasattr(pool, 'underlying_coins') and tx.token.address.address == pool.underlying_coins(i) and round(amount/tx.token.scale, 15) == round(float(tx.amount), 15): - return True + if is_curve_withdrawal_one(tx) or is_curve_withdrawal_multi(tx): + return True + # TODO check which of these still need huersitcs and remove the rest # TODO establish hueristics for automagically sorting these hashes = { Network.Mainnet: [ @@ -76,22 +70,60 @@ def is_curve_withdrawal(tx: TreasuryTx) -> bool: ["0xc14c29fd2bf495bd27c8eb862b34a98eb34dec8e533046fc6278eb41b342cfce", IterFilter('log_index',[430,437])], ["0xb760d18e25c47c89303772f8b0fbb267a1f2c8a1db71a08c9846b065d8e707a1", IterFilter('log_index',[163,170])], "0x5d0be661f67d39999b8107e1ecb3eb3e9c8eceefbd7002da0fa1ea865f58324b", + "0x5956b391625f0121b18118f8e665c6668b36cf5f929fda6348971b57cbee6e55", ], }.get(chain.id, []) return tx in HashMatcher(hashes) + +def is_curve_withdrawal_one(tx: TreasuryTx) -> bool: + if 'RemoveLiquidityOne' not in tx._events: + return False + for event in tx._events['RemoveLiquidityOne']: + # LP Token Side + if tx.to_address == ZERO_ADDRESS and _token_is_curvey(tx) and tx.amount == _scale(event['token_amount'], tx): + return True + # Tokens rec'd + elif tx.from_address == event.address and tx.amount == _scale(event['coin_amount'], tx): + return True + return False + +def is_curve_withdrawal_multi(tx: TreasuryTx) -> bool: + if 'RemoveLiquidity' not in tx._events: + return False + for event in tx._events['RemoveLiquidity']: + # LP Token side + if tx.to_address == ZERO_ADDRESS and _token_is_curvey(tx): + pool = Contract(event.address) + if _is_old_style(tx, pool) or _is_new_style(tx, pool): + return True + print(f'wtf is this: {tx}') + # Tokens rec'd + elif tx.from_address == event.address and tx.to_address.address in treasury.addresses: + for i, amount in enumerate(event['token_amounts']): + if tx.amount == _scale(amount, tx): + pool = Contract(event.address) + check_method = getattr(pool, 'underlying_coins', pool.coins) + return tx.token == check_method(i) + return False + +def _exchange_shaped_correctly(exchange_event) -> bool: + keys = {"buyer","sold_id","tokens_sold","bought_id","tokens_bought"} + return all(key in exchange_event for key in keys) + def is_curve_swap(tx: TreasuryTx) -> bool: if "TokenExchange" in tx._events: - for event in tx._events["TokenExchange"]: - if all(arg in event for arg in {"buyer","sold_id","tokens_sold","bought_id","tokens_bought"}): - pool = contract(event.address) - buy_token, sell_token = fetch_multicall([pool, 'coins', event['bought_id']], [pool, 'coins', event['sold_id']]) - # Sell side - if tx.from_address.address == event['buyer'] and tx.to_address and tx.to_address.address == event.address and tx.token.address.address == sell_token and round(float(tx.amount), 15) == round(event['tokens_sold']/tx.token.scale, 15): - return True - # Buy side - elif tx.from_address.address == event.address and tx.to_address and tx.to_address.address == event['buyer'] and tx.token.address.address == buy_token and round(float(tx.amount), 15) == round(event['tokens_bought']/tx.token.scale, 15): - return True + for exchange in tx._events["TokenExchange"]: + if not _exchange_shaped_correctly(exchange): + continue + pool = Contract(exchange.address) + buy_token, sell_token = fetch_multicall([pool, 'coins', exchange['bought_id']], [pool, 'coins', exchange['sold_id']]) + # Sell side + if tx.from_address == exchange['buyer'] and tx.to_address == exchange.address and tx.token == sell_token and tx.amount == _scale(exchange['tokens_sold'], tx): + return True + # Buy side + elif tx.from_address == exchange.address and tx.to_address == exchange['buyer'] and tx.token == buy_token and tx.amount == _scale(exchange['tokens_bought'], tx): + return True return tx in HashMatcher([ ["0xc14c29fd2bf495bd27c8eb862b34a98eb34dec8e533046fc6278eb41b342cfce", IterFilter('log_index', [439,443])], diff --git a/yearn/treasury/accountant/ignore/swaps/robovault.py b/yearn/treasury/accountant/ignore/swaps/robovault.py index a34e4156e..c6bc65d4c 100644 --- a/yearn/treasury/accountant/ignore/swaps/robovault.py +++ b/yearn/treasury/accountant/ignore/swaps/robovault.py @@ -1,37 +1,38 @@ from brownie import ZERO_ADDRESS + from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import HashMatcher, IterFilter from yearn.treasury.accountant.constants import treasury -from yearn.utils import contract def is_reaper_withdrawal(tx: TreasuryTx) -> bool: # vault side - if tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == ZERO_ADDRESS and tx._symbol.startswith('rv'): + if tx.from_address.address in treasury.addresses and tx.to_address == ZERO_ADDRESS and tx._symbol.startswith('rv'): events = tx._events if 'Transfer' not in events: return False # this code was commented out for some reason, I think its fine but leaving this msg jsut in case for event in events['Transfer']: - sender, receiver, value = event.values() - if event.address == tx.token.address.address and receiver == ZERO_ADDRESS: - underlying = contract(tx.token.address.address).token() - for _event in events['Transfer']: - _sender, _receiver, _value = _event.values() - if _event.address == underlying and _receiver == tx.from_address.address and event.pos < _event.pos and _sender == tx.token.address.address: - return True + if tx.token == event.address: + sender, receiver, value = event.values() + if receiver == ZERO_ADDRESS: + underlying = tx.token.contract.token() + for _event in events['Transfer']: + _sender, _receiver, _value = _event.values() + if _event.address == underlying and tx.from_address == _receiver and event.pos < _event.pos and tx.token == _sender: + return True # token side if tx.from_address.token and "Robovault" in tx.from_address.nickname: try: - vault = contract(tx.from_address.address) + vault = tx.from_address.contract except ValueError as e: if "not verified" in str(e).lower(): return False raise - if vault.token() == tx.token.address.address: + if tx.token == vault.token(): return True # DEV: why didn't these match? @@ -39,6 +40,4 @@ def is_reaper_withdrawal(tx: TreasuryTx) -> bool: ["0x84b64365e647e8c9c44b12819e8b7af02d5595933853c3da3eb43fc6f8ef3112",IterFilter('log_index',[8,12,16,74,78,82,86,73,7,11,77,81,15,85])], ["0xf68dee68d36eac87430f5238a520ae209650ddeea4b09ebe29af1b00623f1148",IterFilter('log_index',[19,27,7,26,11,23,15,22,14,2,6,10,18,3])] ] - - if HashMatcher(hashes).contains(tx): - return True + return HashMatcher(hashes).contains(tx) diff --git a/yearn/treasury/accountant/ignore/swaps/uniswap.py b/yearn/treasury/accountant/ignore/swaps/uniswap.py index 3d1370f26..cdd167985 100644 --- a/yearn/treasury/accountant/ignore/swaps/uniswap.py +++ b/yearn/treasury/accountant/ignore/swaps/uniswap.py @@ -1,4 +1,6 @@ +from decimal import Decimal + from brownie import ZERO_ADDRESS, chain from y import Network, Contract @@ -17,49 +19,47 @@ def is_uniswap_deposit(tx: TreasuryTx) -> bool: if tx.to_address and "Mint" in tx._events and "Transfer" in tx._events: for mint in tx._events["Mint"]: event_args = {"sender", "amount0", "amount1"} - if not all(arg in mint for arg in event_args): + if any(arg not in mint for arg in event_args): continue # LP token - if tx.from_address.address == ZERO_ADDRESS and ( - tx.token.address.address == mint.address or + if tx.from_address == ZERO_ADDRESS and ( + tx.token == mint.address or # KP3R/WETH Uni v3 LP -- used while depositing to kLP-KP3R/WETH mint.address == "0x11B7a6bc0259ed6Cf9DB8F499988F9eCc7167bf5" ): - tokens = [ - Contract(tx.token.address.address).token0(), - Contract(tx.token.address.address).token1(), - ] + lp = tx.token.contract + tokens = [lp.token0(), lp.token1()] if all( - any(token == transfer.address and transfer.values()[0] == tx.to_address.address and transfer.values()[1] == mint.address for transfer in tx._events["Transfer"]) + any(token == transfer.address and tx.to_address == transfer.values()[0] and transfer.values()[1] == mint.address for transfer in tx._events["Transfer"]) for token in tokens ): return True - + # Maybe native asset was used instead of wrapped. if tokens[0] == WRAPPED_GAS_COIN: - if any(tokens[1] == transfer.address and transfer.values()[0] == tx.to_address.address == mint['sender'] and transfer.values()[1] == mint.address for transfer in tx._events["Transfer"]): + if any(tokens[1] == transfer.address and tx.to_address == transfer.values()[0] == mint['sender'] and transfer.values()[1] == mint.address for transfer in tx._events["Transfer"]): for int_tx in chain.get_transaction(tx.hash).internal_transfers: - if int_tx['from'] == tx.to_address.address == mint['sender'] and int_tx['to'] in ROUTERS: + if tx.to_address == int_tx['from'] == mint['sender'] and int_tx['to'] in ROUTERS: for transfer in tx._events['Transfer']: - if transfer[0] == WRAPPED_GAS_COIN == transfer.address and transfer[1] == tx.token.address.address and transfer[2] == int_tx['value']: + if transfer[0] == WRAPPED_GAS_COIN == transfer.address and tx.token == transfer[1] and transfer[2] == int_tx['value']: return True - + elif tokens[1] == WRAPPED_GAS_COIN: - if any(tokens[0] == transfer.address and transfer.values()[0] == tx.to_address.address == mint['sender'] and transfer.values()[1] == mint.address for transfer in tx._events["Transfer"]): + if any(tokens[0] == transfer.address and tx.to_address == transfer.values()[0] == mint['sender'] and transfer.values()[1] == mint.address for transfer in tx._events["Transfer"]): for int_tx in chain.get_transaction(tx.hash).internal_transfers: - if int_tx['from'] == tx.to_address.address == mint['sender'] and int_tx['to'] in ROUTERS: + if tx.to_address == int_tx['from'] == mint['sender'] and int_tx['to'] in ROUTERS: for transfer in tx._events['Transfer']: - if transfer[0] == WRAPPED_GAS_COIN == transfer.address and transfer[1] == tx.token.address.address and transfer[2] == int_tx['value']: + if transfer[0] == WRAPPED_GAS_COIN == transfer.address and tx.token == transfer[1] and transfer[2] == int_tx['value']: return True else: - print(tokens) + print(f"tokens: {tokens}") # Component tokens - elif tx.to_address.address == mint.address: + elif tx.to_address == mint.address: return True - + return tx in HashMatcher({ Network.Mainnet: [ "0x3a000d3aa5d0d83a3ff359de261bfcecdc62cd13500b8ab517802742ac918627", # uni v3 @@ -70,43 +70,56 @@ def is_uniswap_withdrawal(tx: TreasuryTx) -> bool: if tx.to_address and "Burn" in tx._events and "Transfer" in tx._events: for burn in tx._events["Burn"]: event_args = {"sender", "amount0", "amount1","to"} - if not all(arg in burn for arg in event_args): + if any(arg not in burn for arg in event_args): continue # LP token - if tx.from_address.address in treasury.addresses and tx.from_address.address == burn['to'] and tx.token.address.address == burn.address == tx.to_address.address: - tokens = [ - Contract(tx.token.address.address).token0(), - Contract(tx.token.address.address).token1(), - ] - if all( - any(token == transfer.address and transfer.values()[0] == tx.token.address.address == tx.to_address.address and transfer.values()[1] == tx.from_address.address == burn['to'] for transfer in tx._events["Transfer"]) + if tx.from_address.address in treasury.addresses and tx.from_address == burn['to'] and tx.token == tx.to_address == burn.address: + lp = tx.token.contract + tokens = [lp.token0(), lp.token1()] + if tx.token == tx.to_address and all( + any( + token == transfer.address + and tx.to_address == transfer.values()[0] + and tx.from_address == transfer.values()[1] == burn['to'] + for transfer in tx._events["Transfer"] + ) for token in tokens ): return True - + # Maybe native asset was used instead of wrapped. if tokens[0] == WRAPPED_GAS_COIN: - if any(tokens[1] == transfer.address and transfer.values()[0] == tx.token.address.address == tx.to_address.address and transfer.values()[1] == tx.from_address.address == burn['to'] for transfer in tx._events["Transfer"]): + if any( + tokens[1] == transfer.address + and tx.token == tx.to_address == transfer.values()[0] + and tx.from_address == transfer.values()[1] == burn['to'] + for transfer in tx._events["Transfer"] + ): for int_tx in chain.get_transaction(tx.hash).internal_transfers: - if int_tx['from'] in ROUTERS and int_tx['to'] == tx.from_address.address: + if int_tx['from'] in ROUTERS and tx.from_address == int_tx['to']: for transfer in tx._events['Transfer']: - if transfer[0] == tx.token.address.address and transfer[1] == transfer.address == WRAPPED_GAS_COIN and transfer[2] == int_tx['value']: + if tx.token == transfer[0] and transfer[1] == transfer.address == WRAPPED_GAS_COIN and transfer[2] == int_tx['value']: return True elif tokens[1] == WRAPPED_GAS_COIN: - if any(tokens[0] == transfer.address and transfer.values()[0] == tx.token.address.address == tx.to_address.address and transfer.values()[1] == tx.from_address.address == burn['to'] for transfer in tx._events["Transfer"]): + if any( + tokens[0] == transfer.address + and tx.token == tx.to_address == transfer.values()[0] + and tx.from_address == transfer.values()[1] == burn['to'] + for transfer in tx._events["Transfer"] + ): for int_tx in chain.get_transaction(tx.hash).internal_transfers: - if int_tx['from'] in ROUTERS and int_tx['to'] == tx.from_address.address: + if int_tx['from'] in ROUTERS and tx.from_address == int_tx['to']: for transfer in tx._events['Transfer']: - if transfer[0] == tx.token.address.address and transfer[1] == transfer.address == WRAPPED_GAS_COIN and transfer[2] == int_tx['value']: + if transfer[0] == tx.token and transfer[1] == transfer.address == WRAPPED_GAS_COIN and transfer[2] == int_tx['value']: return True else: - print(tokens) + print(f"tokens: {tokens}") # Component tokens - elif tx.from_address.address == burn.address: + elif tx.from_address == burn.address: return True return tx in HashMatcher({ Network.Mainnet: [ @@ -121,7 +134,7 @@ def is_uniswap_swap(tx: TreasuryTx) -> bool: if tx.from_address.address in treasury.addresses and tx._to_nickname == "Non-Verified Contract: 0xa66901D1965F5410dEeB4d0Bb43f7c1B628Cb20b" and tx._symbol == "SOLIDsex": return True # Buy side - elif tx._from_nickname == "Non-Verified Contract: 0xa66901D1965F5410dEeB4d0Bb43f7c1B628Cb20b" and tx.to_address and tx.to_address.address in treasury.addresses and tx._symbol == "WFTM": + elif tx._from_nickname == "Non-Verified Contract: 0xa66901D1965F5410dEeB4d0Bb43f7c1B628Cb20b" and tx.to_address.address in treasury.addresses and tx._symbol == "WFTM": return True elif tx in HashMatcher( @@ -134,10 +147,10 @@ def is_uniswap_swap(tx: TreasuryTx) -> bool: return True # All other swaps - elif "Swap" in tx._events: # and and tx.to_address and tx.to_address.address in treasury.addresses: + elif "Swap" in tx._events: for swap in tx._events["Swap"]: # Sell side - if tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == swap.address: + if tx.from_address.address in treasury.addresses and tx.to_address == swap.address: pool = Contract(swap.address) token0, token1 = fetch_multicall([pool,'token0'],[pool,'token1']) if token0 in SKIP_TOKENS or token1 in SKIP_TOKENS: @@ -148,23 +161,24 @@ def is_uniswap_swap(tx: TreasuryTx) -> bool: if 'sqrtPriceX96' in swap: continue - if tx.token.address.address == token0: - if round(swap['amount0In'] / tx.token.scale, 15) == round(float(tx.amount), 15): + if tx.token == token0: + if Decimal(swap['amount0In']) / tx.token.scale == tx.amount: return True - elif tx.token.address.address == token1: - if round(swap['amount1In'] / tx.token.scale, 15) == round(float(tx.amount), 15): + elif tx.token == token1: + if Decimal(swap['amount1In']) / tx.token.scale == tx.amount: return True # Buy side - elif tx.from_address.address == swap.address and tx.to_address and tx.to_address.address in treasury.addresses: + elif tx.from_address == swap.address and tx.to_address.address in treasury.addresses: pool = Contract(swap.address) token0, token1 = fetch_multicall([pool,'token0'],[pool,'token1']) if token0 in SKIP_TOKENS or token1 in SKIP_TOKENS: # This will be recorded elsewhere continue - if tx.token.address.address == token0: - if round(swap['amount0Out'] / tx.token.scale, 15) == round(float(tx.amount), 15): + if tx.token == token0: + if Decimal(swap['amount0Out']) / tx.token.scale == tx.amount: return True - elif tx.token.address.address == token1: - if round(swap['amount1Out'] / tx.token.scale, 15) == round(float(tx.amount), 15): + elif tx.token == token1: + if Decimal(swap['amount1Out']) / tx.token.scale == tx.amount: return True + return False diff --git a/yearn/treasury/accountant/ignore/swaps/unwrapper.py b/yearn/treasury/accountant/ignore/swaps/unwrapper.py index 4009bc0ee..87d6a1b91 100644 --- a/yearn/treasury/accountant/ignore/swaps/unwrapper.py +++ b/yearn/treasury/accountant/ignore/swaps/unwrapper.py @@ -3,5 +3,4 @@ def is_unwrapper(tx: TreasuryTx) -> bool: - if "Contract: Unwrapper" in [tx._from_nickname, tx._to_nickname]: - return True + return "Contract: Unwrapper" in [tx._from_nickname, tx._to_nickname] diff --git a/yearn/treasury/accountant/ignore/swaps/woofy.py b/yearn/treasury/accountant/ignore/swaps/woofy.py index fdeb90db8..b8b1988f0 100644 --- a/yearn/treasury/accountant/ignore/swaps/woofy.py +++ b/yearn/treasury/accountant/ignore/swaps/woofy.py @@ -13,41 +13,42 @@ def is_woofy(tx: TreasuryTx) -> bool: """ Returns True if the tx involved wrapping or unwrapping WOOFY. """ # Wrapping, YFI side - if tx.to_address and tx.to_address.address == WOOFY and tx._symbol == "YFI" and "Transfer" in tx._events: + if tx.to_address == WOOFY and tx._symbol == "YFI" and "Transfer" in tx._events: # Check for WOOFY transfer for transfer in tx._events["Transfer"]: if transfer.address != WOOFY: continue sender, receiver, amount = transfer.values() - if sender == ZERO_ADDRESS and receiver == tx.from_address.address and amount / YFI_SCALE == tx.amount: + if sender == ZERO_ADDRESS and tx.from_address == receiver and Decimal(amount) / YFI_SCALE == tx.amount: return True # Wrapping, WOOFY side - elif tx.from_address.address == ZERO_ADDRESS and tx._symbol == "WOOFY" and "Transfer" in tx._events: + elif tx.from_address == ZERO_ADDRESS and tx._symbol == "WOOFY" and "Transfer" in tx._events: # Check for YFI transfer for transfer in tx._events["Transfer"]: if transfer.address != YFI: continue sender, receiver, amount = transfer.values() - if receiver == WOOFY and sender == tx.to_address.address and amount / WOOFY_SCALE == tx.amount: + if receiver == WOOFY and tx.to_address == sender and Decimal(amount) / WOOFY_SCALE == tx.amount: return True # Unwrapping, YFI side - elif tx.from_address.address == WOOFY and tx._symbol == "YFI" and "Transfer" in tx._events: + elif tx.from_address == WOOFY and tx._symbol == "YFI" and "Transfer" in tx._events: # Check for WOOFY transfer for transfer in tx._events["Transfer"]: if transfer.address != WOOFY: continue sender, receiver, amount = transfer.values() - if sender == tx.to_address.address and receiver == ZERO_ADDRESS and amount / YFI_SCALE == tx.amount: + if tx.to_address == sender and receiver == ZERO_ADDRESS and Decimal(amount) / YFI_SCALE == tx.amount: return True # Unwrapping, WOOFY side - elif tx.to_address and tx.to_address.address == ZERO_ADDRESS and tx._symbol == "WOOFY" and "Transfer" in tx._events: + elif tx.to_address == ZERO_ADDRESS and tx._symbol == "WOOFY" and "Transfer" in tx._events: # Check for YFI transfer for transfer in tx._events["Transfer"]: if transfer.address != YFI: continue sender, receiver, amount = transfer.values() - if sender == WOOFY and receiver == tx.from_address.address and amount / WOOFY_SCALE == tx.amount: + if sender == WOOFY and tx.from_address == receiver and Decimal(amount) / WOOFY_SCALE == tx.amount: return True + return False diff --git a/yearn/treasury/accountant/ignore/swaps/ycrv.py b/yearn/treasury/accountant/ignore/swaps/ycrv.py index d4e692175..35af517d1 100644 --- a/yearn/treasury/accountant/ignore/swaps/ycrv.py +++ b/yearn/treasury/accountant/ignore/swaps/ycrv.py @@ -13,28 +13,30 @@ def is_minting_ycrv(tx: TreasuryTx) -> bool: new_wrappers = ["st-yCRV","lp-yCRV"] # Zapping old-to-new # yCRV side - if tx.from_address.address == ZERO_ADDRESS and tx._symbol == "yCRV" and tx.to_address and tx.to_address.address in treasury.addresses: + if tx.from_address == ZERO_ADDRESS and tx._symbol == "yCRV" and tx.to_address.address in treasury.addresses: return True # Input side - elif tx._symbol in old_wrappers and tx.to_address and tx.to_address.address in [MINTER, ZAP, YCRV]: + elif tx._symbol in old_wrappers and tx.to_address in [MINTER, ZAP, YCRV]: return True # wrapping wrappers # wrapper side - elif tx._symbol in new_wrappers and tx.from_address.address == ZERO_ADDRESS: + elif tx._symbol in new_wrappers and tx.from_address == ZERO_ADDRESS: return True # yCRV side - elif tx._symbol == "yCRV" and tx.to_address.address == ZAP: + elif tx._symbol == "yCRV" and tx.to_address == ZAP: return True # unwrapping wrappers # wrapper side - elif tx._symbol in new_wrappers and tx.to_address.address == ZAP: + elif tx._symbol in new_wrappers and tx.to_address == ZAP: return True # yCRV side - elif tx._symbol == "yCRV" and tx.from_address.address == ZAP: + elif tx._symbol == "yCRV" and tx.from_address == ZAP: return True # NOTE: Crossing between wrappers is captured by the above logic as well + + return False diff --git a/yearn/treasury/accountant/ignore/swaps/yla.py b/yearn/treasury/accountant/ignore/swaps/yla.py index 29e4fd656..542cc94cf 100644 --- a/yearn/treasury/accountant/ignore/swaps/yla.py +++ b/yearn/treasury/accountant/ignore/swaps/yla.py @@ -3,5 +3,4 @@ def is_yla_withdrawal(tx: TreasuryTx) -> bool: - if tx.to_address: - return "0x85c6D6b0cd1383Cc85e8e36C09D0815dAf36b9E9" in (tx.from_address.address, tx.to_address.address) \ No newline at end of file + return bool(tx.to_address and "0x85c6D6b0cd1383Cc85e8e36C09D0815dAf36b9E9" in (tx.from_address.address, tx.to_address.address)) \ No newline at end of file diff --git a/yearn/treasury/accountant/ignore/vaults.py b/yearn/treasury/accountant/ignore/vaults.py index c682dd204..dc162f06e 100644 --- a/yearn/treasury/accountant/ignore/vaults.py +++ b/yearn/treasury/accountant/ignore/vaults.py @@ -1,77 +1,133 @@ +from decimal import Decimal + from brownie import ZERO_ADDRESS, chain -from y.networks import Network +from y import Network +from yearn.iearn import Registry from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import Filter, HashMatcher, IterFilter -from yearn.treasury.accountant.constants import treasury, v1, v2 -from yearn.utils import contract +from yearn.treasury.accountant.constants import treasury, v1 +from yearn.treasury.accountant.revenue.fees import v2_vaults -vaults = (v1.vaults + v2.vaults) if v1 else v2.vaults +vaults = (v1.vaults + v2_vaults) if v1 else v2_vaults +if chain.id == Network.Mainnet: + iearn = Registry().vaults def is_vault_deposit(tx: TreasuryTx) -> bool: + if is_v1_or_v2_vault_deposit(tx): + return True + + # TODO these go thru zaps and do not get caught by the logic above. figure out how to capture these + zap_hashes = { + Network.Mainnet: [ + "0x39616fdfc8851e10e17d955f55beea5c3dd4eed7c066a8ecbed8e50b496012ff", + "0x248e896eb732dfe40a0fa49131717bb7d2c1721743a2945ab9680787abcf9c50", + "0x2ce0240a08c8cc8d35b018995862711eb660a24d294b1aa674fbc467af4e621b", + ], + }.get(chain.id, []) + + # TODO Figure out hueristics for ETH deposit to yvWETH + # TODO build hueristics for v3 vaults - I did this, now just make sure these sort on a fresh pull + #if tx in HashMatcher({ + # Network.Mainnet: [ + # "0x6efac7fb65f187d9aa48d3ae42f3d3a2acdeed3e0b1ded2bb6967cf08c6548a4", + # "0xf37fe9e92366f215f97eb223571c0070f8a5195274658496fbc214083be43dbf", + # "0x0532f4fbc9b8b105b240f7a37084af6749d30ae03c540191bfb69019c036290c", + # ["0x7918a4121636fa06075a08dc1ab499f6e9b6a84f373499d68fc81e5e5cbd0878", Filter('log_index', 109)], + # ["0x7b156846ad28b651791dca800b17048afb1bd332078d0cd6507041048d859367", IterFilter('log_index', [473, 474])], + # ["0xd7e7abe600aad4a3181a3a410bef2539389579d2ed28f3e75dbbf3a7d8613688", IterFilter('log_index', [532, 533])], + # ["0x6c2debddbc13ca7ec2ae434e8f245c59b4286ce95048b57acf96d0e9253f4e8d", IterFilter('log_index', [285, 286])], + # ], + #}.get(chain.id, [])): + # return True + + return tx in HashMatcher(zap_hashes) or is_v3_vault_deposit(tx) + +def is_v1_or_v2_vault_deposit(tx: TreasuryTx) -> bool: """ This code doesn't validate amounts but so far that's not been a problem. """ - # vault side for vault in vaults: - if tx.token.address.address == vault.vault.address: + if tx.token == vault.vault.address: if "Transfer" not in tx._events: return False for event in tx._events["Transfer"]: - sender, receiver, value = event.values() - if event.address == tx.token.address.address and sender == ZERO_ADDRESS and receiver in treasury.addresses: - for _event in tx._events["Transfer"]: - _sender, _receiver, _value = _event.values() - if _event.address == vault.token.address and _sender == tx.to_address.address and _receiver == tx.token.address.address: - # v1 - if _event.pos < event.pos: - return True - # v2 - if event.pos < _event.pos: - return True + if tx.token == event.address: + sender, receiver, value = event.values() + if sender == ZERO_ADDRESS and receiver in treasury.addresses: + for _event in tx._events["Transfer"]: + _sender, _receiver, _value = _event.values() + if _event.address == vault.token.address and tx.to_address == _sender and tx.token == _receiver: + # v1 + if _event.pos < event.pos: + return True + # v2 + if event.pos < _event.pos: + return True # token side for vault in vaults: - if tx.token.address.address == vault.token.address: + if tx.token == vault.token.address: if "Transfer" not in tx._events: return False for event in tx._events["Transfer"]: - sender, receiver, value = event.values() - if event.address == tx.token.address.address and sender in treasury.addresses and receiver == vault.vault.address: - for _event in tx._events["Transfer"]: - _sender, _receiver, _value = _event.values() - if _event.address == vault.vault.address and _sender == ZERO_ADDRESS and _receiver in treasury.addresses: - # v1? - if event.pos < _event.pos: - return True - # v2 - if _event.pos < event.pos: - return True - - # DEV these go thru zaps and do not get caught by the logic above. figure out how to capture these - hashes = { - Network.Mainnet: [ - "0x39616fdfc8851e10e17d955f55beea5c3dd4eed7c066a8ecbed8e50b496012ff", - "0x248e896eb732dfe40a0fa49131717bb7d2c1721743a2945ab9680787abcf9c50", - "0x2ce0240a08c8cc8d35b018995862711eb660a24d294b1aa674fbc467af4e621b", - ], - }.get(chain.id, []) + if tx.token == event.address: + sender, receiver, value = event.values() + if sender in treasury.addresses and receiver == vault.vault.address: + for _event in tx._events["Transfer"]: + _sender, _receiver, _value = _event.values() + if _event.address == vault.vault.address and _sender == ZERO_ADDRESS and _receiver in treasury.addresses: + # v1? + if event.pos < _event.pos: + return True + # v2 + if _event.pos < event.pos: + return True + return False + +_v3_deposit_keys = 'sender', 'owner', 'assets', 'shares' + +def is_v3_vault_deposit(tx: TreasuryTx) -> bool: + if "Deposit" not in tx._events: + return False - if tx in HashMatcher(hashes): - return True + if deposits := [event for event in tx._events['Deposit'] if all(key in event for key in _v3_deposit_keys)]: + # Vault side + if tx.from_address == ZERO_ADDRESS: + for deposit in deposits: + if tx.token != deposit.address: + continue + if tx.to_address != deposit['owner']: + print('wrong owner') + continue + elif tx.amount == Decimal(deposit['shares']) / tx.token.scale: + return True + print('wrong amount') + print('no matching deposit') + + # Token side + else: + for deposit in deposits: + if tx.to_address != deposit.address: + continue + if tx.from_address != deposit['sender']: + print('sender doesnt match') + continue + if tx.amount == Decimal(deposit['assets']) / tx.token.scale: + return True + print('amount doesnt match') + return False + +def is_iearn_withdrawal(tx: TreasuryTx) -> bool: + # Vault side + if tx.to_address == ZERO_ADDRESS: + return any(tx.token == earn.vault.address for earn in iearn) + # Token side + return any(tx.from_address == earn.vault.address and tx.token == earn.token for earn in iearn) - # TODO Figure out hueristics for ETH deposit to yvWETH - return tx in HashMatcher({ - Network.Mainnet: [ - "0x6efac7fb65f187d9aa48d3ae42f3d3a2acdeed3e0b1ded2bb6967cf08c6548a4", - "0xf37fe9e92366f215f97eb223571c0070f8a5195274658496fbc214083be43dbf", - ], - }.get(chain.id, [])) - def is_vault_withdrawal(tx: TreasuryTx) -> bool: - # This is a strange tx that won't sort the usual way and isn't worth determining sorting hueristics for. if tx in HashMatcher({ Network.Mainnet: [ @@ -81,36 +137,39 @@ def is_vault_withdrawal(tx: TreasuryTx) -> bool: }.get(chain.id, [])): return True - if not tx.to_address or tx.to_address.address not in list(treasury.addresses) + [ZERO_ADDRESS]: + if tx.to_address not in list(treasury.addresses) + [ZERO_ADDRESS]: return False # vault side - if any(tx.token.address.address == vault.vault.address for vault in vaults): - if 'Transfer' in tx._events: - for event in tx._events['Transfer']: + if any(tx.token == vault.vault.address for vault in vaults) and 'Transfer' in tx._events: + for event in tx._events['Transfer']: + if tx.token == event.address: sender, receiver, value = event.values() - if event.address == tx.token.address.address and receiver == ZERO_ADDRESS == tx.to_address.address and sender in treasury.addresses and sender == tx.from_address.address: - underlying = contract(tx.token.address.address).token() + if tx.to_address == ZERO_ADDRESS == receiver and sender in treasury.addresses and tx.from_address == sender: + underlying = tx.token.contract.token() for _event in tx._events['Transfer']: _sender, _receiver, _value = _event.values() - if _event.address == underlying and _receiver == tx.from_address.address and event.pos < _event.pos and _sender == tx.token.address.address: + if _event.address == underlying and tx.from_address == _receiver and event.pos < _event.pos and tx.token == _sender: return True # token side for vault in vaults: - if tx.token.address.address == vault.token.address and "Transfer" in tx._events: + if tx.token == vault.token.address and "Transfer" in tx._events: for event in tx._events["Transfer"]: - sender, receiver, value = event.values() - if event.address == tx.token.address.address and sender == vault.vault.address == tx.from_address.address and receiver == tx.to_address.address: - for _event in tx._events["Transfer"]: - _sender, _receiver, _value = _event.values() - if _event.address == vault.vault.address and _receiver == ZERO_ADDRESS and _sender in treasury.addresses and _sender == tx.to_address.address and _event.pos < event.pos: - return True + if tx.token == event.address: + sender, receiver, value = event.values() + if tx.from_address == vault.vault.address == sender and tx.to_address == receiver: + for _event in tx._events["Transfer"]: + _sender, _receiver, _value = _event.values() + if _event.address == vault.vault.address and _receiver == ZERO_ADDRESS and _sender in treasury.addresses and tx.to_address == _sender and _event.pos < event.pos: + return True + return False def is_dolla_fed_withdrawal(tx: TreasuryTx) -> bool: if tx._from_nickname == 'Token: Curve DOLA Pool yVault - Unlisted' and tx.to_address.address in treasury.addresses and tx._symbol == "DOLA3POOL3CRV-f": return True - elif tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == ZERO_ADDRESS and tx._symbol == "yvCurve-DOLA-U": + elif tx.from_address.address in treasury.addresses and tx.to_address == ZERO_ADDRESS and tx._symbol == "yvCurve-DOLA-U": return True + return False def is_dola_frax_withdrawal(tx: TreasuryTx) -> bool: if tx._symbol == "yvCurve-DOLA-FRAXBP-U" and tx._from_nickname == "Yearn yChad Multisig" and tx._to_nickname == "Zero Address": @@ -120,5 +179,3 @@ def is_dola_frax_withdrawal(tx: TreasuryTx) -> bool: return tx in HashMatcher([ ["0x59a3a3b9e724835958eab6d0956a3acf697191182c41403c96d39976047d7240", Filter('log_index', 232)] ]) - - diff --git a/yearn/treasury/accountant/ignore/ygov.py b/yearn/treasury/accountant/ignore/ygov.py index dfd8ae002..2c5848e20 100644 --- a/yearn/treasury/accountant/ignore/ygov.py +++ b/yearn/treasury/accountant/ignore/ygov.py @@ -2,5 +2,4 @@ from yearn.special import Ygov def is_sent_to_ygov(tx: TreasuryTx) -> bool: - if tx._from_nickname == "Yearn Treasury" and tx._symbol == "yDAI+yUSDC+yUSDT+yTUSD" and tx.to_address.address == Ygov().vault.address: - return True + return tx._from_nickname == "Yearn Treasury" and tx._symbol == "yDAI+yUSDC+yUSDT+yTUSD" and tx.to_address == Ygov().vault.address diff --git a/yearn/treasury/accountant/other_expenses/__init__.py b/yearn/treasury/accountant/other_expenses/__init__.py index 2c59c8310..7f5aa3076 100644 --- a/yearn/treasury/accountant/other_expenses/__init__.py +++ b/yearn/treasury/accountant/other_expenses/__init__.py @@ -3,7 +3,7 @@ from y.networks import Network from yearn.treasury.accountant.classes import TopLevelTxGroup -from yearn.treasury.accountant.other_expenses import bugs, general +from yearn.treasury.accountant.other_expenses import bugs, general, revshare OTHER_EXPENSE_LABEL = "Other Operating Expense" @@ -31,6 +31,12 @@ other_expense_txgroup.create_child("yfi.eth", general.is_yfi_dot_eth) other_expense_txgroup.create_child("Fund Vyper Compiler Audit Context", general.is_yyper_contest) other_expense_txgroup.create_child("Reimburse yETH Applications", general.is_reimburse_yeth_applications) + other_expense_txgroup.create_child("dYFI Launch", general.is_dyfi_launch) + other_expense_txgroup.create_child("dYFI Redemptions", general.is_dyfi_redemptions) + other_expense_txgroup.create_child("veYFI Launch", general.is_veyfi_launch) + other_expense_txgroup.create_child("Vyper Donation", general.is_vyper_donation) + other_expense_txgroup.create_child("Unknown", general.is_unknown) + other_expense_txgroup.create_child("yBudget Reward", general.is_ybudget_reward) # Bugs if chain.id == Network.Mainnet: @@ -43,3 +49,10 @@ bug_reimbursements_txgroup.create_child("Reimburse st-yCRV User", bugs.is_stycrv) bug_reimbursements_txgroup.create_child("Slippage Bug", bugs.is_slippage_bug_reimbursement) bug_reimbursements_txgroup.create_child("Reimburse Opti Zap Bug", bugs.is_opti_zap_bug) + bug_reimbursements_txgroup.create_child("Reimburse Discord Link Bug", bugs.is_reimburse_discord_link_bug) + +if chain.id == Network.Mainnet: + revshare_txgroup = other_expense_txgroup.create_child("Revshare") + + revshare_txgroup.create_child("yAudit Revshare", revshare.is_yaudit_revshare) + revshare_txgroup.create_child("yLockers Revshare", revshare.is_ylockers_revshare) \ No newline at end of file diff --git a/yearn/treasury/accountant/other_expenses/bugs.py b/yearn/treasury/accountant/other_expenses/bugs.py index ecdde8e91..e38beabcb 100644 --- a/yearn/treasury/accountant/other_expenses/bugs.py +++ b/yearn/treasury/accountant/other_expenses/bugs.py @@ -12,19 +12,13 @@ def is_double_fee_reimbursement(tx: TreasuryTx) -> bool: "0x4ce0c829fb46fc1ea03e434599a68af4c6f65f80aff7e934a008c0fe63e9da3f", "0x90b54bf0d35621160b5094c263a2684f8e7b37fc6467c8c1ce6a53e2e7acbfa1", ] - if tx._from_nickname == "Disperse.app" and tx in HashMatcher(hashes): - return True - return False + return tx._from_nickname == "Disperse.app" and tx in HashMatcher(hashes) def is_ydai_fee_reimbursement(tx: TreasuryTx) -> bool: - if tx._from_nickname == "Disperse.app" and tx in HashMatcher(["0x2f667223aaefc4b153c28440d151fdb19333aff5d052c0524f2804fbd5a7964c"]): - return True - return False + return tx._from_nickname == "Disperse.app" and tx in HashMatcher(["0x2f667223aaefc4b153c28440d151fdb19333aff5d052c0524f2804fbd5a7964c"]) def is_yyfi_fee_reimbursement(tx: TreasuryTx) -> bool: - if tx._from_nickname == "Disperse.app" and tx in HashMatcher(["0x867b547b67910a08c939978d8071acca28ecc444d7155c0626e87730f67c058c"]): - return True - return False + return tx._from_nickname == "Disperse.app" and tx in HashMatcher(["0x867b547b67910a08c939978d8071acca28ecc444d7155c0626e87730f67c058c"]) def is_lossy_fee_reimbursement(tx: TreasuryTx) -> bool: """old vault code doesn't prevent fees from making harvest lossy. so here we airdrop the fee-take back to vault and do some housekeeper to prevent this from happening on other strats.""" @@ -47,3 +41,7 @@ def is_slippage_bug_reimbursement(tx: TreasuryTx) -> bool: def is_opti_zap_bug(tx: TreasuryTx) -> bool: return tx in HashMatcher(["0xf1bc9683863fd7133377e618e80a7035bc9389e7abf3f650aed4df8b068a2b79"]) + +def is_reimburse_discord_link_bug(tx: TreasuryTx) -> bool: + "https://github.com/yearn/chief-multisig-officer/pull/1201" + return tx in HashMatcher(["0x2201d33e1050a0c2639a6dc88c2faebc99fe60c2210713cd95ab1424d7e7df1c"]) diff --git a/yearn/treasury/accountant/other_expenses/general.py b/yearn/treasury/accountant/other_expenses/general.py index 58833d52c..370d8f7f0 100644 --- a/yearn/treasury/accountant/other_expenses/general.py +++ b/yearn/treasury/accountant/other_expenses/general.py @@ -1,37 +1,30 @@ -from decimal import Decimal - from yearn.entities import TreasuryTx from yearn.treasury.accountant.classes import Filter, HashMatcher, IterFilter from yearn.treasury.accountant.constants import treasury def is_strategist_buyout(tx: TreasuryTx) -> bool: - hashes = [ + return tx in HashMatcher([ ["0x47035f156d4e6144c144b2ac5e91497e353c9a4e23133587bbf3da2f9d7da596", Filter("_symbol", "YFI")] - ] - return tx in HashMatcher(hashes) + ]) def is_gitcoin_matching_donation(tx: TreasuryTx) -> bool: gitcoin = "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" - if tx.from_address.address in treasury.addresses and tx.to_address and tx.to_address.address == gitcoin and tx._symbol in ["DAI", "USDC"]: - return True + return tx.from_address.address in treasury.addresses and tx.to_address == gitcoin and tx._symbol in ["DAI", "USDC"] def is_yacademy_fellow_grant(tx: TreasuryTx) -> bool: - hashes = [ + return tx._from_nickname == "Disperse.app" and tx in HashMatcher([ "0x2b74fb1a5deadbb0885dfa33502304382525a0847350a688b707b3882930eeab", "0x028eff213177fbfa170bc9a3227096b1d688a8b6191c8ec06321299a5396949f", - ] - if tx._from_nickname == "Disperse.app": - return tx in HashMatcher(hashes) + ]) def is_yfi_story(tx: TreasuryTx) -> bool: story_dot_ychad_dot_eth = "0x93C6c14C134C4fF52cbB6BC2f50F19d84874cDD1" - if tx.to_address and tx.to_address.address == story_dot_ychad_dot_eth: - return True + return tx.to_address == story_dot_ychad_dot_eth def is_aztek_gas_subsidy(tx: TreasuryTx) -> bool: - return tx.to_address and tx.to_address.address == "0xABc30E831B5Cc173A9Ed5941714A7845c909e7fA" + return tx.to_address == "0xABc30E831B5Cc173A9Ed5941714A7845c909e7fA" def is_devcon_event(tx: TreasuryTx) -> bool: return tx in HashMatcher([ @@ -46,7 +39,6 @@ def is_eth_global(tx: TreasuryTx) -> bool: def is_veyfi_gas(tx: TreasuryTx) -> bool: """ a gas subsidy for contributors to fund their veyfi wallet """ - return tx._symbol == "ETH" and tx in HashMatcher([ "0x8ed7ee716e04096a7274188b5b371bc7c92aff305fa7b47f32ad622374fb23fc", "0x9b8f9dfaaedceaeb2b286db92f2aba2d2e519954b47a2d603cd4ce5fd03336fe", @@ -103,7 +95,7 @@ def is_4626_alliance(tx: TreasuryTx) -> bool: ]) def is_yeth_bootstrap(tx: TreasuryTx) -> bool: - return tx.token == "ETH" and tx.hash == '0x0c59e87027bcdcaa718e322a28bc436106d73ae8623071930437bdb0706c4d65' + return tx.token == "ETH" and tx.hash == '0x0c59e87027bcdcaa718e322a28bc436106d73ae8623071930437bdb0706c4d65' and tx._from_nickname == "Yearn yChad Multisig" def is_warroom_games(tx: TreasuryTx) -> bool: return tx.hash == "0x8f17ead9cea87166cf99ed2cdbc46dfdf98c04c261de5b5167caddce5f704cb2" and tx.log_index in [429,430,431] @@ -117,3 +109,34 @@ def is_yyper_contest(tx: TreasuryTx) -> bool: def is_reimburse_yeth_applications(tx: TreasuryTx) -> bool: return tx in HashMatcher([["0x846d475425a1a70469b8674b6f15568c83a14ed3251cafa006811722af676f44", Filter('_symbol', 'ETH')]]) + +def is_dyfi_launch(tx: TreasuryTx) -> bool: + if tx in HashMatcher(["0x2ec726e5ee52cdc063e61795d1b96a75d16fd91824136c990b7c3ddd52b28e31"]): + # unused returned + if tx.amount > 0: + tx.amount *= -1 + if tx.value_usd > 0: + tx.value_usd *= -1 + return True + return tx in HashMatcher([ + "0x066c32f02fc0908d55b6651afcfb20473ec3d99363de222f2e8f4a7e0c66462e", + ]) + +def is_dyfi_redemptions(tx: TreasuryTx) -> bool: + """YFI going to the dyfi redemptions contract""" + return tx._symbol == "YFI" and tx._to_nickname == "dYFI Redemption Contract" + +def is_veyfi_launch(tx: TreasuryTx) -> bool: + return tx in HashMatcher([["0x51202f9e8a9afa84a9a0c37831ca9a18508810175cb95ab7c52691bbe69a56d5", Filter('_symbol', 'YFI')]]) + +def is_unknown(tx: TreasuryTx) -> bool: + return tx in HashMatcher([ + ["0xdf3e6cf2e50052e4eeb57fb2562b5e1b02701014ce65b60e6c8a850c409b341a", IterFilter('log_index', [121, 122])], + "0x81fd665147690345100c385d273135dba3b4163b17ccc7d7c7b48fb636297205", + ]) + +def is_vyper_donation(tx: TreasuryTx) -> bool: + return tx.to_address == "0x70CCBE10F980d80b7eBaab7D2E3A73e87D67B775" + +def is_ybudget_reward(tx: TreasuryTx) -> bool: + return tx.hash == "0xa1b242b2626def6cdbe49d92a06aad96fa018c27b48719a98530c5e5e0ac61c5" \ No newline at end of file diff --git a/yearn/treasury/accountant/other_expenses/revshare.py b/yearn/treasury/accountant/other_expenses/revshare.py new file mode 100644 index 000000000..dae338d27 --- /dev/null +++ b/yearn/treasury/accountant/other_expenses/revshare.py @@ -0,0 +1,11 @@ + +from yearn.entities import TreasuryTx +from yearn.treasury.accountant.classes import Filter, HashMatcher + +# NOTE: These predate the yteam revshare splitting implementation so were done manually + +def is_yaudit_revshare(tx: TreasuryTx) -> bool: + return tx in HashMatcher([["0xdf3e6cf2e50052e4eeb57fb2562b5e1b02701014ce65b60e6c8a850c409b341a", Filter('log_index', 127)]]) + +def is_ylockers_revshare(tx: TreasuryTx) -> bool: + return tx in HashMatcher([["0x038aeb3351b762bc92c5e4274c01520ae08dc314e2282ececc2a19a033d994a8", Filter('log_index', 163)]]) diff --git a/yearn/treasury/accountant/other_income/airdrop.py b/yearn/treasury/accountant/other_income/airdrop.py index 2e4d02d2c..d7a75a1b7 100644 --- a/yearn/treasury/accountant/other_income/airdrop.py +++ b/yearn/treasury/accountant/other_income/airdrop.py @@ -14,10 +14,14 @@ def is_airdrop(tx: TreasuryTx) -> bool: "0xb39f2991fdc2c70b43046be3eac36bff35c21c7f66e2888a52afc3956abae451", # Gnosis SAFE airdrop "0x4923fd32b4eacdc1617700c67176935676ca4d06bbfbb73644730c55534623db", # Gnosis SAFE airdrop ["0xe8b5a4ebf1f04048f6226b22b2865a33621e88ea255dcea0cfd7a975a3a7e387", Filter('log_index', 72)], # Gnosis SAFE airdrop + "0x5ba604cae0d355835b182fa23c8a58ae695905e69ed08c7cf8a52f3eca889484", # Gnosis SAFE airdrop "0x44f7d3b2030799ea45932baf6049528a059aabd6387f3128993d646d01c8e877", # TKX "0xf2dbe58dffd3bc1476755e9f74e2ae07531579d0a3ea9e2aaac2ef902e080c2a", # TKX "0x8079e9cae847da196dc5507561bc9d1434f765f05045bc1a82df735ec83bc6ec", # MTV "0x037a9cc5baa7d63a11d0f0720ee552bbf4ad85118ee5425220a263695fedbe9f", # Gnosis SAFE airdrop + # NOTE: this one was rec'd elsewhere, dumped, and WETH sent to treasury + "0xc12ded505ea158717890e4ae6e7ab5eb5cb61edbc13dfd125dd0e6f9b1af9477", # Gnosis SAFE airdrop + "0x7c086a82b43b2f49db93b76a0698cf86a9c620b3bf924f0003175b04a17455ad", # PRISMA ], }.get(chain.id, []) return tx in HashMatcher(hashes) \ No newline at end of file diff --git a/yearn/treasury/accountant/other_income/robovault.py b/yearn/treasury/accountant/other_income/robovault.py index 7e4d2bd66..6af77af09 100644 --- a/yearn/treasury/accountant/other_income/robovault.py +++ b/yearn/treasury/accountant/other_income/robovault.py @@ -3,11 +3,10 @@ from typing import Optional from brownie import chain -from y.networks import Network +from y import ContractNotVerified, ERC20, Network from yearn.entities import TreasuryTx, TxGroup from yearn.treasury.accountant.constants import treasury -from yearn.utils import contract def is_robovault_share(tx: TreasuryTx) -> Optional[TxGroup]: @@ -18,23 +17,21 @@ def is_robovault_share(tx: TreasuryTx) -> Optional[TxGroup]: return False if not ( - tx.to_address and tx.to_address.address in treasury.addresses + tx.to_address.address in treasury.addresses and tx._symbol.startswith('rv') and tx.from_address.is_contract ): return False try: - strat = contract(tx.from_address.address) - except ValueError as e: - if not "Contract source code not verified" in str(e): - raise + strat = tx.from_address.contract + except ContractNotVerified: return False if not hasattr(strat,'vault'): return False - if strat.vault(block_identifier=tx.block) == tx.token.address.address: + if strat.vault(block_identifier=tx.block) == tx.token: return True # For some reason these weren't caught by the above logic. Its inconsequential and not worth investigating to come up with better hueristics. @@ -44,5 +41,5 @@ def is_robovault_share(tx: TreasuryTx) -> Optional[TxGroup]: return all([ tx._from_nickname == "Contract: Strategy", tx._symbol == 'rv3USDCc', - contract(strat.vault(block_identifier = tx.block)).symbol() == 'rv3USDCb' + ERC20(strat.vault(block_identifier = tx.block)).symbol == 'rv3USDCb' ]) diff --git a/yearn/treasury/accountant/prepare_db.py b/yearn/treasury/accountant/prepare_db.py index 19544c60d..ec7f2d02e 100644 --- a/yearn/treasury/accountant/prepare_db.py +++ b/yearn/treasury/accountant/prepare_db.py @@ -9,7 +9,6 @@ from yearn.outputs.postgres.utils import cache_address from yearn.partners.partners import partners from yearn.treasury.accountant.constants import BRIDGE_ASSISTOOOR, DISPERSE_APP -from yearn.utils import contract @db_session @@ -28,6 +27,7 @@ def prepare_db() -> None: cache_cowswap_msig() cache_address_nicknames_for_tokens() cache_token_dumper() + cache_dyfi_redemptions() def cache_ychad() -> None: """ Label yChad in pg. """ @@ -108,4 +108,8 @@ def cache_address_nicknames_for_tokens() -> None: def cache_token_dumper() -> None: cache_address('0xC001d00d425Fa92C4F840baA8f1e0c27c4297a0B').nickname = "Token Dumper Multisig" + +def cache_dyfi_redemptions() -> None: + if chain.id == Network.Mainnet: + cache_address('0x7dC3A74F0684fc026f9163C6D5c3C99fda2cf60a').nickname = "dYFI Redemption Contract" \ No newline at end of file diff --git a/yearn/treasury/accountant/revenue/__init__.py b/yearn/treasury/accountant/revenue/__init__.py index ff65a0438..cddc866f3 100644 --- a/yearn/treasury/accountant/revenue/__init__.py +++ b/yearn/treasury/accountant/revenue/__init__.py @@ -4,15 +4,16 @@ from yearn.treasury.accountant.classes import TopLevelTxGroup from yearn.treasury.accountant.revenue import (bribes, dinobots, farming, fees, - keepcoins, seasolver, yacademy) + keepcoins, seasolver, yteams) REVENUE_LABEL = "Protocol Revenue" revenue_txgroup = TopLevelTxGroup(REVENUE_LABEL) fees_txgroup = revenue_txgroup.create_child("Fees") -fees_txgroup.create_child("Vaults V1", fees.is_fees_v1) +if chain.id == Network.Mainnet: + fees_txgroup.create_child("Vaults V1", fees.is_fees_v1) fees_txgroup.create_child("Vaults V2", fees.is_fees_v2) -fees_txgroup.create_child("Factory Vaults V2", fees.is_factory_fees_v2) +#fees_txgroup.create_child("Factory Vaults V2", fees.is_factory_fees_v2) fees_txgroup.create_child("Vaults V3", fees.is_fees_v3) fees_txgroup.create_child("YearnFed Fees", fees.is_yearn_fed_fees) @@ -48,4 +49,4 @@ farming_txgroup.create_child("SOLIDsex Farming", farming.is_solidsex) if chain.id == Network.Mainnet: - revenue_txgroup.create_child("yAcademy Audit Rev Share", yacademy.is_yacademy_audit_revenue) + revenue_txgroup.create_child("yTeam Rev Share", yteams.is_yteam_rev_share) diff --git a/yearn/treasury/accountant/revenue/bribes.py b/yearn/treasury/accountant/revenue/bribes.py index 7f7720dc4..dba9fd8f3 100644 --- a/yearn/treasury/accountant/revenue/bribes.py +++ b/yearn/treasury/accountant/revenue/bribes.py @@ -2,7 +2,13 @@ from yearn.entities import TreasuryTx def is_ycrv_bribe(tx: TreasuryTx) -> bool: - return tx._from_nickname == "Contract: BribeSplitter" + return tx._from_nickname in [ + # OLD + "Contract: BribeSplitter", + # NEW + "Contract: YCRVSplitter", + # done manually during migration + ] or tx.hash == "0x3c635388812bed82845c0df3531583399fdf736ccfb95837b362379766955f2d" def is_ybribe_fees(tx: TreasuryTx) -> bool: return False diff --git a/yearn/treasury/accountant/revenue/farming.py b/yearn/treasury/accountant/revenue/farming.py index c7608143a..6b3f4431f 100644 --- a/yearn/treasury/accountant/revenue/farming.py +++ b/yearn/treasury/accountant/revenue/farming.py @@ -1,55 +1,46 @@ from brownie import ZERO_ADDRESS +from y import Contract + from yearn.entities import TreasuryTx from yearn.treasury.accountant.constants import treasury -from yearn.utils import contract +_sex_distributors = [ + ZERO_ADDRESS, + "0x7FcE87e203501C3a035CbBc5f0Ee72661976D6E1", # StakingRewards +] + +_solid_distributors = [ + "0x7FcE87e203501C3a035CbBc5f0Ee72661976D6E1", # StakingRewards + "0x26E1A0d851CF28E697870e1b7F053B605C8b060F", # LpDepositor +] + +_solidsex_distributors = [ + "0x7FcE87e203501C3a035CbBc5f0Ee72661976D6E1", # StakingRewards + "0xA5e76B97e12567bbA2e822aC68842097034C55e7", # FeeDistributor +] + def is_sex(tx: TreasuryTx) -> bool: - sex_distributors = [ - ZERO_ADDRESS, - # StakingRewards - "0x7FcE87e203501C3a035CbBc5f0Ee72661976D6E1", - ] - if tx._symbol == "SEX" and tx.from_address.address in sex_distributors and tx.to_address and tx.to_address.address in treasury.addresses: - return True - return False + return tx._symbol == "SEX" and tx.from_address in _sex_distributors and tx.to_address.address in treasury.addresses def is_solid(tx: TreasuryTx) -> bool: - solid_distributors = [ - # StakingRewards - "0x7FcE87e203501C3a035CbBc5f0Ee72661976D6E1", - # LpDepositor - "0x26E1A0d851CF28E697870e1b7F053B605C8b060F", - ] - if tx._symbol == "SOLID" and tx.from_address.address in solid_distributors and tx.to_address and tx.to_address.address in treasury.addresses: - return True - return False + return tx._symbol == "SOLID" and tx.from_address in _solid_distributors and tx.to_address.address in treasury.addresses def is_solidsex(tx: TreasuryTx) -> bool: - solidsex_distributors = [ - # StakingRewards - "0x7FcE87e203501C3a035CbBc5f0Ee72661976D6E1", - # FeeDistributor - "0xA5e76B97e12567bbA2e822aC68842097034C55e7", - ] - if tx._symbol == "SOLIDsex" and tx.from_address.address in solidsex_distributors and tx.to_address and tx.to_address.address in treasury.addresses: - return True - return False + return tx._symbol == "SOLIDsex" and tx.from_address in _solidsex_distributors and tx.to_address.address in treasury.addresses def is_generic_comp_rewards(tx: TreasuryTx) -> bool: - if tx.to_address and tx.to_address.address in treasury.addresses and "DistributedSupplierComp" in tx._events: + if tx.to_address.address in treasury.addresses and "DistributedSupplierComp" in tx._events: for event in tx._events["DistributedSupplierComp"]: - if tx.from_address.address == event.address and 'supplier' in event and event['supplier'] == tx.to_address.address: - troller = contract(event.address) - if hasattr(troller, 'getCompAddress') and troller.getCompAddress() == tx.token.address.address: + if tx.from_address == event.address and 'supplier' in event and tx.to_address == event['supplier']: + troller = Contract(event.address) + if hasattr(troller, 'getCompAddress') and troller.getCompAddress() == tx.token: return True + return False def is_comp_rewards(tx: TreasuryTx) -> bool: - if tx._symbol == "COMP" and is_generic_comp_rewards(tx): - return True + return tx._symbol == "COMP" and is_generic_comp_rewards(tx) def is_scream_rewards(tx: TreasuryTx) -> bool: - if tx._symbol == "SCREAM" and is_generic_comp_rewards(tx): - return True - + return tx._symbol == "SCREAM" and is_generic_comp_rewards(tx) diff --git a/yearn/treasury/accountant/revenue/fees.py b/yearn/treasury/accountant/revenue/fees.py index 574274f0c..ec3892423 100644 --- a/yearn/treasury/accountant/revenue/fees.py +++ b/yearn/treasury/accountant/revenue/fees.py @@ -1,53 +1,69 @@ -from brownie import chain -from y.networks import Network +import asyncio +import logging +from functools import lru_cache + +from y import Contract from yearn.entities import TreasuryTx -from yearn.multicall2 import fetch_multicall from yearn.treasury.accountant.constants import treasury, v1, v2 -from yearn.utils import contract -def is_fees_v1(tx: TreasuryTx) -> bool: - if chain.id != Network.Mainnet: - return False +logger = logging.getLogger(__name__) + +_vaults = asyncio.get_event_loop().run_until_complete(v2.vaults) +logger.info('%s v2 vaults loaded', len(_vaults)) +_experiments = asyncio.get_event_loop().run_until_complete(v2.experiments) +logger.info('%s v2 experiments loaded', len(_experiments)) +_removed = asyncio.get_event_loop().run_until_complete(v2.removed) +logger.info('%s removed v2 vaults loaded', len(_removed)) +v2_vaults = _vaults + _experiments + _removed + + +# v1 helpers +_is_y3crv = lambda tx: tx._symbol == "y3Crv" and tx._from_nickname.startswith("Contract: Strategy") and tx._from_nickname.endswith("3pool") +_is_ypool = lambda tx: tx._symbol == "yyDAI+yUSDC+yUSDT+yTUSD" and tx._from_nickname.startswith("Contract: Strategy") and tx._from_nickname.endswith("ypool") - if not tx.to_address or tx.to_address.address not in treasury.addresses: +@lru_cache(maxsize=None) +def _get_rewards(controller: Contract, block: int) -> str: + try: + return controller.rewards(block_identifier=block) + except ValueError as e: + if str(e) == "No data was returned - the call likely reverted": + return None + raise + + +def is_fees_v1(tx: TreasuryTx) -> bool: + if tx.to_address.address not in treasury.addresses: return False for vault in v1.vaults: - if ( - tx.token.address.address != vault.token.address + if tx.token != vault.token.address: # Fees from single-sided strategies are not denominated in `vault.token` - and not (tx._symbol == "y3Crv" and tx._from_nickname.startswith("Contract: Strategy") and tx._from_nickname.endswith("3pool")) - and not (tx._symbol == "yyDAI+yUSDC+yUSDT+yTUSD" and tx._from_nickname.startswith("Contract: Strategy") and tx._from_nickname.endswith("ypool")) - ): - continue + if not (_is_y3crv(tx) or _is_ypool(tx)): + continue - try: - controller = contract(vault.vault.controller(block_identifier=tx.block)) - except Exception as e: - known_exceptions = [ - "No data was returned - the call likely reverted", - ] - - if str(e) not in known_exceptions: - raise - + rewards = _get_rewards(vault.controller, tx.block) + if tx.to_address != rewards: + logger.debug('to address %s doesnt match rewards address %s', tx.to_address.address, rewards) continue - - if [tx.from_address.address, tx.to_address.address] == fetch_multicall([controller, 'strategies',vault.token.address],[controller,'rewards'], block=tx.block): - return True - + strategy = vault.controller.strategies(vault.token.address, block_identifier=tx.block) + if tx.from_address != strategy: + logger.debug('from address %s doesnt match strategy %s set on controller %s', tx.from_address.address, strategy, vault.controller) + continue + return True return False + def is_fees_v2(tx: TreasuryTx) -> bool: + if tx.to_address.address not in treasury.addresses: + return False if any( - tx.from_address.address == vault.vault.address - and tx.token.address.address == vault.vault.address - and tx.to_address.address in treasury.addresses - and tx.to_address.address == vault.vault.rewards(block_identifier=tx.block) - for vault in v2.vaults + v2.experiments + tx.from_address == vault.vault.address + and tx.token == vault.vault.address + and tx.to_address == vault.vault.rewards(block_identifier=tx.block) + for vault in v2_vaults ): return True elif is_inverse_fees_from_stash_contract(tx): @@ -56,32 +72,21 @@ def is_fees_v2(tx: TreasuryTx) -> bool: return True return False -factory_strats = [ - ["Contract: StrategyCurveBoostedFactoryClonable", ["CRV", "LDO"]], - ["Contract: StrategyConvexFactoryClonable", ["CRV", "CVX"]], - ["Contract: StrategyConvexFraxFactoryClonable", ["CRV", "CVX", "FXS"]], -] - -def is_factory_fees_v2(tx: TreasuryTx) -> bool: - for strategy, tokens in factory_strats: - if tx._from_nickname == strategy and tx._symbol in tokens: - return True - def is_fees_v3(tx: TreasuryTx) -> bool: # Stay tuned... return False def is_yearn_fed_fees(tx: TreasuryTx) -> bool: - if tx.to_address and tx.to_address.address in treasury.addresses: + if tx.to_address.address in treasury.addresses: # New version - if tx._symbol in ["yvCurve-DOLA-U", "CRV"] and tx.from_address.address == "0x64e4fC597C70B26102464B7F70B1F00C77352910": + if tx._symbol in ["yvCurve-DOLA-U", "CRV"] and tx.from_address == "0x64e4fC597C70B26102464B7F70B1F00C77352910": return True # Old versions - if tx._symbol in ["yvCurve-DOLA-U", "yveCRV-DAO"] and tx.from_address.address in ["0x09F61718474e2FFB884f438275C0405E3D3559d3", "0x7928becDda70755B9ABD5eE7c7D5E267F1412042"]: + if tx._symbol in ["yvCurve-DOLA-U", "yveCRV-DAO"] and tx.from_address in ["0x09F61718474e2FFB884f438275C0405E3D3559d3", "0x7928becDda70755B9ABD5eE7c7D5E267F1412042"]: return True if tx._symbol == "INV" and tx._from_nickname == "Inverse Treasury" and tx._to_nickname == "ySwap Multisig": return True - if tx.from_address.address == "0x9D5Df30F475CEA915b1ed4C0CCa59255C897b61B" and tx._to_nickname == "ySwap Multisig": + if tx.from_address == "0x9D5Df30F475CEA915b1ed4C0CCa59255C897b61B" and tx._to_nickname == "ySwap Multisig": return True return False @@ -94,6 +99,7 @@ def is_temple(tx: TreasuryTx) -> bool: return True elif tx._from_nickname == "Contract: Splitter" and tx._symbol in ["yveCRV-DAO","CRV"]: return True + return False def is_inverse_fees_from_stash_contract(tx: TreasuryTx) -> bool: - return tx.from_address.address == "0xE376e8e8E3B0793CD61C6F1283bA18548b726C2e" and tx.to_address.nickname == "Token: Curve stETH Pool yVault" + return tx.from_address == "0xE376e8e8E3B0793CD61C6F1283bA18548b726C2e" and tx.to_address.nickname == "Token: Curve stETH Pool yVault" diff --git a/yearn/treasury/accountant/revenue/keepcoins.py b/yearn/treasury/accountant/revenue/keepcoins.py index 85cf0bf8e..a48ae6a9e 100644 --- a/yearn/treasury/accountant/revenue/keepcoins.py +++ b/yearn/treasury/accountant/revenue/keepcoins.py @@ -15,10 +15,10 @@ }.get(chain.id, []) def is_keep_angle(tx: TreasuryTx) -> bool: - if tx._symbol == "ANGLE" and tx.to_address and tx.to_address.address in treasury.addresses: + if tx._symbol == "ANGLE" and tx.to_address.address in treasury.addresses: if tx._from_nickname == "Contract: StrategyAngleUSDC": return True - return tx.from_address.address in angle_strats_with_non_specific_names + return tx.from_address in angle_strats_with_non_specific_names return False @@ -44,21 +44,10 @@ def is_keep_bal(tx: TreasuryTx) -> bool: "0xe614f717b3e8273f38Ed7e0536DfBA60AD021c85", ] - if ( - tx._symbol == "BAL" and - - tx.to_address and tx.to_address.address in treasury.addresses and - (any(f"Contract: {strat}" == tx._from_nickname for strat in strats) or (any(strat.address == tx.from_address.address) for strat in _strats)) - ): - return True - return False + return tx._symbol == "BAL" and tx.to_address.address in treasury.addresses and (any(f"Contract: {strat}" == tx._from_nickname for strat in strats) or tx.from_address in _strats) def is_keep_beets(tx: TreasuryTx) -> bool: - if tx._symbol == "BEETS" and tx.to_address and tx.to_address.address in treasury.addresses and tx.hash != "0x1e997aa8c79ece76face8deb8fe7df4cea4f6a1ef7cd28501013ed30dfbe238f": - return True - return False + return tx._symbol == "BEETS" and tx.to_address.address in treasury.addresses and tx.hash != "0x1e997aa8c79ece76face8deb8fe7df4cea4f6a1ef7cd28501013ed30dfbe238f" def is_keep_pool(tx: TreasuryTx) -> bool: - if tx._symbol == "POOL" and tx._from_nickname == "Contract: StrategyPoolTogether" and tx.to_address and tx.to_address.address in treasury.addresses: - return True - return False + return tx._symbol == "POOL" and tx._from_nickname == "Contract: StrategyPoolTogether" and tx.to_address.address in treasury.addresses diff --git a/yearn/treasury/accountant/revenue/seasolver.py b/yearn/treasury/accountant/revenue/seasolver.py index a34edc927..26ce7bfda 100644 --- a/yearn/treasury/accountant/revenue/seasolver.py +++ b/yearn/treasury/accountant/revenue/seasolver.py @@ -10,4 +10,4 @@ def is_seasolver_slippage_revenue(tx: TreasuryTx) -> bool: def is_cowswap_incentive(tx: TreasuryTx) -> bool: """ Incentives for swapping on CowSwap """ - return tx._symbol == "COW" and tx.from_address.address == "0xA03be496e67Ec29bC62F01a428683D7F9c204930" \ No newline at end of file + return tx._symbol == "COW" and tx.from_address == "0xA03be496e67Ec29bC62F01a428683D7F9c204930" \ No newline at end of file diff --git a/yearn/treasury/accountant/revenue/yacademy.py b/yearn/treasury/accountant/revenue/yacademy.py deleted file mode 100644 index f8fd268ec..000000000 --- a/yearn/treasury/accountant/revenue/yacademy.py +++ /dev/null @@ -1,7 +0,0 @@ - -from yearn.entities import TreasuryTx - -YACADEMY_SPLIT_CONTRACT = "0x2ed6c4B5dA6378c7897AC67Ba9e43102Feb694EE" - -def is_yacademy_audit_revenue(tx: TreasuryTx) -> bool: - return tx.from_address.address == YACADEMY_SPLIT_CONTRACT or tx.hash == "0x6e4f4405bd0970d42a48795a5219c14c763705f6ea9879affea652438758c065" diff --git a/yearn/treasury/accountant/revenue/yteams.py b/yearn/treasury/accountant/revenue/yteams.py new file mode 100644 index 000000000..758ead54c --- /dev/null +++ b/yearn/treasury/accountant/revenue/yteams.py @@ -0,0 +1,12 @@ + +from yearn.entities import TreasuryTx + +OXSPLIT_CONTRACT = "0x2ed6c4B5dA6378c7897AC67Ba9e43102Feb694EE" +SPLITS_WAREHOUSE_CONTRACT = "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8" + +def is_yteam_rev_share(tx: TreasuryTx) -> bool: + return tx.from_address in [OXSPLIT_CONTRACT, SPLITS_WAREHOUSE_CONTRACT] or tx.hash in [ + # These predate the split implementation + # yAudit + "0x6e4f4405bd0970d42a48795a5219c14c763705f6ea9879affea652438758c065", + ] diff --git a/yearn/treasury/streams.py b/yearn/treasury/streams.py index fdf30dcaa..92048a42a 100644 --- a/yearn/treasury/streams.py +++ b/yearn/treasury/streams.py @@ -1,19 +1,37 @@ -from typing import List, Optional +import asyncio +import logging +from datetime import date, datetime, timedelta, timezone +from decimal import Decimal +from functools import lru_cache +from typing import Awaitable, List, Optional, Tuple +from async_lru import alru_cache from brownie import chain +from eth_portfolio._cache import cache_to_disk from pony.orm import db_session, select +from tqdm.asyncio import tqdm_asyncio +from y import Contract, Network, get_price +from y.time import closest_block_after_timestamp_async + +from yearn.cache import memory from yearn.constants import YCHAD_MULTISIG, YFI -from yearn.entities import Stream, Token, TxGroup +from yearn.entities import Stream, StreamedFunds, TxGroup from yearn.events import decode_logs, get_logs_asap -from yearn.outputs.postgres.utils import cache_token +from yearn.outputs.postgres.utils import token_dbid from yearn.treasury.constants import BUYER -from yearn.utils import contract +from yearn.utils import dates_generator, threads + + +ONE_DAY = 60 * 60 * 24 dai = "0x6B175474E89094C44Da98b954EedeAC495271d0F" -streams_dai = contract('0x60c7B0c5B3a4Dc8C690b074727a17fF7aA287Ff2') -streams_yfi = contract('0xf3764eC89B1ad20A31ed633b1466363FAc1741c4') +if chain.id == Network.Mainnet: + streams_dai = Contract('0x60c7B0c5B3a4Dc8C690b074727a17fF7aA287Ff2') + streams_yfi = Contract('0xf3764eC89B1ad20A31ed633b1466363FAc1741c4') + +logger = logging.getLogger(__name__) class YearnStreams: def __init__(self): @@ -33,10 +51,8 @@ def streams_for_recipient(self, recipient: str, at_block: Optional[int] = None) return list(select(s for s in Stream if s.to_address.address == recipient)) return list(select(s for s in Stream if s.to_address.address == recipient and (s.end_block is None or at_block <= s.end_block))) - def streams_for_token(self, token: Token, include_inactive: bool = False) -> List[Stream]: - if not isinstance(token, Token): - token = cache_token(token) - streams = list(select(s for s in Stream if s.token == token)) + def streams_for_token(self, token: str, include_inactive: bool = False) -> List[Stream]: + streams = list(select(s for s in Stream if s.token.token_id == token_dbid(token))) if include_inactive is False: streams = [s for s in streams if s.is_alive] return streams @@ -109,3 +125,166 @@ def get_streams(self): for stream in self.streams_for_recipient(stream.to_address.address): if stream.txgroup is None: stream.txgroup = team_payments_txgroup + +if chain.id == Network.Mainnet: + streams = YearnStreams() + +@db_session +def all_other_dai_streams(): + return [s for s in streams.dai_streams() if s.to_address.address != BUYER] + +@alru_cache +async def get_stream_contract(stream_id: str) -> Contract: + address = await threads.run(_get_stream_contract, stream_id) + return await Contract.coroutine(address) + +@db_session +def get_start_date(stream_id: str) -> date: + return Stream[stream_id].start_date + +@db_session +def is_closed(stream_id: str) -> bool: + return bool(StreamedFunds.get(stream=Stream[stream_id], is_last_day=True)) + +@cache_to_disk +async def start_timestamp(stream_id: str, block: Optional[int] = None) -> int: + contract = await get_stream_contract(stream_id) + return int(await contract.streamToStart.coroutine(f'0x{stream_id}', block_identifier=block)) + + +async def process_stream_for_date(stream_id: str, date: datetime) -> Optional[StreamedFunds]: + if entity := await threads.run(StreamedFunds.get_entity, stream_id, date): + return entity + + stream_token = _get_token_for_stream(stream_id) + check_at = date + timedelta(days=1) - timedelta(seconds=1) + block = await closest_block_after_timestamp_async(int(check_at.timestamp())) + price_fut = asyncio.create_task(get_price(stream_token, block, sync=False)) + _start_timestamp = await start_timestamp(stream_id, block) + if _start_timestamp == 0: + # If the stream was already closed, we can return `None`. + if await threads.run(is_closed, stream_id): + price_fut.cancel() + return None + + while _start_timestamp == 0: + # is active last block? + block -= 1 + _start_timestamp = await start_timestamp(stream_id, block) + + block_datetime = datetime.fromtimestamp(chain[block].timestamp) + assert block_datetime.date() == date.date() + seconds_active = (check_at - block_datetime).seconds + is_last_day = True + else: + seconds_active = int(check_at.timestamp()) - _start_timestamp + is_last_day = False + + # How many seconds was the stream active on `date`? + seconds_active_today = seconds_active if seconds_active < ONE_DAY else ONE_DAY + if seconds_active_today < ONE_DAY and not is_last_day: + if date.date() == await threads.run(get_start_date, stream_id): + logger.debug('stream started today, partial day accepted') + else: + seconds_active_today = ONE_DAY + logger.debug("active for %s seconds on %s", seconds_active_today, date.date()) + if is_last_day: + logger.debug('is last day') + + price = Decimal(await price_fut) + return await threads.run(StreamedFunds.create_entity, stream_id, date, price, seconds_active_today, is_last_day) + +@memory.cache +def get_ts(block: int) -> datetime: + return datetime.fromtimestamp(chain[block].timestamp) + +def get_start_and_end(stream: Stream) -> Tuple[datetime, datetime]: + start_timestamp = get_ts(stream.start_block) + end_timestamp = get_ts(stream.end_block) if stream.end_block else datetime.now(timezone.utc) + return start_timestamp, end_timestamp + +async def process_stream(stream, run_forever: bool = False) -> None: + # NOTE: We need to go one-by-one for the math to be right + async for date in dates_generator(*get_start_and_end(stream), stop_at_today=not run_forever): + if await process_stream_for_date(stream.stream_id, date) is None: + return + +async def process_streams(run_forever: bool = False): + await tqdm_asyncio.gather(*[process_stream(stream, run_forever=run_forever) for stream in streams.streams(include_inactive=True)], desc='Loading streams') + + +@db_session +def _get_stream_contract(stream_id: str) -> str: + return Stream[stream_id].contract.address + +@lru_cache +@db_session +def _get_token_for_stream(stream_id): + return Stream[stream_id].token.address.address + + +# TODO: Fit this better into the rest of the exporter so it runs in tandem with treasury txs exporter +@db_session +def _get_coro() -> Awaitable[None]: + #print('buybacks active stream(s)') + for stream in streams.buyback_streams(): + #stream.print() + pass + + team_dai_streams = [stream for stream in all_other_dai_streams() if int(stream.amount_per_second) == 385802469135802432] + + #print(f'{len(team_dai_streams)} team dai streams') + total_rate = 0 + for i, stream in enumerate(team_dai_streams): + total_rate += stream.amount_per_second + total_rate /= stream.scale + #print(f'team dai per second: {total_rate}') + #print(f'team dai per day: {total_rate * 60 * 60 * 24}') + + v3_multisig_i_think = "0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7" + v3_dai_streams = [stream for stream in all_other_dai_streams() if stream.to_address.address == v3_multisig_i_think] + + print(f'{len(v3_dai_streams)} v3 dai streams') + total_rate = 0 + for i, stream in enumerate(v3_dai_streams): + total_rate += stream.amount_per_second + total_rate /= stream.scale + print(f'v3 dai per second: {total_rate}') + print(f'v3 dai per day: {total_rate * 60 * 60 * 24}') + + misc_dai_streams = [stream for stream in all_other_dai_streams() if stream not in team_dai_streams and stream.to_address.address != v3_multisig_i_think] + + print('all other active streams') + total_rate = 0 + for i, stream in enumerate(misc_dai_streams): + print(f'stream {i}') + total_rate += stream.amount_per_second + #print(stream.amount_per_second) + stream.print() + total_rate /= stream.scale + print(f'all other streams dai per second: {total_rate}') + print(f'all other streams dai per day: {total_rate * 60 * 60 * 24}') + + yfi_streams = streams.yfi_streams() + + normal_daily_rate = Decimal(5.2950767511632e-07) * 60 * 60 * 24 + print(f'{len(yfi_streams)} active yfi streams') + total_rate = 0 + for i, stream in enumerate(yfi_streams): + rate = stream.amount_per_second + total_rate += rate + if rate != 5.2950767511632e-07: + print(f'stream {i}') + stream.print() + print(f'normal YFI per day: {normal_daily_rate}') + daily_rate = rate * 60 * 60 * 24 + daily_difference = daily_rate - normal_daily_rate + print(f'daily difference: {daily_difference} YFI') + print(f'monthly difference: {daily_difference * 30} YFI') + print(f'recipient: {stream.to_address.address}') + + print('') + print(f'total yfi per second: {total_rate}') + print(f'total yfi per day: {total_rate * 60 * 60 * 24}') + + return process_streams() \ No newline at end of file diff --git a/yearn/utils.py b/yearn/utils.py index 8e6412cca..4479053ba 100644 --- a/yearn/utils.py +++ b/yearn/utils.py @@ -2,27 +2,24 @@ import json import logging import threading -from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta from functools import lru_cache from typing import AsyncGenerator, List +import a_sync +import dank_mids import eth_retry import pandas as pd from brownie import Contract, chain, interface, web3 from brownie.convert.datatypes import HexString from brownie.network.contract import _fetch_from_explorer, _resolve_address -from dank_mids.brownie_patch import patch_contract -from typing_extensions import ParamSpec from y.networks import Network -from y.utils.dank_mids import dank_w3 from yearn.typing import AddressOrContract logger = logging.getLogger(__name__) -threads = ThreadPoolExecutor(8) -run_in_thread = lambda fn, *args: asyncio.get_event_loop().run_in_executor(threads, fn, *args) +threads = a_sync.PruningThreadPoolExecutor(8) BINARY_SEARCH_BARRIER = { Network.Mainnet: 0, @@ -72,13 +69,13 @@ def __call__(self, *args, **kwargs): @eth_retry.auto_retry def contract(address: AddressOrContract) -> Contract: with _contract_lock: - address = web3.toChecksumAddress(str(address)) + address = web3.to_checksum_address(str(address)) if chain.id in PREFER_INTERFACE: if address in PREFER_INTERFACE[chain.id]: _interface = PREFER_INTERFACE[chain.id][address] i = _interface(address) - return _squeeze(patch_contract(i, dank_w3)) + return _squeeze(dank_mids.patch_contract(i)) # autofetch-sources: false # Try to fetch the contract from the local sqlite db. @@ -89,7 +86,7 @@ def contract(address: AddressOrContract) -> Contract: c = _resolve_proxy(address) # Lastly, get rid of unnecessary memory-hog properties - return _squeeze(patch_contract(c, dank_w3)) + return _squeeze(dank_mids.patch_contract(c)) # These tokens have trouble when resolving the implementation via the chain. @@ -151,9 +148,9 @@ def _resolve_proxy(address): df.drop_duplicates(subset=["name", "type"], keep="last", inplace=True) deduplicated = df.to_dict("records") if len(abi) != len(deduplicated): - logger.warn(f"Warning: combined abi for contract {address} contains duplicates!") - logger.warn(f"original:\n{abi}") - logger.warn(f"deduplicated:\n{deduplicated}") + logger.warning(f"Warning: combined abi for contract {address} contains duplicates!") + logger.warning(f"original:\n{abi}") + logger.warning(f"deduplicated:\n{deduplicated}") abi = deduplicated diff --git a/yearn/v1/registry.py b/yearn/v1/registry.py index 41963e170..f7b820c78 100644 --- a/yearn/v1/registry.py +++ b/yearn/v1/registry.py @@ -3,16 +3,14 @@ from functools import cached_property from typing import Dict, List, Optional -from brownie import chain, interface, web3 -from dank_mids.brownie_patch import patch_contract -from y.contracts import contract_creation_block_async -from y.networks import Network -from y.utils.dank_mids import dank_w3 +from brownie import chain, interface +import dank_mids +from y import Contract, Network, contract_creation_block_async +from y._decorators import stuck_coro_debugger from yearn.exceptions import UnsupportedNetwork from yearn.multicall2 import fetch_multicall_async from yearn.typing import Block -from yearn.utils import contract from yearn.v1.vaults import VaultV1 logger = logging.getLogger(__name__) @@ -24,12 +22,12 @@ def __init__(self) -> None: raise UnsupportedNetwork("Vaults V1 registry is only available on Mainnet.") # TODO Fix ENS resolution for registry.ychad.eth - self.registry = patch_contract(interface.YRegistry("0x3eE41C098f9666ed2eA246f4D2558010e59d63A0"), dank_w3) + self.registry = dank_mids.patch_contract(interface.YRegistry("0x3eE41C098f9666ed2eA246f4D2558010e59d63A0")) @cached_property def vaults(self) -> List[VaultV1]: - addresses_provider = contract("0x9be19Ee7Bc4099D62737a7255f5c227fBcd6dB93") - addresses_generator_v1_vaults = contract(addresses_provider.addressById("ADDRESSES_GENERATOR_V1_VAULTS")) + addresses_provider = Contract("0x9be19Ee7Bc4099D62737a7255f5c227fBcd6dB93") + addresses_generator_v1_vaults = Contract(addresses_provider.addressById("ADDRESSES_GENERATOR_V1_VAULTS")) # NOTE: we assume no more v1 vaults are deployed return [VaultV1(vault_address, *self.registry.getVaultInfo(vault_address)) for vault_address in addresses_generator_v1_vaults.assetsAddresses()] @@ -37,6 +35,7 @@ def vaults(self) -> List[VaultV1]: def __repr__(self) -> str: return f"" + @stuck_coro_debugger async def describe(self, block: Optional[Block] = None) -> Dict[str, Dict]: vaults = await self.active_vaults_at(block) share_prices = await fetch_multicall_async(*[[vault.vault, "getPricePerFullShare"] for vault in vaults], block=block) @@ -44,6 +43,7 @@ async def describe(self, block: Optional[Block] = None) -> Dict[str, Dict]: data = await asyncio.gather(*[vault.describe(block=block) for vault in vaults]) return {vault.name: desc for vault, desc in zip(vaults, data)} + @stuck_coro_debugger async def total_value_at(self, block: Optional[Block] = None) -> Dict[str, float]: vaults = await self.active_vaults_at(block) balances = await fetch_multicall_async(*[[vault.vault, "balance"] for vault in vaults], block=block) @@ -52,6 +52,7 @@ async def total_value_at(self, block: Optional[Block] = None) -> Dict[str, float prices = await asyncio.gather(*[vault.get_price(block) for (vault, balance) in vaults]) return {vault.name: balance * price / 10 ** vault.decimals for (vault, balance), price in zip(vaults, prices)} + @stuck_coro_debugger async def active_vaults_at(self, block: Optional[Block] = None) -> List[VaultV1]: if block: blocks = await asyncio.gather(*[contract_creation_block_async(str(vault.vault)) for vault in self.vaults]) diff --git a/yearn/v1/vaults.py b/yearn/v1/vaults.py index c228438ed..2fe538b12 100644 --- a/yearn/v1/vaults.py +++ b/yearn/v1/vaults.py @@ -1,20 +1,20 @@ import asyncio import logging from dataclasses import dataclass -from functools import cached_property from typing import TYPE_CHECKING, Optional +import dank_mids +from async_property import async_cached_property from brownie import ZERO_ADDRESS, interface from brownie.network.contract import InterfaceContainer -from dank_mids.brownie_patch import patch_contract +from y import Contract, magic +from y._decorators import stuck_coro_debugger from y.exceptions import PriceError, yPriceMagicError -from y.prices import magic -from y.utils.dank_mids import dank_w3 from yearn import constants from yearn.common import Tvl from yearn.multicall2 import fetch_multicall_async -from yearn.utils import contract +from yearn.prices.curve import curve from yearn.v1 import constants if TYPE_CHECKING: @@ -36,10 +36,10 @@ class VaultV1: decimals: Optional[int] = None def __post_init__(self): - self.vault = contract(self.vault) - self.controller = contract(self.controller) - self.strategy = contract(self.strategy) - self.token = contract(self.token) + self.vault = Contract(self.vault) + self.controller = Contract(self.controller) + self.strategy = Contract(self.strategy) + self.token = Contract(self.token) if str(self.vault) not in constants.VAULT_ALIASES: logger.warning("no vault alias for %s, reading from vault.sybmol()", self.vault) self.name = constants.VAULT_ALIASES.get(str(self.vault), self.vault.symbol()) @@ -52,6 +52,7 @@ async def get_price(self, block=None): return await magic.get_price(underlying, block=block, sync=False) return await magic.get_price(self.token, block=block, sync=False) + @stuck_coro_debugger async def get_strategy(self, block=None): if self.name in ["aLINK", "LINK"] or block is None: return self.strategy @@ -59,18 +60,20 @@ async def get_strategy(self, block=None): controller = await self.get_controller(block) strategy = await controller.strategies.coroutine(self.token, block_identifier=block) if strategy != ZERO_ADDRESS: - return contract(strategy) + return Contract(strategy) + @stuck_coro_debugger async def get_controller(self, block=None): if block is None: return self.controller - return contract(self.vault.controller(block_identifier=block)) + return await Contract.coroutine(await self.vault.controller.coroutine(block_identifier=block)) - @cached_property - def is_curve_vault(self): - from yearn.prices.curve import curve - return curve.get_pool(str(self.token)) is not None + @async_cached_property + @stuck_coro_debugger + async def is_curve_vault(self): + return await magic.curve.get_pool(str(self.token)) is not None + @stuck_coro_debugger async def describe(self, block=None): info = {} strategy = self.strategy @@ -94,7 +97,7 @@ async def describe(self, block=None): attrs["max"] = [self.vault, "max"] # new curve voter proxy vaults - if self.is_curve_vault and hasattr(strategy, "proxy"): + if await self.is_curve_vault and hasattr(strategy, "proxy"): vote_proxy, gauge = await fetch_multicall_async( [strategy, "voter"], # voter is static, can pin [strategy, "gauge"], # gauge is static per strategy, can cache @@ -103,9 +106,8 @@ async def describe(self, block=None): # guard historical queries where there are no vote_proxy and gauge # for block <= 10635293 (2020-08-11) if vote_proxy and gauge: - from yearn.prices.curve import curve - vote_proxy = patch_contract(interface.CurveYCRVVoter(vote_proxy), dank_w3) - gauge = contract(gauge) + vote_proxy = dank_mids.patch_contract(interface.CurveYCRVVoter(vote_proxy)) + gauge = Contract(gauge) boost, _apy = await asyncio.gather( curve.calculate_boost(gauge, vote_proxy, block=block), curve.calculate_apy(gauge, self.token, block=block), @@ -118,7 +120,7 @@ async def describe(self, block=None): attrs["lifetime earned"] = [strategy, "earned"] # /scale if strategy._name == "StrategyYFIGovernance": - ygov = patch_contract(interface.YearnGovernance(await strategy.gov.coroutine()), dank_w3) + ygov = dank_mids.patch_contract(interface.YearnGovernance(await strategy.gov.coroutine())) attrs["earned"] = [ygov, "earned", strategy] attrs["reward rate"] = [ygov, "rewardRate"] attrs["ygov balance"] = [ygov, "balanceOf", strategy] @@ -140,18 +142,20 @@ async def describe(self, block=None): if "token price" not in info: info["token price"] = float(await self.get_price(block=block)) if info["vault total"] > 0 else 0 - info["tvl"] = info["vault balance"] * info["token price"] + info["tvl"] = info["vault balance"] * float(info["token price"]) return info + @stuck_coro_debugger async def apy(self, samples: "ApySamples"): from yearn import apy from yearn.prices.curve import curve - if curve.get_pool(self.token.address): + if await magic.curve.get_pool(self.token.address): return await apy.curve.simple(self, samples) else: return await apy.v1.simple(self, samples) + @stuck_coro_debugger async def tvl(self, block=None): total_assets = await self.vault.balance.coroutine(block_identifier=block) try: diff --git a/yearn/v2/registry.py b/yearn/v2/registry.py index cfd1d5d3c..dd3ea6d1d 100644 --- a/yearn/v2/registry.py +++ b/yearn/v2/registry.py @@ -1,28 +1,27 @@ import asyncio +import itertools import logging -import threading import time from collections import OrderedDict -from typing import Dict, List +from functools import cached_property +from typing import AsyncIterator, Awaitable, Dict, List, NoReturn, overload +import a_sync +import dank_mids import inflection -from brownie import Contract, chain, web3 -from dank_mids.brownie_patch import patch_contract -from joblib import Parallel, delayed +from async_property import async_cached_property, async_property +from brownie import chain, web3 +from brownie.network.event import _EventItem from web3._utils.abi import filter_by_name from web3._utils.events import construct_event_topic_set -from y.contracts import contract_creation_block_async -from y.exceptions import NodeNotSynced -from y.networks import Network -from y.prices import magic -from y.utils.dank_mids import dank_w3 - -from yearn.decorators import (sentry_catch_all, wait_or_exit_after, - wait_or_exit_before) -from yearn.events import decode_logs, get_logs_asap +from y import Contract, Network, magic +from y._decorators import stuck_coro_debugger +from y.utils.events import Events, ProcessedEvents + +from yearn.decorators import set_exc, wait_or_exit_before from yearn.exceptions import UnsupportedNetwork from yearn.multicall2 import fetch_multicall_async -from yearn.utils import Singleton, contract +from yearn.utils import Singleton from yearn.v2.vaults import Vault logger = logging.getLogger(__name__) @@ -41,118 +40,124 @@ ] } +VaultName = str + class Registry(metaclass=Singleton): - def __init__(self, watch_events_forever=True, include_experimental=True): + def __init__(self, include_experimental=True): self.releases = {} # api_version => template - self._vaults = {} # address -> Vault - self._experiments = {} # address => Vault - self._staking_pools = {} # vault address -> staking_pool address self.governance = None self.tags = {} - self._watch_events_forever = watch_events_forever self.include_experimental = include_experimental - self.registries = self.load_registry() - # load registry state in the background - self._done = threading.Event() - self._has_exception = False - self._thread = threading.Thread(target=self.watch_events, daemon=True) - self._thread.start() - - def load_registry(self): + self._done = a_sync.Event(name=f"{self.__module__}.{self.__class__.__name__}._done") + self._registries = [] + self._vaults = {} # address -> Vault + self._experiments = {} # address => Vault + self._removed = {} + self._staking_pools = {} # vault address -> staking_pool address + + @async_cached_property + @stuck_coro_debugger + async def registries(self) -> List[Contract]: if chain.id == Network.Mainnet: - registries = self.load_from_ens() + registries = await self.load_from_ens() elif chain.id == Network.Gnosis: - registries = [contract('0xe2F12ebBa58CAf63fcFc0e8ab5A61b145bBA3462')] + registries = [await Contract.coroutine('0xe2F12ebBa58CAf63fcFc0e8ab5A61b145bBA3462')] elif chain.id == Network.Fantom: - registries = [contract('0x727fe1759430df13655ddb0731dE0D0FDE929b04')] + registries = [await Contract.coroutine('0x727fe1759430df13655ddb0731dE0D0FDE929b04')] elif chain.id == Network.Arbitrum: - registries = [contract('0x3199437193625DCcD6F9C9e98BDf93582200Eb1f')] + registries = [await Contract.coroutine('0x3199437193625DCcD6F9C9e98BDf93582200Eb1f')] elif chain.id == Network.Optimism: - registries = [ - contract('0x79286Dd38C9017E5423073bAc11F53357Fc5C128'), - contract('0x81291ceb9bB265185A9D07b91B5b50Df94f005BF'), - contract('0x8ED9F6343f057870F1DeF47AaE7CD88dfAA049A8'), # StakingRewardsRegistry - ] + registries = await asyncio.gather(*[ + Contract.coroutine('0x79286Dd38C9017E5423073bAc11F53357Fc5C128'), + Contract.coroutine('0x81291ceb9bB265185A9D07b91B5b50Df94f005BF'), + Contract.coroutine('0x8ED9F6343f057870F1DeF47AaE7CD88dfAA049A8'), # StakingRewardsRegistry + ]) elif chain.id == Network.Base: - registries = [contract('0xF3885eDe00171997BFadAa98E01E167B53a78Ec5')] + registries = [await Contract.coroutine('0xF3885eDe00171997BFadAa98E01E167B53a78Ec5')] else: raise UnsupportedNetwork('yearn v2 is not available on this network') for r in registries[:]: if hasattr(r, 'releaseRegistry') and "ReleaseRegistryUpdated" in r.topics: - logs = get_logs_asap(str(r), [r.topics["ReleaseRegistryUpdated"]]) # Add all past and present Release Registries - for rr in {list(event.values())[0] for event in decode_logs(logs)}: - registries.append(contract(rr)) + events = Events(addresses=r, topics=[r.topics['ReleaseRegistryUpdated']]) + for rr in set(await asyncio.gather(*[ + asyncio.create_task(Contract.coroutine(list(event.values())[0])) + async for event in events.events(to_block=await dank_mids.eth.block_number) + ])): + registries.append(rr) logger.debug("release registry %s found for registry %s", rr, r) + logger.info('registry loaded') + events._task.cancel() return registries - def load_from_ens(self): + @stuck_coro_debugger + async def load_from_ens(self): # track older registries to pull experiments - resolver = contract('0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41') + resolver = await Contract.coroutine('0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41') topics = construct_event_topic_set( filter_by_name('AddressChanged', resolver.abi)[0], web3.codec, {'node': web3.ens.namehash('v2.registry.ychad.eth')}, ) - events = decode_logs(get_logs_asap(str(resolver), topics)) - registries = [contract(event['newAddress'].hex()) for event in events] + events = Events(addresses=resolver, topics=topics) + registries = [ + asyncio.create_task( + coro=Contract.coroutine(event['newAddress'].hex()), + name=f"load registry {event['newAddress']}", + ) + async for event in events.events(to_block = await dank_mids.eth.block_number) + ] + if registries: + registries = await asyncio.gather(*registries) logger.info('loaded %d registry versions', len(registries)) + events._task.cancel() return registries - @property + @async_property + @stuck_coro_debugger @wait_or_exit_before - def vaults(self) -> List[Vault]: + async def vaults(self) -> List[Vault]: return list(self._vaults.values()) - @property + @async_property + @stuck_coro_debugger @wait_or_exit_before - def experiments(self) -> List[Vault]: + async def experiments(self) -> List[Vault]: return list(self._experiments.values()) - @property + @async_property + @stuck_coro_debugger @wait_or_exit_before - def staking_pools(self) -> Dict: - return self._staking_pools + async def removed(self) -> List[Vault]: + return list(self._removed.values()) + @async_property + @stuck_coro_debugger @wait_or_exit_before + async def staking_pools(self) -> Dict: + return self._staking_pools + def __repr__(self) -> str: - return f"" - - @wait_or_exit_after - def load_vaults(self): - if not self._thread._started.is_set(): - self._thread.start() - - @sentry_catch_all - def watch_events(self): - start = time.time() - sleep_time = 300 - from_block = None - height = chain.height - while True: - logs = get_logs_asap([str(addr) for addr in self.registries], None, from_block=from_block, to_block=height) - self.process_events(decode_logs(logs)) - self._filter_vaults() + return f"" + + @set_exc + async def load_events(self) -> NoReturn: + if not self._done.is_set(): + start = time.time() + events: RegistryEvents = await self._events + async for _ in events.events(to_block = await dank_mids.eth.block_number): + self._filter_vaults() if not self._done.is_set(): self._done.set() logger.info("loaded v2 registry in %.3fs", time.time() - start) - if not self._watch_events_forever: - return - time.sleep(sleep_time) - - # set vars for next loop - from_block = height + 1 - height = chain.height - if height < from_block: - raise NodeNotSynced(f"No new blocks in the past {sleep_time/60} minutes.") def process_events(self, events): temp_rekt_vaults = TEMP_REKT_VAULTS.get(chain.id, []) for event in events: if "vault" in event and event["vault"] in temp_rekt_vaults: - logger.warn(f"skipping temp rekt vault {event['vault']}") + logger.warning(f"skipping temp rekt vault {event['vault']}") continue # hack to make camels to snakes @@ -162,7 +167,7 @@ def process_events(self, events): self.governance = event["governance"] if event.name == "NewRelease": - self.releases[event["api_version"]] = contract(event["template"]) + self.releases[event["api_version"]] = Contract(event["template"]) if event.name == "NewVault": # experiment was endorsed @@ -198,27 +203,20 @@ def process_events(self, events): def vault_from_event(self, event): return Vault( - vault=patch_contract(Contract.from_abi("Vault", event["vault"], self.releases[event["api_version"]].abi), dank_w3), + vault=dank_mids.patch_contract(Contract.from_abi("Vault", event["vault"], self.releases[event["api_version"]].abi)), token=event["token"], api_version=event["api_version"], registry=self, - watch_events_forever=self._watch_events_forever, ) - def load_strategies(self): - # stagger loading strategies to not run out of connections in the pool - vaults = self.vaults + self.experiments - Parallel(1, "threading")(delayed(vault.load_strategies)() for vault in vaults) - - def load_harvests(self): - vaults = self.vaults + self.experiments - Parallel(1, "threading")(delayed(vault.load_harvests)() for vault in vaults) - - async def describe(self, block=None): - vaults = await self.active_vaults_at(block) - results = await asyncio.gather(*[vault.describe(block=block) for vault in vaults]) - return {vault.name: result for vault, result in zip(vaults, results)} + @stuck_coro_debugger + async def describe(self, block=None) -> Dict[VaultName, Dict]: + return await a_sync.gather({ + vault.name: asyncio.create_task(vault.describe(block=block)) + async for vault in self.active_vaults_at(block, iter=True) + }) + @stuck_coro_debugger async def total_value_at(self, block=None): vaults = await self.active_vaults_at(block) prices, results = await asyncio.gather( @@ -227,24 +225,119 @@ async def total_value_at(self, block=None): ) return {vault.name: assets * price / vault.scale for vault, assets, price in zip(vaults, results, prices)} - async def active_vaults_at(self, block=None): - vaults = self.vaults + self.experiments - if block: - blocks = await asyncio.gather(*[contract_creation_block_async(str(vault.vault)) for vault in vaults]) - vaults = [vault for vault, deploy_block in zip(vaults, blocks) if deploy_block <= block] - # fixes edge case: a vault is not necessarily initialized on creation - activations = await fetch_multicall_async(*[[vault.vault, 'activation'] for vault in vaults], block=block) - return [vault for vault, activation in zip(vaults, activations) if activation] - + @overload + def active_vaults_at(self, block=None, iter = False) -> Awaitable[List[Vault]]:... + @overload + def active_vaults_at(self, block=None, iter = True) -> AsyncIterator[Vault]:... + def active_vaults_at(self, block=None, iter: bool = False): + if iter: + return self._active_vaults_at_iter(block=block) + else: + return self._active_vaults_at(block=block) + + @stuck_coro_debugger + async def _active_vaults_at(self, block=None) -> List[Vault]: + self._task + events: RegistryEvents = await self._events + await events.events(to_block = block or events._init_block) + self._filter_vaults() + vaults = list(itertools.chain(self._vaults.values(), self._experiments.values())) + return [vault for vault, active in zip(vaults, await asyncio.gather(*[vault.is_active(block) for vault in vaults])) if active] + + async def _active_vaults_at_iter(self, block=None) -> AsyncIterator[Vault]: + # ensure loader task is running + self._task + events: RegistryEvents = await self._events + # make sure the events are loaded thru now before proceeding + await events.events(to_block=block or events._init_block) + self._filter_vaults() + + vaults: List[Vault] = list(itertools.chain(self._vaults.values(), self._experiments.values())) + + i = 0 # TODO figure out why we need this here + while len(vaults) == 0: + await asyncio.sleep(6) + vaults = list(itertools.chain(self._vaults.values(), self._experiments.values())) + i += 1 + if i >= 20: + logger.error("we're stuck") + + async for vault, active in a_sync.as_completed({vault: vault.is_active(block) for vault in vaults}, aiter=True): + if active: + yield vault + + @async_cached_property + async def _events(self) -> "RegistryEvents": + return RegistryEvents(self, await self.registries) + + @cached_property + def _task(self) -> asyncio.Task: + return asyncio.create_task(self.load_events()) + def _filter_vaults(self): - logger.debug('filtering vaults') if chain.id in DEPRECATED_VAULTS: for vault in DEPRECATED_VAULTS[chain.id]: - self._remove_vault(vault) - logger.debug('vaults filtered') + self._remove_vault(vault, save=False) - def _remove_vault(self, address): - self._vaults.pop(address, None) - self._experiments.pop(address, None) + def _remove_vault(self, address, save=True): + v = self._vaults.pop(address, None) + e = self._experiments.pop(address, None) self.tags.pop(address, None) + if save: + if v and e: + raise NotImplementedError(v, e) + if v or e: + self._removed[address] = v or e + else: + raise NotImplementedError(v, e, address) logger.debug('removed %s', address) + + +class RegistryEvents(ProcessedEvents[_EventItem]): + __slots__ = "_init_block", "_registry" + def __init__(self, registry: Registry, registries: List[Contract]): + assert registries, registries + self._init_block = chain.height + self._registry = registry + super().__init__(addresses=registries) + def _process_event(self, event: _EventItem) -> _EventItem: + # hack to make camels to snakes + event._ordered = [OrderedDict({inflection.underscore(k): v for k, v in od.items()}) for od in event._ordered] + logger.debug("starting to process %s for %s: %s", event.name, event.address, dict(event)) + if event.name == "NewGovernance": + self._registry.governance = event["governance"] + + if event.name == "NewRelease": + self._registry.releases[event["api_version"]] = Contract(event["template"]) + + if event.name == "NewVault": + # experiment was endorsed + if event["vault"] in self._registry._experiments: + vault = self._registry._experiments.pop(event["vault"]) + vault.name = f"{vault.vault.symbol()} {event['api_version']}" + self._registry._vaults[event["vault"]] = vault + logger.debug("endorsed vault %s %s", vault.vault, vault.name) + # we already know this vault from another registry + elif event["vault"] not in self._registry._vaults: + vault = self._registry.vault_from_event(event) + vault.name = f"{vault.vault.symbol()} {event['api_version']}" + self._registry._vaults[event["vault"]] = vault + logger.debug("new vault %s %s", vault.vault, vault.name) + + if self._registry.include_experimental and event.name == "NewExperimentalVault": + vault = self._registry.vault_from_event(event) + vault.name = f"{vault.vault.symbol()} {event['api_version']} {event['vault'][:8]}" + self._registry._experiments[event["vault"]] = vault + logger.debug("new experiment %s %s", vault.vault, vault.name) + + if event.name == "VaultTagged": + if event["tag"] == "Removed": + self._registry._remove_vault(event["vault"]) + logger.debug("Removed vault %s", event["vault"]) + else: + self._registry.tags[event["vault"]] = event["tag"] + + if event.name == "StakingPoolAdded": + self._registry._staking_pools[event["token"]] = event["staking_pool"] + logger.debug("done processing %s for %s: %s", event.name, event.address, dict(event)) + return event diff --git a/yearn/v2/strategies.py b/yearn/v2/strategies.py index cf39f6371..e3ad7ae1c 100644 --- a/yearn/v2/strategies.py +++ b/yearn/v2/strategies.py @@ -1,18 +1,17 @@ import logging -import threading -import time from functools import cached_property -from typing import Any, List +from typing import Any, AsyncIterator, List -from brownie import chain +from async_property import async_property +from brownie.network.event import _EventItem from eth_utils import encode_hex, event_abi_to_log_topic from multicall.utils import run_in_subprocess -from y.exceptions import NodeNotSynced +from y import Contract +from y._decorators import stuck_coro_debugger +from y.utils.events import ProcessedEvents -from yearn.decorators import sentry_catch_all, wait_or_exit_after -from yearn.events import decode_logs, get_logs_asap from yearn.multicall2 import fetch_multicall_async -from yearn.utils import contract, safe_views +from yearn.utils import safe_views STRATEGY_VIEWS_SCALED = [ "maxDebtPerHarvest", @@ -30,46 +29,21 @@ logger = logging.getLogger(__name__) -def _unpack_results(views: List[str], results: List[Any], scale: int): - # unpack self.vault.vault.strategies(self.strategy) - info = dict(zip(views, results)) - info.update(results[-1].dict()) - # scale views - for view in STRATEGY_VIEWS_SCALED: - if view in info: - info[view] = (info[view] or 0) / scale - # unwrap structs - for view in info: - if hasattr(info[view], '_dict'): - info[view] = info[view].dict() - return info - class Strategy: - def __init__(self, strategy, vault, watch_events_forever): - self.strategy = contract(strategy) + def __init__(self, strategy, vault): + self.strategy = Contract(strategy) self.vault = vault try: self.name = self.strategy.name() except ValueError: self.name = strategy[:10] self._views = safe_views(self.strategy.abi) - self._harvests = [] - self._topics = [ - [ - encode_hex(event_abi_to_log_topic(event)) - for event in self.strategy.abi - if event["type"] == "event" and event["name"] in STRATEGY_EVENTS - ] - ] - self._watch_events_forever = watch_events_forever - self._done = threading.Event() - self._has_exception = False - self._thread = threading.Thread(target=self.watch_events, daemon=True) + self._events = Harvests(self) - @property - def unique_name(self): - if [strategy.name for strategy in self.vault.strategies].count(self.name) > 1: + @async_property + async def unique_name(self): + if [strategy.name for strategy in await self.vault.strategies].count(self.name) > 1: return f'{self.name} {str(self.strategy)[:8]}' else: return self.name @@ -80,60 +54,55 @@ def __repr__(self) -> str: def __eq__(self, other): if isinstance(other, Strategy): return self.strategy == other.strategy - if isinstance(other, str): return self.strategy == other - raise ValueError("Strategy is only comparable with [Strategy, str]") - @sentry_catch_all - def watch_events(self): - start = time.time() - sleep_time = 300 - from_block = None - height = chain.height - while True: - logs = get_logs_asap(str(self.strategy), topics=self._topics, from_block=from_block, to_block=height) - events = decode_logs(logs) - self.process_events(events) - if not self._done.is_set(): - self._done.set() - logger.info("loaded %d harvests %s in %.3fs", len(self._harvests), self.name, time.time() - start) - if not self._watch_events_forever: - return - time.sleep(sleep_time) - - # read new logs at end of loop - from_block = height + 1 - height = chain.height - if height < from_block: - raise NodeNotSynced(f"No new blocks in the past {sleep_time/60} minutes.") - - - def process_events(self, events): - for event in events: - if event.name == "Harvested": - block = event.block_number - logger.debug("%s harvested on %d", self.name, block) - self._harvests.append(block) - - @wait_or_exit_after - def load_harvests(self): - if not self._thread._started.is_set(): - self._thread.start() - - @property - def harvests(self) -> List[int]: - self.load_harvests() - return self._harvests + async def harvests(self, thru_block: int) -> AsyncIterator[dict]: + async for event in self._events.events(to_block=thru_block): + yield event + @stuck_coro_debugger + async def describe(self, block=None): + results = await fetch_multicall_async(*self._calls, block=block) + return await self._unpack_results(results) + @cached_property def _calls(self): return *[[self.strategy, view] for view in self._views], [self.vault.vault, "strategies", self.strategy], async def _unpack_results(self, results): return await run_in_subprocess(_unpack_results, self._views, results, self.vault.scale) + + +class Harvests(ProcessedEvents[int]): + def __init__(self, strategy: Strategy): + topics = [ + [ + encode_hex(event_abi_to_log_topic(event)) + for event in strategy.strategy.abi + if event["type"] == "event" and event["name"] in STRATEGY_EVENTS + ] + ] + super().__init__(addresses=[str(strategy.strategy)], topics=topics) + self.strategy = strategy + def _include_event(self, event: _EventItem) -> bool: + return event.name == "Harvested" + def _get_block_for_obj(self, block: int) -> int: + return block + # TODO: work this in somehow: + # logger.info("loaded %d harvests %s in %.3fs", len(self._harvests), self.name, time.time() - start) + def _process_event(self, event: _EventItem) -> int: + block = event.block_number + logger.debug("%s harvested on %d", self.strategy.name, block) + return block - async def describe(self, block=None): - results = await fetch_multicall_async(*self._calls, block=block) - return await self._unpack_results(results) + +def _unpack_results(views: List[str], results: List[Any], scale: int): + # unpack self.vault.vault.strategies(self.strategy) + info = dict(zip(views, results)) + info.update(results[-1].dict()) + # scale views + info = {view: (result or 0) / scale if view in STRATEGY_VIEWS_SCALED else result for view, result in info.items()} + # unwrap structs + return {view: result.dict() if hasattr(info[view], '_dict') else result for view, result in info.items()} \ No newline at end of file diff --git a/yearn/v2/vaults.py b/yearn/v2/vaults.py index 917095bdb..31fdd8640 100644 --- a/yearn/v2/vaults.py +++ b/yearn/v2/vaults.py @@ -1,29 +1,31 @@ import asyncio import logging import re -import threading import time +from contextlib import suppress from functools import cached_property -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import (TYPE_CHECKING, Any, AsyncIterator, Dict, List, NoReturn, + Optional, Union) +import a_sync +import dank_mids +from async_property import async_cached_property, async_property from brownie import chain +from brownie.network.event import _EventItem from eth_utils import encode_hex, event_abi_to_log_topic -from joblib import Parallel, delayed from multicall.utils import run_in_subprocess from semantic_version.base import Version from y import ERC20, Contract, Network, magic -from y.exceptions import NodeNotSynced, PriceError, yPriceMagicError -from y.networks import Network -from y.prices import magic -from y.utils.events import get_logs_asap +from y.contracts import contract_creation_block_async +from y._decorators import stuck_coro_debugger +from y.exceptions import ContractNotVerified, PriceError, yPriceMagicError +from y.utils.events import ProcessedEvents from yearn.common import Tvl -from yearn.decorators import sentry_catch_all, wait_or_exit_after -from yearn.events import decode_logs, get_logs_asap from yearn.multicall2 import fetch_multicall_async from yearn.special import Ygov from yearn.typing import Address -from yearn.utils import run_in_thread, safe_views +from yearn.utils import safe_views from yearn.v2.strategies import Strategy if TYPE_CHECKING: @@ -65,6 +67,9 @@ async def get_price_return_exceptions(token, block=None): Network.Mainnet: [ # borked in the vyper exploit of july 2023 "0x718AbE90777F5B778B52D553a5aBaa148DD0dc5D", + # price borked + "0xc5F3D11580c41cD07104e9AF154Fc6428bb93c73", + "0x4213458C69c19E6792510E1153cb0c5834665fdC", ] }.get(chain.id, []) @@ -91,7 +96,7 @@ def _unpack_results(vault: Address, is_experiment: bool, _views: List[str], resu info["token price"] = float(price) if "totalAssets" in info: - info["tvl"] = info["token price"] * info["totalAssets"] + info["tvl"] = float(info["token price"]) * info["totalAssets"] for strategy_name, desc in zip(strategies, strategy_descs): info["strategies"][strategy_name] = desc @@ -101,9 +106,9 @@ def _unpack_results(vault: Address, is_experiment: bool, _views: List[str], resu info["version"] = "v2" return info - + class Vault: - def __init__(self, vault: Contract, api_version=None, token=None, registry=None, watch_events_forever=True): + def __init__(self, vault: Contract, api_version=None, token=None, registry=None): self._strategies: Dict[Address, Strategy] = {} self._revoked: Dict[Address, Strategy] = {} self._reports = [] @@ -111,31 +116,35 @@ def __init__(self, vault: Contract, api_version=None, token=None, registry=None, self.api_version = api_version if token is None: token = vault.token() - self.token = Contract(token) + try: + self.token = Contract(token) + except ContractNotVerified as e: + if token not in [ + "0x08BfA22bB3e024CDfEB3eca53c0cb93bF59c4147", + "0x79586fa680958102154093B795Fdb8EFBc013822", + "0x021cF6B7ebb8c8EFcF21396Eb4c94658976172c7", + ]: + raise Exception(e, f"vault: {vault}") + self.token = token self.registry = registry self.scale = 10 ** self.vault.decimals() # multicall-safe views with 0 inputs and numeric output. self._views = safe_views(self.vault.abi) + self._calls = [[self.vault, view] for view in self._views] # load strategies from events and watch for freshly attached strategies - self._topics = [ - [ - encode_hex(event_abi_to_log_topic(event)) - for event in self.vault.abi - if event["type"] == "event" and event["name"] in STRATEGY_EVENTS - ] - ] - self._watch_events_forever = watch_events_forever - self._done = threading.Event() - self._has_exception = False - self._thread = threading.Thread(target=self.watch_events, daemon=True) + self._events = VaultEvents(self) def __repr__(self): strategies = "..." # don't block if we don't have the strategies loaded - if self._done.is_set(): - strategies = ", ".join(f"{strategy}" for strategy in self.strategies) + with suppress(RuntimeError): # NOTE on RuntimeError, event loop isnt running and task doesn't exist. can't be created, is not complete. no need to check strats. + if self._task.done(): + strategies = ", ".join(f"{strategy}" for strategy in self._strategies) return f'' + def __hash__(self) -> int: + return hash(self.vault.address) + def __eq__(self, other): if isinstance(other, Vault): return self.vault == other.vault @@ -156,127 +165,86 @@ def from_address(cls, address): instance.name = vault.name() return instance - @property - def strategies(self) -> List[Strategy]: - self.load_strategies() + @async_property + @stuck_coro_debugger + async def strategies(self) -> List[Strategy]: + await self.load_strategies() return list(self._strategies.values()) - - @property - def revoked_strategies(self) -> List[Strategy]: - self.load_strategies() + + async def strategies_at_block(self, block: int) -> AsyncIterator[Strategy]: + self._task # ensure fetcher task is running + working = {} + async for _ in self._events.events(to_block=block): + for address in self._strategies: + if address in working: + continue + working[address] = asyncio.create_task(contract_creation_block_async(address, when_no_history_return_0=True)) + async for address, deploy_block in a_sync.as_completed(working, aiter=True): + if deploy_block > block: + continue + while address not in self._strategies: + logger.info('%s not in %s._strategies', address, self) + await asyncio.sleep(5) + yield self._strategies[address] + + @async_property + @stuck_coro_debugger + async def revoked_strategies(self) -> List[Strategy]: + await self.load_strategies() return list(self._revoked.values()) - @property - def reports(self): + @async_property + @stuck_coro_debugger + async def reports(self): # strategy reports are loaded at the same time as other vault strategy events - self.load_strategies() + await self.load_strategies() return self._reports - @property - def is_endorsed(self): + @async_property + @stuck_coro_debugger + async def is_endorsed(self): if not self.registry: return None - return str(self.vault) in self.registry.vaults + return str(self.vault) in await self.registry.vaults - @property - def is_experiment(self): + @async_property + @stuck_coro_debugger + async def is_experiment(self): if not self.registry: return None # experimental vaults are either listed in the registry or have the 0x address suffix in the name - return str(self.vault) in self.registry.experiments or re.search(r"0x.*$", self.name) is not None + return str(self.vault) in await self.registry.experiments or re.search(r"0x.*$", self.name) is not None - @wait_or_exit_after - def load_strategies(self): - if not self._thread._started.is_set(): - self._thread.start() + @stuck_coro_debugger + async def is_active(self, block: Optional[int]) -> bool: + if block and await contract_creation_block_async(str(self.vault)) > block: + return False + # fixes edge case: a vault is not necessarily initialized on creation + return await self.vault.activation.coroutine(block_identifier=block) - def load_harvests(self): - Parallel(1, "threading")(delayed(strategy.load_harvests)() for strategy in self.strategies) + @stuck_coro_debugger + async def load_strategies(self): + await self._task - @sentry_catch_all - def watch_events(self): + async def watch_events(self) -> NoReturn: start = time.time() - sleep_time = 300 - from_block = None - height = chain.height - while True: - logs = get_logs_asap(str(self.vault), topics=self._topics, from_block=from_block, to_block=height) - events = decode_logs(logs) - self.process_events(events) - if not self._done.is_set(): - self._done.set() - logger.info("loaded %d strategies %s in %.3fs", len(self._strategies), self.name, time.time() - start) - if not self._watch_events_forever: - return - time.sleep(sleep_time) - - # set vars for next loop - from_block = height + 1 - height = chain.height - if height < from_block: - raise NodeNotSynced(f"No new blocks in the past {sleep_time/60} minutes.") - - - def process_events(self, events): - for event in events: - # some issues during the migration of this strat prevented it from being verified so we skip it here... - if chain.id == Network.Optimism: - failed_migration = False - for key in ["newVersion", "oldVersion", "strategy"]: - failed_migration |= (key in event and event[key] == "0x4286a40EB3092b0149ec729dc32AD01942E13C63") - if failed_migration: - continue - - if event.name == "StrategyAdded": - strategy_address = event["strategy"] - logger.debug("%s strategy added %s", self.name, strategy_address) - try: - self._strategies[strategy_address] = Strategy(strategy_address, self, self._watch_events_forever) - except ValueError: - logger.error(f"Error loading strategy {strategy_address}") - pass - elif event.name == "StrategyRevoked": - logger.debug("%s strategy revoked %s", self.name, event["strategy"]) - self._revoked[event["strategy"]] = self._strategies.pop( - event["strategy"], Strategy(event["strategy"], self, self._watch_events_forever) - ) - elif event.name == "StrategyMigrated": - logger.debug("%s strategy migrated %s -> %s", self.name, event["oldVersion"], event["newVersion"]) - self._revoked[event["oldVersion"]] = self._strategies.pop( - event["oldVersion"], Strategy(event["oldVersion"], self, self._watch_events_forever) - ) - self._strategies[event["newVersion"]] = Strategy(event["newVersion"], self, self._watch_events_forever) - elif event.name == "StrategyReported": - self._reports.append(event) - - async def _unpack_results(self, results): - results, strategy_descs, price = results - strategies = await run_in_thread(getattr, self, 'strategies') - return await run_in_subprocess( - _unpack_results, - self.vault.address, - self.is_experiment, - self._views, - results, - self.scale, - price, - # must be picklable. - [strategy.unique_name for strategy in strategies], - strategy_descs, - ) + await self._events.events(await dank_mids.eth.block_number) + logger.info("loaded %d strategies %s in %.3fs", len(self._strategies), self.name, time.time() - start) + @stuck_coro_debugger async def describe(self, block=None): - strategies = await run_in_thread(getattr, self, 'strategies') + block = block or await dank_mids.eth.block_number results = await asyncio.gather( - fetch_multicall_async(*[[self.vault, view] for view in self._views], block=block), - asyncio.gather(*[strategy.describe(block=block) for strategy in strategies]), - get_price_return_exceptions(self.token, block=block) + fetch_multicall_async(*self._calls, block=block), + self._describe_strategies(block), + get_price_return_exceptions(self.token, block=block), ) return await self._unpack_results(results) - + + @stuck_coro_debugger async def apy(self, samples: "ApySamples"): from yearn import apy - if self._needs_curve_simple: + if await self._needs_curve_simple: return await apy.curve.simple(self, samples) elif pool := await apy.velo.get_staking_pool(self.token.address): return await apy.velo.staking(self, pool, samples) @@ -287,6 +255,7 @@ async def apy(self, samples: "ApySamples"): else: return await apy.v2.simple(self, samples) + @stuck_coro_debugger async def tvl(self, block=None): total_assets = await self.vault.totalAssets.coroutine(block_identifier=block) try: @@ -304,8 +273,13 @@ async def tvl(self, block=None): return Tvl(total_assets, price, tvl) @cached_property - def _needs_curve_simple(self): - from yearn.prices.curve import curve + def _task(self) -> asyncio.Task: + """The daemon task that loads events for this vault. Starts when first accessed.""" + return asyncio.create_task(self.watch_events()) + + @async_cached_property + @stuck_coro_debugger + async def _needs_curve_simple(self): # some curve vaults which should not be calculated with curve logic curve_simple_excludes = { Network.Arbitrum: [ @@ -316,4 +290,66 @@ def _needs_curve_simple(self): if chain.id in curve_simple_excludes: needs_simple = self.vault.address not in curve_simple_excludes[chain.id] - return needs_simple and curve and curve.get_pool(self.token.address) + return needs_simple and magic.curve and await magic.curve.get_pool(self.token.address) + + @stuck_coro_debugger + async def _describe_strategies(self, block: int) -> List[dict]: + return await asyncio.gather(*[asyncio.create_task(strategy.describe(block=block)) async for strategy in self.strategies_at_block(block)]) + + @stuck_coro_debugger + async def _unpack_results(self, results): + # TODO: get rid of this + results, strategy_descs, price = results + return await run_in_subprocess( + _unpack_results, + self.vault.address, + await self.is_experiment, + self._views, + results, + self.scale, + price, + await asyncio.gather(*[strategy.unique_name for strategy in await self.strategies]), + strategy_descs, + ) + + +class VaultEvents(ProcessedEvents[_EventItem]): + __slots__ = "vault", + def __init__(self, vault: Vault, **kwargs: Any): + topics = [[encode_hex(event_abi_to_log_topic(event)) for event in vault.vault.abi if event["type"] == "event" and event["name"] in STRATEGY_EVENTS]] + super().__init__(addresses=[str(vault.vault)], topics=topics, **kwargs) + self.vault = vault + def _process_event(self, event: _EventItem) -> _EventItem: + # some issues during the migration of this strat prevented it from being verified so we skip it here... + try: + if chain.id == Network.Optimism: + failed_migration = False + for key in ["newVersion", "oldVersion", "strategy"]: + failed_migration |= (key in event and event[key] == "0x4286a40EB3092b0149ec729dc32AD01942E13C63") + if failed_migration: + return event + + if event.name == "StrategyAdded": + strategy_address = event["strategy"] + logger.debug("%s strategy added %s", self.vault.name, strategy_address) + try: + self.vault._strategies[strategy_address] = Strategy(strategy_address, self.vault) + except ValueError: + logger.error(f"Error loading strategy {strategy_address}") + elif event.name == "StrategyRevoked": + logger.debug("%s strategy revoked %s", self.vault.name, event["strategy"]) + self.vault._revoked[event["strategy"]] = self.vault._strategies.pop( + event["strategy"], Strategy(event["strategy"], self.vault) + ) + elif event.name == "StrategyMigrated": + logger.debug("%s strategy migrated %s -> %s", self.vault.name, event["oldVersion"], event["newVersion"]) + self.vault._revoked[event["oldVersion"]] = self.vault._strategies.pop( + event["oldVersion"], Strategy(event["oldVersion"], self.vault) + ) + self.vault._strategies[event["newVersion"]] = Strategy(event["newVersion"], self.vault) + elif event.name == "StrategyReported": + self.vault._reports.append(event) + return event + except Exception as e: + logger.exception(e) + raise e \ No newline at end of file diff --git a/yearn/yearn.py b/yearn/yearn.py index 7f0ec550c..1265d8209 100644 --- a/yearn/yearn.py +++ b/yearn/yearn.py @@ -6,6 +6,7 @@ from brownie import chain from y.contracts import contract_creation_block_async +from y._decorators import stuck_coro_debugger from y.networks import Network import yearn.iearn @@ -28,37 +29,32 @@ class Yearn: Can describe all products. """ - def __init__(self, load_strategies=True, load_harvests=False, load_transfers=False, watch_events_forever=True, exclude_ib_tvl=True) -> None: + def __init__(self, exclude_ib_tvl=True) -> None: start = time() if chain.id == Network.Mainnet: self.registries = { "earn": yearn.iearn.Registry(), "v1": yearn.v1.registry.Registry(), - "v2": yearn.v2.registry.Registry(watch_events_forever=watch_events_forever), + "v2": yearn.v2.registry.Registry(), "ib": yearn.ironbank.Registry(exclude_ib_tvl=exclude_ib_tvl), "special": yearn.special.Registry(), } elif chain.id in [Network.Gnosis, Network.Base]: self.registries = { - "v2": yearn.v2.registry.Registry(watch_events_forever=watch_events_forever), + "v2": yearn.v2.registry.Registry(), } elif chain.id in [Network.Fantom, Network.Arbitrum, Network.Optimism]: self.registries = { - "v2": yearn.v2.registry.Registry(watch_events_forever=watch_events_forever), + "v2": yearn.v2.registry.Registry(), "ib": yearn.ironbank.Registry(exclude_ib_tvl=exclude_ib_tvl), } else: raise UnsupportedNetwork('yearn is not supported on this network') self.exclude_ib_tvl = exclude_ib_tvl - - if load_strategies: - self.registries["v2"].load_strategies() - if load_harvests: - self.registries["v2"].load_harvests() logger.info('loaded yearn in %.3fs', time() - start) - + @stuck_coro_debugger async def active_vaults_at(self, block=None): active_vaults_by_registry = await asyncio.gather(*[registry.active_vaults_at(block) for registry in self.registries.values()]) active = [vault for registry in active_vaults_by_registry for vault in registry] @@ -71,14 +67,14 @@ async def active_vaults_at(self, block=None): return active - + @stuck_coro_debugger async def describe(self, block=None): if block is None: block = chain.height desc = await asyncio.gather(*[self.registries[key].describe(block=block) for key in self.registries]) return dict(zip(self.registries, desc)) - + @stuck_coro_debugger async def describe_wallets(self, block=None): from yearn.outputs.describers.registry import RegistryWalletDescriber describer = RegistryWalletDescriber() @@ -102,12 +98,12 @@ async def describe_wallets(self, block=None): data.update(agg_stats) return data - + @stuck_coro_debugger async def total_value_at(self, block=None): desc = await asyncio.gather(*[self.registries[key].total_value_at(block=block) for key in self.registries]) return dict(zip(self.registries, desc)) - + @stuck_coro_debugger async def data_for_export(self, block, timestamp) -> List: start = time() data = await self.describe(block) @@ -183,7 +179,7 @@ async def data_for_export(self, block, timestamp) -> List: return metrics_to_export - + @stuck_coro_debugger async def wallet_data_for_export(self, block: int, timestamp: int): data = await self.describe_wallets(block) metrics_to_export = [] diff --git a/yearn/yeth.py b/yearn/yeth.py index 98e50d885..590b9f283 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -7,24 +7,19 @@ from pprint import pformat from typing import Optional, AsyncIterator +import dank_mids import eth_retry from brownie import chain from brownie.network.event import _EventItem - -from y import Contract, Network, magic -from y.contracts import contract_creation_block_async -from y.datatypes import Block +from y import Block, Contract, Network, magic, contract_creation_block_async, get_block_timestamp_async, get_block_at_timestamp from y.exceptions import PriceError, yPriceMagicError -from y.time import get_block_timestamp_async, closest_block_after_timestamp from y.utils.events import Events -from y.utils.dank_mids import dank_w3 from yearn.apy.common import (SECONDS_PER_WEEK, SECONDS_PER_YEAR, Apy, ApyFees, ApySamples, SharePricePoint, calculate_roi, get_samples) from yearn.common import Tvl from yearn.debug import Debug -from yearn.events import decode_logs, get_logs_asap from yearn.prices.constants import weth from yearn.utils import Singleton @@ -163,7 +158,7 @@ async def _get_lst_data(self, block=None): async def apy(self, samples: ApySamples) -> Apy: now_rate, week_ago_rate = await asyncio.gather( RATE_PROVIDER.rate.coroutine(str(self.lst), block_identifier=samples.now), - RATE_PROVIDER.rate(str(self.lst), block_identifier=samples.week_ago), + RATE_PROVIDER.rate.coroutine(str(self.lst), block_identifier=samples.week_ago), ) now_rate /= 1e18 week_ago_rate /= 1e18 @@ -264,18 +259,18 @@ async def _get_swap_volumes(self, from_block, to_block): } async def describe(self, block=None): + # we'll start this processing early because it can take a while if block: to_block = block block_timestamp = await get_block_timestamp_async(block) now_time = datetime.fromtimestamp(block_timestamp, tz=timezone.utc) else: - to_block = await dank_w3.eth.block_number + to_block = await dank_mids.eth.block_number now_time = datetime.today() - - from_block = self._get_from_block(now_time) - - self.swap_volumes = await self._get_swap_volumes(from_block, to_block) - products = await self.active_vaults_at(block) + products, self.swap_volumes = await asyncio.gather( + self.active_vaults_at(block), + self._get_swap_volumes(await self._get_from_block(now_time), to_block), + ) data = await asyncio.gather(*[product.describe(block=block) for product in products]) return {product.name: desc for product, desc in zip(products, data)} @@ -291,26 +286,25 @@ async def active_vaults_at(self, block=None): products = [product for product, deploy_block in zip(products, blocks) if deploy_block <= block] return products - def _get_from_block(self, now_time): + async def _get_from_block(self, now_time): from_time = None match self.resolution: case "1d": - from_time = (now_time - timedelta(days=1)).timestamp() + delta = timedelta(days=1) case "1h": - from_time = (now_time - timedelta(hours=1)).timestamp() + delta = timedelta(hours=1) case "30m": - from_time = (now_time - timedelta(minutes=30)).timestamp() + delta = timedelta(minutes=30) case "15m": - from_time = (now_time - timedelta(minutes=15)).timestamp() + delta = timedelta(minutes=15) case "5m": - from_time = (now_time - timedelta(minutes=5)).timestamp() + delta = timedelta(minutes=5) case "1m": - from_time = (now_time - timedelta(minutes=1)).timestamp() + delta = timedelta(minutes=1) case "30s": - from_time = (now_time - timedelta(seconds=30)).timestamp() + delta = timedelta(seconds=30) case "15s": - from_time = (now_time - timedelta(seconds=15)).timestamp() + delta = timedelta(seconds=15) case _: raise Exception(f"Invalid resolution {self.resolution} specified!") - - return closest_block_after_timestamp(int(from_time), True) + return await get_block_at_timestamp(now_time - delta, sync=False)