diff --git a/poetry.lock b/poetry.lock index bba48036..62c3e8f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -359,148 +359,145 @@ dev = ["base58", "mypy", "pylint", "pytest", "pytest-cov"] [[package]] name = "bitarray" -version = "3.0.0" +version = "3.1.0" description = "efficient arrays of booleans -- C extension" optional = false python-versions = "*" files = [ - {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ddbf71a97ad1d6252e6e93d2d703b624d0a5b77c153b12f9ea87d83e1250e0c"}, - {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0e7f24a0b01e6e6a0191c50b06ca8edfdec1988d9d2b264d669d2487f4f4680"}, - {file = "bitarray-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:150b7b29c36d9f1a24779aea723fdfc73d1c1c161dc0ea14990da27d4e947092"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8330912be6cb8e2fbfe8eb69f82dee139d605730cadf8d50882103af9ac83bb4"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e56ba8be5f17dee0ffa6d6ce85251e062ded2faa3cbd2558659c671e6c3bf96d"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd94b4803811c738e504a4b499fb2f848b2f7412d71e6b517508217c1d7929d"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0255bd05ec7165e512c115423a5255a3f301417973d20a80fc5bfc3f3640bcb"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe606e728842389943a939258809dc5db2de831b1d2e0118515059e87f7bbc1a"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e89ea59a3ed86a6eb150d016ed28b1bedf892802d0ed32b5659d3199440f3ced"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cf0cc2e91dd38122dec2e6541efa99aafb0a62e118179218181eff720b4b8153"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d9fe3ee51afeb909b68f97e14c6539ace3f4faa99b21012e610bbe7315c388d"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:37be5482b9df3105bad00fdf7dc65244e449b130867c3879c9db1db7d72e508b"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0027b8f3bb2bba914c79115e96a59b9924aafa1a578223a7c4f0a7242d349842"}, - {file = "bitarray-3.0.0-cp310-cp310-win32.whl", hash = "sha256:628f93e9c2c23930bd1cfe21c634d6c84ec30f45f23e69aefe1fcd262186d7bb"}, - {file = "bitarray-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0b655c3110e315219e266b2732609fddb0857bc69593de29f3c2ba74b7d3f51a"}, - {file = "bitarray-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:44c3e78b60070389b824d5a654afa1c893df723153c81904088d4922c3cfb6ac"}, - {file = "bitarray-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:545d36332de81e4742a845a80df89530ff193213a50b4cbef937ed5a44c0e5e5"}, - {file = "bitarray-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9eb510cde3fa78c2e302bece510bf5ed494ec40e6b082dec753d6e22d5d1b1"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e3727ab63dfb6bde00b281934e2212bb7529ea3006c0031a556a84d2268bea5"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2055206ed653bee0b56628f6a4d248d53e5660228d355bbec0014bdfa27050ae"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:147542299f458bdb177f798726e5f7d39ab8491de4182c3c6d9885ed275a3c2b"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f761184b93092077c7f6b7dad7bd4e671c1620404a76620da7872ceb576a94"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e008b7b4ce6c7f7a54b250c45c28d4243cc2a3bbfd5298fa7dac92afda229842"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfea514e665af278b2e1d4deb542de1cd4f77413bee83dd15ae16175976ea8d5"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:66d6134b7bb737b88f1d16478ad0927c571387f6054f4afa5557825a4c1b78e2"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3cd565253889940b4ec4768d24f101d9fe111cad4606fdb203ea16f9797cf9ed"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4800c91a14656789d2e67d9513359e23e8a534c8ee1482bb9b517a4cfc845200"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c2945e0390d1329c585c584c6b6d78be017d9c6a1288f9c92006fe907f69cc28"}, - {file = "bitarray-3.0.0-cp311-cp311-win32.whl", hash = "sha256:c23286abba0cb509733c6ce8f4013cd951672c332b2e184dbefbd7331cd234c8"}, - {file = "bitarray-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca79f02a98cbda1472449d440592a2fe2ad96fe55515a0447fa8864a38017cf8"}, - {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d"}, - {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9"}, - {file = "bitarray-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a"}, - {file = "bitarray-3.0.0-cp312-cp312-win32.whl", hash = "sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c"}, - {file = "bitarray-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999"}, - {file = "bitarray-3.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7cb885c043000924554fe2124d13084c8fdae03aec52c4086915cd4cb87fe8be"}, - {file = "bitarray-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7814c9924a0b30ecd401f02f082d8697fc5a5be3f8d407efa6e34531ff3c306a"}, - {file = "bitarray-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcf524a087b143ba736aebbb054bb399d49e77cf7c04ed24c728e411adc82bfa"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1d5abf1d6d910599ac16afdd9a0ed3e24f3b46af57f3070cf2792f236f36e0b"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9929051feeaf8d948cc0b1c9ce57748079a941a1a15c89f6014edf18adaade84"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96cf0898f8060b2d3ae491762ae871b071212ded97ff9e1e3a5229e9fefe544c"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab37da66a8736ad5a75a58034180e92c41e864da0152b84e71fcc253a2f69cd4"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeb79e476d19b91fd6a3439853e4e5ba1b3b475920fa40d62bde719c8af786f"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f75fc0198c955d840b836059bd43e0993edbf119923029ca60c4fc017cefa54a"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f12cc7c7638074918cdcc7491aff897df921b092ffd877227892d2686e98f876"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dbe1084935b942fab206e609fa1ed3f46ad1f2612fb4833e177e9b2a5e006c96"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ac06dd72ee1e1b6e312504d06f75220b5894af1fb58f0c20643698f5122aea76"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00f9a88c56e373009ac3c73c55205cfbd9683fbd247e2f9a64bae3da78795252"}, - {file = "bitarray-3.0.0-cp313-cp313-win32.whl", hash = "sha256:9c6e52005e91803eb4e08c0a08a481fb55ddce97f926bae1f6fa61b3396b5b61"}, - {file = "bitarray-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb98d5b6eac4b2cf2a5a69f60a9c499844b8bea207059e9fc45c752436e6bb49"}, - {file = "bitarray-3.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eb27c01b747649afd7e1c342961680893df6d8d81f832a6f04d8c8e03a8a54cc"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4683bff52f5a0fd523fb5d3138161ef87611e63968e1fcb6cf4b0c6a86970fe0"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb7302dbcfcb676f0b66f15891f091d0233c4fc23e1d4b9dc9b9e958156e347f"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:153d7c416a70951dcfa73487af05d2f49c632e95602f1620cd9a651fa2033695"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251cd5bd47f542893b2b61860eded54f34920ea47fd5bff038d85e7a2f7ae99b"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fa4b4d9fa90124b33b251ef74e44e737021f253dc7a9174e1b39f097451f7ca"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:18abdce7ab5d2104437c39670821cba0b32fdb9b2da9e6d17a4ff295362bd9dc"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:2855cc01ee370f7e6e3ec97eebe44b1453c83fb35080313145e2c8c3c5243afb"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:0cecaf2981c9cd2054547f651537b4f4939f9fe225d3fc2b77324b597c124e40"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:22b00f65193fafb13aa644e16012c8b49e7d5cbb6bb72825105ff89aadaa01e3"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:20f30373f0af9cb583e4122348cefde93c82865dbcbccc4997108b3d575ece84"}, - {file = "bitarray-3.0.0-cp36-cp36m-win32.whl", hash = "sha256:aef404d5400d95c6ec86664df9924bde667c8865f8e33c9b7bd79823d53b3e5d"}, - {file = "bitarray-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ec5b0f2d13da53e0975ac15ecbe8badb463bdb0bebaa09457f4df3320421915c"}, - {file = "bitarray-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:041c889e69c847b8a96346650e50f728b747ae176889199c49a3f31ae1de0e23"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc83ea003dd75e9ade3291ef0585577dd5524aec0c8c99305c0aaa2a7570d6db"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c33129b49196aa7965ac0f16fcde7b6ad8614b606caf01669a0277cef1afe1d"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ef5c787c8263c082a73219a69eb60a500e157a4ac69d1b8515ad836b0e71fb4"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e15c94d79810c5ab90ddf4d943f71f14332890417be896ca253f21fa3d78d2b1"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cd021ada988e73d649289cee00428b75564c46d55fbdcb0e3402e504b0ae5ea"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7f1c24be7519f16a47b7e2ad1a1ef73023d34d8cbe1a3a59b185fc14baabb132"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:000df24c183011b5d27c23d79970f49b6762e5bb5aacd25da9c3e9695c693222"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:42bf1b222c698b467097f58b9f59dc850dfa694dde4e08237407a6a103757aa3"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:648e7ce794928e8d11343b5da8ecc5b910af75a82ea1a4264d5d0a55c3785faa"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f536fc4d1a683025f9caef0bebeafd60384054579ffe0825bb9bd8c59f8c55b8"}, - {file = "bitarray-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:a754c1464e7b946b1cac7300c582c6fba7d66e535cd1dab76d998ad285ac5a37"}, - {file = "bitarray-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e91d46d12781a14ccb8b284566b14933de4e3b29f8bc5e1c17de7a2001ad3b5b"}, - {file = "bitarray-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:904c1d5e3bd24f0c0d37a582d2461312033c91436a6a4f3bdeeceb4bea4a899d"}, - {file = "bitarray-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47ccf9887bd595d4a0536f2310f0dcf89e17ab83b8befa7dc8727b8017120fda"}, - {file = "bitarray-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:71ad0139c95c9acf4fb62e203b428f9906157b15eecf3f30dc10b55919225896"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e002ac1073ac70e323a7a4bfa9ab95e7e1a85c79160799e265563f342b1557"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acc07211a59e2f245e9a06f28fa374d094fb0e71cf5366eef52abbb826ddc81e"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98a4070ddafabddaee70b2aa7cc6286cf73c37984169ab03af1782da2351059a"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7d09ef06ba57bea646144c29764bf6b870fb3c5558ca098191e07b6a1d40bf7"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce249ed981f428a8b61538ca82d3875847733d579dd40084ab8246549160f8a4"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea40e98d751ed4b255db4a88fe8fb743374183f78470b9e9305aab186bf28ede"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:928b8b6dfcd015e1a81334cfdac02815da2a2407854492a80cf8a3a922b04052"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fbb645477595ce2a0fbb678d1cfd08d3b896e5d56196d40fb9e114eeab9382b3"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:dc1937a0ff2671797d35243db4b596329842480d125a65e9fe964bcffaf16dfc"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a4f49ac31734fe654a68e2515c0da7f5bbdf2d52755ba09a42ac406f1f08c9d0"}, - {file = "bitarray-3.0.0-cp38-cp38-win32.whl", hash = "sha256:6d2a2ce73f9897268f58857ad6893a1a6680c5a6b28f79d21c7d33285a5ae646"}, - {file = "bitarray-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b1047999f1797c3ea7b7c85261649249c243308dcf3632840d076d18fa72f142"}, - {file = "bitarray-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:39b38a3d45dac39d528c87b700b81dfd5e8dc8e9e1a102503336310ef837c3fd"}, - {file = "bitarray-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0e104f9399144fab6a892d379ba1bb4275e56272eb465059beef52a77b4e5ce6"}, - {file = "bitarray-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0879f839ec8f079fa60c3255966c2e1aa7196699a234d4e5b7898fbc321901b5"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9502c2230d59a4ace2fddfd770dad8e8b414cbd99517e7e56c55c20997c28b8d"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57d5ef854f8ec434f2ffd9ddcefc25a10848393fe2976e2be2c8c773cf5fef42"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3c36b2fcfebe15ad1c10a90c1d52a42bebe960adcbce340fef867203028fbe7"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66a33a537e781eac3a352397ce6b07eedf3a8380ef4a804f8844f3f45e335544"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa54c7e1da8cf4be0aab941ea284ec64033ede5d6de3fd47d75e77cafe986e9d"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a667ea05ba1ea81b722682276dbef1d36990f8908cf51e570099fd505a89f931"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d756bfeb62ca4fe65d2af7a39249d442c05070c047d03729ad6cd4c2e9b0f0bd"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c9e9fef0754867d88e948ce8351c9fd7e507d8514e0f242fd67c907b9cdf98b3"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:67a0b56dd02f2713f6f52cacb3f251afd67c94c5f0748026d307d87a81a8e15c"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d8c36ddc1923bcc4c11b9994c54eaae25034812a42400b7b8a86fe6d242166a2"}, - {file = "bitarray-3.0.0-cp39-cp39-win32.whl", hash = "sha256:1414a7102a3c4986f241480544f5c99f5d32258fb9b85c9c04e84e48c490ab35"}, - {file = "bitarray-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:8c9733d2ff9b7838ac04bf1048baea153174753e6a47312be14c83c6a395424b"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fef4e3b3f2084b4dae3e5316b44cda72587dcc81f68b4eb2dbda1b8d15261b61"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e9eee03f187cef1e54a4545124109ee0afc84398628b4b32ebb4852b4a66393"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb5702dd667f4bb10fed056ffdc4ddaae8193a52cd74cb2cdb54e71f4ef2dd1"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:666e44b0458bb2894b64264a29f2cc7b5b2cbcc4c5e9cedfe1fdbde37a8e329a"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c756a92cf1c1abf01e56a4cc40cb89f0ff9147f2a0be5b557ec436a23ff464d8"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e51e7f8289bf6bb631e1ef2a8f5e9ca287985ff518fe666abbdfdb6a848cb26"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa5d8e4b28388b337face6ce4029be73585651a44866901513df44be9a491ab"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3963b80a68aedcd722a9978d261ae53cb9bb6a8129cc29790f0f10ce5aca287a"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b555006a7dea53f6bebc616a4d0249cecbf8f1fadf77860120a2e5dbdc2f167"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4ac2027ca650a7302864ed2528220d6cc6921501b383e9917afc7a2424a1e36d"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf90aba4cff9e72e24ecdefe33bad608f147a23fa5c97790a5bab0e72fe62b6d"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a199e6d7c3bad5ba9d0e4dc00dde70ee7d111c9dfc521247fa646ef59fa57e"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43b6c7c4f4a7b80e86e24a76f4c6b9b67d03229ea16d7d403520616535c32196"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fc13da3518f14825b239374734fce93c1a9299ed7b558c3ec1d659ec7e4c70"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:369b6d457af94af901d632c7e625ca6caf0a7484110fc91c6290ce26bc4f1478"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ee040ad3b7dfa05e459713099f16373c1f2a6f68b43cb0575a66718e7a5daef4"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dad7ba2af80f9ec1dd988c3aca7992408ec0d0b4c215b65d353d95ab0070b10"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4839d3b64af51e4b8bb4a602563b98b9faeb34fd6c00ed23d7834e40a9d080fc"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f71f24b58e75a889b9915e3197865302467f13e7390efdea5b6afc7424b3a2ea"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bcf0150ae0bcc4aa97bdfcb231b37bad1a59083c1b5012643b266012bf420e68"}, - {file = "bitarray-3.0.0.tar.gz", hash = "sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03"}, + {file = "bitarray-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1fad6456993f604726dcb21d1f003430988d5138f858d79e5b8682f56dfad6"}, + {file = "bitarray-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0dc0f28340929fffa17fbd3eca5bfae5c1827f3000c0bd9312999d8c5b1464a0"}, + {file = "bitarray-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84de57891a80944e259956fd72bd542290b76c063182f0e85ce5380c49cfcfb6"}, + {file = "bitarray-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3f4be402e7c611a0a7048f3d042b9e4d697ca6f721d08098118d1ae8bb8a69c"}, + {file = "bitarray-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eabaee3c7d411f6ed48f92752e61a409530a0ebf65498bd408432800a804e0b8"}, + {file = "bitarray-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cb6c270c49d6779af97586a955977493146626ae34710a4c1ec84cb0115f4d4"}, + {file = "bitarray-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:481a00d710c7811b38563f6fe22da20be2e6f722196f68873e12d23f4fdf82c1"}, + {file = "bitarray-3.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b81bbefe9d205af66a15a3097f8b66f29fe767fa9cb5dd5a7881f53d2505c2d5"}, + {file = "bitarray-3.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:506df7b12e1380f56cfa20b9c203518afe585f0b86e850a96e3691cf0175e4c0"}, + {file = "bitarray-3.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:43ec740770723de6c99f0a858807ef905664f31cf254e0b803f3dd2797537d7a"}, + {file = "bitarray-3.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4deac3da54c3df136d6615a2410d5a6170353b4d08142273266296c376fb1889"}, + {file = "bitarray-3.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c58cbd0cc9f6347fffa36a6870fac695cd6e98c39fcfd8002d44a70fba03cd8c"}, + {file = "bitarray-3.1.0-cp310-cp310-win32.whl", hash = "sha256:e092a5c4cc9ae6bc757bc1fee8894cd0275f0a0689c8f57555b00e198cdbcecc"}, + {file = "bitarray-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:453395201790f16b22092c25bed6ecf3bafee7f36db840d4cfc07cd9b6f9628f"}, + {file = "bitarray-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a1cb97bdc51e3db8def039607ca42d12c6e55fb134a8d211cca2fcf0a7b3a986"}, + {file = "bitarray-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92d8130efa6bebd4903b70f6d98832aabbd78c1031bc64c7c9d4e39b9db3bfa6"}, + {file = "bitarray-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a9559ab050618902f8b8055c5b0b78dfc2a7974656fddee26b033ec56945f7"}, + {file = "bitarray-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2c57a4e8f0d9eb5cef7c83c8a4fd8bf06c35d641fc3cb24bf9511adb54e2d8a"}, + {file = "bitarray-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9bcaa1764d0d302050a31b0f6879ea20e3900b2e73b4ea647ab570950c2062b"}, + {file = "bitarray-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:769184a57abfdfc24c37bb5d0f3836ffe65c7c5cdbeec431766520cd246a27c6"}, + {file = "bitarray-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f247387bb58b8c626088be2fa4ca5869fb12638b45acd29fab21aee48529560"}, + {file = "bitarray-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1303604e4069699cef3e969f31d6c3e8e5a0bc637569d90b3647469eff91dc7b"}, + {file = "bitarray-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f238c2eb7a0a6a50537e5d182edb629901af94746854108483f0b11b0e3d1a78"}, + {file = "bitarray-3.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a96b9b63348d9e5cca70fc31b5d0f07a420a70ef89b376aa4148b87d24b39"}, + {file = "bitarray-3.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5255fb9e4e4879adea5fb61cbde347767d346e29822a51ca21816f28340f4cdb"}, + {file = "bitarray-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:68bab51a827abc6f037ea76bb374ec28804a47b51716f9eb3f7da3d754bc8431"}, + {file = "bitarray-3.1.0-cp311-cp311-win32.whl", hash = "sha256:7ff061c714ff62357ff3be23d96c73944ad9181b8b21d49c29ed82fb2fb0efa4"}, + {file = "bitarray-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:b2f8aed62de760be852e0dee0ad507e0fe17fc12f83b36cc0bfe4900e0a0e915"}, + {file = "bitarray-3.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4c298fefda9dfe2ef24aee8fe48acc3a1a063b266791a814330c6932248785c4"}, + {file = "bitarray-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9211fb3a109ba76cb8bd0f76645d256f8a46e733693de22d0d7857941603cee"}, + {file = "bitarray-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6bfe0a618301f0afed9004b8230a413ad399b58fef50a744a623461b96d8e3"}, + {file = "bitarray-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2db21d733e06d0d5b86fd3c848a84b8930e71250542aa7602e2c048b96fce163"}, + {file = "bitarray-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3807c38b37a500583eafd4ae866d2a051834acd4e11cd8557af9cf5379fa7e21"}, + {file = "bitarray-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcd15f43e846bfdaac8e5420006eba2d6adce29b6336492cf9d651e4a313ea86"}, + {file = "bitarray-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc2266e15f02d3192d1c2d29a790a4eccb230e23de0de6136815b32f9cdd5b4d"}, + {file = "bitarray-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54a6a8525bc66228ed9cc69d562979319ef151dba2ed302bf7a21c7e124c137b"}, + {file = "bitarray-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ffb6b3a6efdabe6a4f3042b16a68499711a7835e950847a5643f60a43de3335f"}, + {file = "bitarray-3.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:430302c3ca7ed0dbb629db87e24a3be082851d71777787d4c8ad424624363780"}, + {file = "bitarray-3.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d4277999453fa63e96ed8a31698c9ac64ee5f38bd5c63205d69d9b73780fa152"}, + {file = "bitarray-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8fab5053ff281954718dc56c44ee7d1462ccc7e296b84da504b72b01c6c0c41"}, + {file = "bitarray-3.1.0-cp312-cp312-win32.whl", hash = "sha256:598072dbe456cc57270c6f34fa6b9aafd56bb5291e0d14f8509c1522f6d77f32"}, + {file = "bitarray-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:4fed8491a77f9fa5303bf6a24a76fcafbf1b496bb22d72b213b673ef8ce6e201"}, + {file = "bitarray-3.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ea4a888e45fdabc89705837b10030a990ec4879d14fccc3b9450ab56ca9d7ec"}, + {file = "bitarray-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2695c23b9857105631e9d4f8ebbcc8a4e55c6c9f31bc80d9e5e018e377d650d8"}, + {file = "bitarray-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a1ffc6318cdcbf91e8492596004bb1572ce3c703aa42c16c8ac1188625b8d"}, + {file = "bitarray-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1221d5d1255a74397f938ff822d8df4b0cc416b74e5659d21c3fe5f3d06590a1"}, + {file = "bitarray-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91e9309912700ab085cf78d162ad0bf0d3de35e5e084868400672b4ce3befbb0"}, + {file = "bitarray-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c319f77118bac6aded58a0d2f172ac1b06e00bfa5391033848864e39bfd771"}, + {file = "bitarray-3.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08004c22af11a234a73a8edf744524fb67e89ab4c80791f647c522687ddcb4b9"}, + {file = "bitarray-3.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afb4d8876fff11cced4377f055498fd3c179e6e2bf7165fd055dabc494005252"}, + {file = "bitarray-3.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7c228c78bd20f2a46a2f3f2e349292c604ae26210dad56afe4b64ea65f72db75"}, + {file = "bitarray-3.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:139115998ffcffd8b883e1f21ced6f1d96947c5ace51f9656ceccedea5765557"}, + {file = "bitarray-3.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7e3ade382c9a5d5d77635041dd2aaa4e59326c8f62e1fb33ce28ddaeaa6ae7b6"}, + {file = "bitarray-3.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5efdb6403d206068d434caf9deb7ab936732c06e5617a63e213c94ac750303f4"}, + {file = "bitarray-3.1.0-cp313-cp313-win32.whl", hash = "sha256:69721fef0427a7f56ed7eef16ff9ddec96c64cee4d7abb5e7511c9aedebafc0f"}, + {file = "bitarray-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:34d75765b5c558e7bffa247493412831c6edb5c40e639a6ac94999bc6f40a6c8"}, + {file = "bitarray-3.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6b85d01d8cf30937d9f55d2029a0f7575e9b559d606e6fff17bb79c2830e0afb"}, + {file = "bitarray-3.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acdac94fa2853e74503248b4079a30f3c09ab993f1b2a393fd88071824bdb0de"}, + {file = "bitarray-3.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f851a69521240418fc1ab6b313ef5ef6533e451a46184077b1f5e410441c7d9a"}, + {file = "bitarray-3.1.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf6be47880210416d3cd2405fd9a5f28b8fd1896b77acf729024f64b4ecd2676"}, + {file = "bitarray-3.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf4d0759d431953324de2ab251b954123bd4dfa83c990370814a88306f1ffc53"}, + {file = "bitarray-3.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0e1e3836468b62c079a69dbbfcdb8f0abb8dc8540f5cfca48147a8178cc95ea"}, + {file = "bitarray-3.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:0b380164b3b2ff5aa833ee2cf00855645308f14694546ca02beca6b9069e3c75"}, + {file = "bitarray-3.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:de8ce30fb9e9d2fa063abf8f9443d7bdf77b5de52a5cffbfd007f6150e6bee96"}, + {file = "bitarray-3.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:72b0f7cbc5c0bf008fde06b7777474a942cc0aa9c743dafc583a1dc430a07122"}, + {file = "bitarray-3.1.0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:ccd384e435d9c540c7e54c9149abbf462691e8a6c9287448d44da9f22230e09b"}, + {file = "bitarray-3.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:37d8da058472b2fedd27903781d5ea27264cde11dcb81a3237724427de83f241"}, + {file = "bitarray-3.1.0-cp36-cp36m-win32.whl", hash = "sha256:f78e2a4fd88b6fa383f44ca866cf6b5e1c0370de7ace7a0b4af7b3f11d806558"}, + {file = "bitarray-3.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a394cfcd6d9f7fac71429df8e8c4b93b1f0db3ef7a06eb84d9f69d901b862ee4"}, + {file = "bitarray-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:756da43ac44b1cbf576a02b3e9710fa24f14823b38df95868ea8a3fea840c725"}, + {file = "bitarray-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:638026c90fb135f0014a47bc5e9d5ed8acff8bbc3283f05e811e9f84a1f32a0e"}, + {file = "bitarray-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f90b57ea9aa9c6159c4a9110e8f787194583ede08b6ff5a6ea36c17b8973b75"}, + {file = "bitarray-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93cb9b8e2cda9006d489bfa8e7f4fe5680860b6ff45fa9081a6554a9e661fa97"}, + {file = "bitarray-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2225ce0383a2e6950865f178513d8939c89782e909cbcfe4dd7c4f572b1e5a1"}, + {file = "bitarray-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf22a95774a3d04200605bef8f436867df3ec87a13033fc482cce7ce17f62644"}, + {file = "bitarray-3.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:226297bb0a9f6636c3b74cfa39a1feec7fbd26c6b59fd9d5b78c4066a87aa571"}, + {file = "bitarray-3.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f4c28344774fadb554489f3a8c7a9c75237a85996a8a6d5d91ffa781bdd7b1ff"}, + {file = "bitarray-3.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:ce05239a8422ca3ebb1f00cf3577a1d255f07f7dd4c2ab20e8e009da393b86f3"}, + {file = "bitarray-3.1.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:9ce0f80904644ddbdad42eb1eb3945c19bc274b8b0e0844c7f7b9effafd279d0"}, + {file = "bitarray-3.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:c467bf00939b162144862d167dd55a651e44c256805f25e44edbd547fc0b0922"}, + {file = "bitarray-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:90b714bc59a9dd11d1bba0d68dd395a3e51f275c8547e9b68af561763a61196e"}, + {file = "bitarray-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4cbbe82edbe2f93f2598042d208caa87969025537ddce34cf647e4da6c4f6af4"}, + {file = "bitarray-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:34dd123c69a7f8520c20b45351df1d169429b9e636425c3c8278fdde80151fe6"}, + {file = "bitarray-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:03ae745110d4e343d78f5c3836058d510222b17469b1eb64362cdba7cbdfa26a"}, + {file = "bitarray-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfef13ba01363e185f3c7ffd8dc84c109a2be7da4d035b289c8f8a94b02c8183"}, + {file = "bitarray-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:798a71af5e1cded785dabc2f03bf65cd021a7e0ea54d8874a49cbb64669694e2"}, + {file = "bitarray-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d5eb9d2389c76469c4f10d636a79bcd13142d863a5d07a962738ae55fa60dfa"}, + {file = "bitarray-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a0a9c6a9fe679cd48204c719329de0ce72dede6f8e862744adcaf85b1d0c4c6"}, + {file = "bitarray-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97cb1a6cf3538ecda5ce537111f246f468919ca38ff6cfe188f99ee3353c8f2"}, + {file = "bitarray-3.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6d49b0748e9aa0b3bcec4074675405d6a1b19288fb0e8ad8bc6db95c66840747"}, + {file = "bitarray-3.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b57959f7538b93cb058aa8c467cfd8b82c0af88a2168a4f382ff77e3a90274ee"}, + {file = "bitarray-3.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:752e2de8b0094d56a6e98516609f4d2b9a2027d1a9038de3f021a7d1d13d0599"}, + {file = "bitarray-3.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5b90b46cc73fc2db871985ae93132c7945f264aea6000348a124ba84b48af980"}, + {file = "bitarray-3.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:491d5c5b34b00c971be98b36736e090c847b0e2904a1b097a32bb02226fb11ba"}, + {file = "bitarray-3.1.0-cp38-cp38-win32.whl", hash = "sha256:515d954219d9b81b6442ab270cb8142100f32eac06f00f0c588da6c1416c9a1f"}, + {file = "bitarray-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:40c3f234b515aa8cfce0adc1df2b9c3b45e4307f8b756c9f3069cbc3effb5acb"}, + {file = "bitarray-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fdd73f8f563b1f0d106c09949bd90b1104464b5cd148c02b7dae4e3efdb1dcd"}, + {file = "bitarray-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c609504ce985754867a91db78fb1d5110df589447233badb5a1d453fb2e1714"}, + {file = "bitarray-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff398a6850da105e5cf13e4297f1bd181d90c16a4db9ce20e0608e1edf9c81b0"}, + {file = "bitarray-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39f32b9c1b03f2c2cec002513ef91a9818cf85e8da94069e430d71256c0b858c"}, + {file = "bitarray-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d6e940dfefe604ecbd77849b107319c73f51dfb24ae18c80710dad9bdb59244"}, + {file = "bitarray-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19c3a7f80b286bd4df0fcbddbe97fd8b48028c0497678ee1f0bab820978295bd"}, + {file = "bitarray-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47b6cbbecd24d3b943cef3e704dbf9b510dec310251e1c279d10105cb40a33d8"}, + {file = "bitarray-3.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ab0063f8264cae001fc24238bf8b90a424be25b952951b749b647fd615011428"}, + {file = "bitarray-3.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:86544ffdf8d0cb7271acb5c3a560f4c7850f4c11057cc9c9acdadbea125aa68a"}, + {file = "bitarray-3.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:56ce6219922715fa8f9c65a40a504d4a5e64504ed7c4f3fac64071117559ef33"}, + {file = "bitarray-3.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:56fac415c19689b327f22ca6c02ffa908009f21aab931230d292d5239de40094"}, + {file = "bitarray-3.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e47bee13f43dfc5c5c3661e70b350a5dd37f62f25e196c9a20f504ca02774ac6"}, + {file = "bitarray-3.1.0-cp39-cp39-win32.whl", hash = "sha256:df4ea0d3035298716550b20396d831bc5871efbba7f9cc8e84eabd0906c0163e"}, + {file = "bitarray-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7c96ecf342bf6093523477cf1eacd958df5206564aef347d3d2d8d4541a1c6a0"}, + {file = "bitarray-3.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c0790bbbe08b141d6a93061e68d1954c86374ca4982e5586becdf7dc0d61e95c"}, + {file = "bitarray-3.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dde6268797d6e3f0639b1a5048eee5e42aa63fd00c4477741f9a5b1720f1aa8f"}, + {file = "bitarray-3.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea7791224248fe8ad6f5533877f94850b5636cf957f245a2f4854da21d0be766"}, + {file = "bitarray-3.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7319e68ad1cb081dd7d1b2fe1242c12fe8f9ee3c78812db010b18e9753f7bb"}, + {file = "bitarray-3.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3aab1395cd4a23b1815f87120e59e1a66a5f3e92cbe42e4653b9fae1320e0eeb"}, + {file = "bitarray-3.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:729ede2a88c0b379080606f2dd6ff4b4fc1798683f1fb244d79e929b298784c4"}, + {file = "bitarray-3.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fe0998dfa82776c17c262b20c2ff735f310aed4c85dcc2614ed389815a6bb4ef"}, + {file = "bitarray-3.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19f7b08cabfeed0190fbfcf7aad2cd71bb6934f319bd9f195b138616ebc56edf"}, + {file = "bitarray-3.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba0aede899cd78952977db93cf24f5845163efbe85e4220de95298976d3c54b"}, + {file = "bitarray-3.1.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6370b01839750fcb3aea8639344fb3898f73dac42abe344bc5e39669b93fe110"}, + {file = "bitarray-3.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:37901e3b417ae482858c29be7b963ef1e6cf0ac16f03f4852295aee1aa77ac18"}, + {file = "bitarray-3.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7eb06912a72f5728864808a68abc1e247bc24c9983a1f3e400f9954245715fac"}, + {file = "bitarray-3.1.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b9fe9c40c13c0c796bee8d6416185c59f270eb9d22dfb7f6b1cadd0d8be3b4c8"}, + {file = "bitarray-3.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44b93406e302f1077c9b4c4b990a40a7afbebab66e4579dce2b06d7f55d8e7c7"}, + {file = "bitarray-3.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:984803aa15421f3bade7c05fd44ab6acdcebe16b646f3201f5ccf1a111ba7661"}, + {file = "bitarray-3.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d39a36b6b95889a4cbc1d3083053cebe3315c13ff9a8760c14a3fe9cd8a102b1"}, + {file = "bitarray-3.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:41282599c51e26bc491a3d1471a785a0158c529a2e99757a353a708a9df66655"}, + {file = "bitarray-3.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4653e4cab6001a97c6689a4bfe3517128a74656eaa5df22f6ce01fd9b264db30"}, + {file = "bitarray-3.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d6ef7906263c12a27361ec06e89180a01f8ac57ffa0e1cdf7953948505fea4e"}, + {file = "bitarray-3.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:868e0a4fc33333b9cfaa9a0c6b847ce64e7d2064162c2183608244a649487d0a"}, + {file = "bitarray-3.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64123ae24065478a17d2c6ed08e7e9ea6131be054326a01c9b13f5e36ac1a95"}, + {file = "bitarray-3.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dcceb74c288777e86dbf2497e0bfc444aecf1e710e8a030ea4dba13600ac066"}, + {file = "bitarray-3.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:244897669375d345fe67cd5c0701ec5ea81991a9193fa8cfa4bcb55277375eff"}, + {file = "bitarray-3.1.0.tar.gz", hash = "sha256:71757171a45eac58782861c49137ba3bed0da489155311857f69f4e9baf81fa4"}, ] [[package]] @@ -1438,13 +1435,13 @@ test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-x [[package]] name = "ethpm-types" -version = "0.6.23" +version = "0.6.24" description = "ethpm_types: Implementation of EIP-2678" optional = false python-versions = "<4,>=3.9" files = [ - {file = "ethpm_types-0.6.23-py3-none-any.whl", hash = "sha256:37894a5a54afcd7b7ec5c4f67f0101a2b756811b1e9d058ea37daa75f70f2b27"}, - {file = "ethpm_types-0.6.23.tar.gz", hash = "sha256:4338d423e28ea45829b176d47fae1544d12f58b5786fcd9d1090e6a39cfb1acf"}, + {file = "ethpm_types-0.6.24-py3-none-any.whl", hash = "sha256:9cdd40d3343ee65b7d67b2f19e6f8a028fd67bc411724d5aea5590bfbea9bc6b"}, + {file = "ethpm_types-0.6.24.tar.gz", hash = "sha256:d668e87dc18523bdf8631471a60c8a8d2f0d9f1d31c5ba71f3cf69b88b9de1da"}, ] [package.dependencies] diff --git a/prediction_market_agent_tooling/deploy/agent.py b/prediction_market_agent_tooling/deploy/agent.py index 2639b504..72106b68 100644 --- a/prediction_market_agent_tooling/deploy/agent.py +++ b/prediction_market_agent_tooling/deploy/agent.py @@ -522,6 +522,7 @@ class DeployableTraderAgent(DeployablePredictionAgent): MarketType.OMEN, MarketType.MANIFOLD, MarketType.POLYMARKET, + MarketType.SEER, ] def __init__( diff --git a/prediction_market_agent_tooling/deploy/betting_strategy.py b/prediction_market_agent_tooling/deploy/betting_strategy.py index f1ec7f1e..391c1ff8 100644 --- a/prediction_market_agent_tooling/deploy/betting_strategy.py +++ b/prediction_market_agent_tooling/deploy/betting_strategy.py @@ -13,7 +13,6 @@ Trade, TradeType, ) -from prediction_market_agent_tooling.markets.omen.data_models import get_boolean_outcome from prediction_market_agent_tooling.markets.omen.omen import ( get_buy_outcome_token_amount, ) @@ -94,11 +93,8 @@ def _build_rebalance_trades_from_positions( sell price is higher. """ trades = [] - for outcome in [ - market.get_outcome_str_from_bool(True), - market.get_outcome_str_from_bool(False), - ]: - outcome_bool = get_boolean_outcome(outcome) + for outcome_bool in [True, False]: + outcome = market.get_outcome_str_from_bool(outcome_bool) prev_amount: TokenAmount = ( existing_position.amounts[outcome] if existing_position and outcome in existing_position.amounts diff --git a/prediction_market_agent_tooling/markets/agent_market.py b/prediction_market_agent_tooling/markets/agent_market.py index 40afe29c..eecee98a 100644 --- a/prediction_market_agent_tooling/markets/agent_market.py +++ b/prediction_market_agent_tooling/markets/agent_market.py @@ -284,8 +284,7 @@ def has_successful_resolution(self) -> bool: def has_unsuccessful_resolution(self) -> bool: return self.resolution in [Resolution.CANCEL, Resolution.MKT] - @staticmethod - def get_outcome_str_from_bool(outcome: bool) -> OutcomeStr: + def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr: raise NotImplementedError("Subclasses must implement this method") def get_outcome_str(self, outcome_index: int) -> str: diff --git a/prediction_market_agent_tooling/markets/blockchain_utils.py b/prediction_market_agent_tooling/markets/blockchain_utils.py new file mode 100644 index 00000000..10da1bff --- /dev/null +++ b/prediction_market_agent_tooling/markets/blockchain_utils.py @@ -0,0 +1,82 @@ +from web3 import Web3 +from web3.constants import HASH_ZERO + +from prediction_market_agent_tooling.config import APIKeys +from prediction_market_agent_tooling.gtypes import ( + ChecksumAddress, + HexBytes, + HexStr, + xDai, + xdai_type, +) +from prediction_market_agent_tooling.loggers import logger +from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket +from prediction_market_agent_tooling.markets.omen.data_models import ( + ContractPrediction, + IPFSAgentResult, +) +from prediction_market_agent_tooling.markets.omen.omen_contracts import ( + OmenAgentResultMappingContract, +) +from prediction_market_agent_tooling.tools.balances import get_balances +from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler +from prediction_market_agent_tooling.tools.utils import BPS_CONSTANT +from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32 + + +def get_total_balance( + address: ChecksumAddress, + web3: Web3 | None = None, + sum_xdai: bool = True, + sum_wxdai: bool = True, +) -> xDai: + """ + Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance. + """ + current_balances = get_balances(address, web3) + # xDai and wxDai have equal value and can be exchanged for almost no cost, so we can sum them up. + total_balance = 0.0 + if sum_xdai: + total_balance += current_balances.xdai + if sum_wxdai: + total_balance += current_balances.wxdai + return xdai_type(total_balance) + + +def store_trades( + market_id: str, + traded_market: ProcessedTradedMarket | None, + keys: APIKeys, + agent_name: str, +) -> None: + if traded_market is None: + logger.warning(f"No prediction for market {market_id}, not storing anything.") + return + + reasoning = traded_market.answer.reasoning if traded_market.answer.reasoning else "" + + ipfs_hash_decoded = HexBytes(HASH_ZERO) + if keys.enable_ipfs_upload: + logger.info("Storing prediction on IPFS.") + ipfs_hash = IPFSHandler(keys).store_agent_result( + IPFSAgentResult(reasoning=reasoning, agent_name=agent_name) + ) + ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash) + + tx_hashes = [ + HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None + ] + prediction = ContractPrediction( + publisher=keys.bet_from_address, + ipfs_hash=ipfs_hash_decoded, + tx_hashes=tx_hashes, + estimated_probability_bps=int(traded_market.answer.p_yes * BPS_CONSTANT), + ) + tx_receipt = OmenAgentResultMappingContract().add_prediction( + api_keys=keys, + market_address=Web3.to_checksum_address(market_id), + prediction=prediction, + ) + logger.info( + f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}." + ) diff --git a/prediction_market_agent_tooling/markets/data_models.py b/prediction_market_agent_tooling/markets/data_models.py index 4ddaa0e0..5a770c46 100644 --- a/prediction_market_agent_tooling/markets/data_models.py +++ b/prediction_market_agent_tooling/markets/data_models.py @@ -9,6 +9,7 @@ class Currency(str, Enum): xDai = "xDai" + sDai = "sDai" Mana = "Mana" USDC = "USDC" diff --git a/prediction_market_agent_tooling/markets/markets.py b/prediction_market_agent_tooling/markets/markets.py index 2b0e8007..10e76498 100644 --- a/prediction_market_agent_tooling/markets/markets.py +++ b/prediction_market_agent_tooling/markets/markets.py @@ -28,6 +28,7 @@ from prediction_market_agent_tooling.markets.polymarket.polymarket import ( PolymarketAgentMarket, ) +from prediction_market_agent_tooling.markets.seer.seer import SeerAgentMarket from prediction_market_agent_tooling.tools.utils import ( DatetimeUTC, should_not_happen, @@ -41,6 +42,7 @@ class MarketType(str, Enum): MANIFOLD = "manifold" POLYMARKET = "polymarket" METACULUS = "metaculus" + SEER = "seer" @property def market_class(self) -> type[AgentMarket]: @@ -56,7 +58,7 @@ def job_class(self) -> type[JobAgentMarket]: @property def is_blockchain_market(self) -> bool: - return self in [MarketType.OMEN, MarketType.POLYMARKET] + return self in [MarketType.OMEN, MarketType.POLYMARKET, MarketType.SEER] MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = { @@ -64,6 +66,7 @@ def is_blockchain_market(self) -> bool: MarketType.OMEN: OmenAgentMarket, MarketType.POLYMARKET: PolymarketAgentMarket, MarketType.METACULUS: MetaculusAgentMarket, + MarketType.SEER: SeerAgentMarket, } JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = { diff --git a/prediction_market_agent_tooling/markets/omen/omen.py b/prediction_market_agent_tooling/markets/omen/omen.py index 29f9a5cf..df26926d 100644 --- a/prediction_market_agent_tooling/markets/omen/omen.py +++ b/prediction_market_agent_tooling/markets/omen/omen.py @@ -4,7 +4,6 @@ import tenacity from web3 import Web3 -from web3.constants import HASH_ZERO from prediction_market_agent_tooling.config import APIKeys from prediction_market_agent_tooling.gtypes import ( @@ -28,6 +27,10 @@ ProcessedTradedMarket, SortBy, ) +from prediction_market_agent_tooling.markets.blockchain_utils import ( + get_total_balance, + store_trades, +) from prediction_market_agent_tooling.markets.data_models import ( Bet, BetAmount, @@ -42,9 +45,7 @@ PRESAGIO_BASE_URL, Condition, ConditionPreparationEvent, - ContractPrediction, CreatedMarket, - IPFSAgentResult, OmenBet, OmenMarket, OmenUserPosition, @@ -55,7 +56,6 @@ OMEN_DEFAULT_MARKET_FEE_PERC, REALITY_DEFAULT_FINALIZATION_TIMEOUT, Arbitrator, - OmenAgentResultMappingContract, OmenConditionalTokenContract, OmenFixedProductMarketMakerContract, OmenFixedProductMarketMakerFactoryContract, @@ -74,7 +74,6 @@ ) from prediction_market_agent_tooling.tools.custom_exceptions import OutOfFundsError from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes -from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler from prediction_market_agent_tooling.tools.tokens.auto_deposit import ( auto_deposit_collateral_token, ) @@ -82,7 +81,6 @@ auto_withdraw_collateral_token, ) from prediction_market_agent_tooling.tools.utils import ( - BPS_CONSTANT, DatetimeUTC, calculate_sell_amount_in_collateral, check_not_none, @@ -90,7 +88,6 @@ from prediction_market_agent_tooling.tools.web3_utils import ( add_fraction, get_receipt_block_timestamp, - ipfscidv0_to_byte32, remove_fraction, wei_to_xdai, xdai_to_wei, @@ -206,7 +203,7 @@ def place_bet( self, outcome: bool, amount: BetAmount, - omen_auto_deposit: bool = True, + auto_deposit: bool = True, web3: Web3 | None = None, api_keys: APIKeys | None = None, ) -> str: @@ -222,7 +219,7 @@ def place_bet( amount=amount_xdai, market=self, binary_outcome=outcome, - auto_deposit=omen_auto_deposit, + auto_deposit=auto_deposit, web3=web3, ) @@ -434,38 +431,11 @@ def store_trades( keys: APIKeys, agent_name: str, ) -> None: - if traded_market is None: - logger.warning(f"No prediction for market {self.id}, not storing anything.") - return - - reasoning = ( - traded_market.answer.reasoning if traded_market.answer.reasoning else "" - ) - - ipfs_hash_decoded = HexBytes(HASH_ZERO) - if keys.enable_ipfs_upload: - logger.info("Storing prediction on IPFS.") - ipfs_hash = IPFSHandler(keys).store_agent_result( - IPFSAgentResult(reasoning=reasoning, agent_name=agent_name) - ) - ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash) - - tx_hashes = [ - HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None - ] - prediction = ContractPrediction( - publisher=keys.bet_from_address, - ipfs_hash=ipfs_hash_decoded, - tx_hashes=tx_hashes, - estimated_probability_bps=int(traded_market.answer.p_yes * BPS_CONSTANT), - ) - tx_receipt = OmenAgentResultMappingContract().add_prediction( - api_keys=keys, - market_address=Web3.to_checksum_address(self.id), - prediction=prediction, - ) - logger.info( - f"Added prediction to market {self.id}. - receipt {tx_receipt['transactionHash'].hex()}." + return store_trades( + market_id=self.id, + traded_market=traded_market, + keys=keys, + agent_name=agent_name, ) @staticmethod @@ -516,8 +486,7 @@ def index_set_to_outcome_str(cls, index_set: int) -> OutcomeStr: cls.get_outcome_str(cls.index_set_to_outcome_index(index_set)) ) - @staticmethod - def get_outcome_str_from_bool(outcome: bool) -> OutcomeStr: + def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr: return ( OutcomeStr(OMEN_TRUE_OUTCOME) if outcome else OutcomeStr(OMEN_FALSE_OUTCOME) ) @@ -1302,25 +1271,6 @@ def get_binary_market_p_yes_history(market: OmenAgentMarket) -> list[Probability return history -def get_total_balance( - address: ChecksumAddress, - web3: Web3 | None = None, - sum_xdai: bool = True, - sum_wxdai: bool = True, -) -> xDai: - """ - Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance. - """ - current_balances = get_balances(address, web3) - # xDai and wxDai have equal value and can be exchanged for almost no cost, so we can sum them up. - total_balance = 0.0 - if sum_xdai: - total_balance += current_balances.xdai - if sum_wxdai: - total_balance += current_balances.wxdai - return xdai_type(total_balance) - - def withdraw_wxdai_to_xdai_to_keep_balance( api_keys: APIKeys, min_required_balance: xDai, diff --git a/prediction_market_agent_tooling/markets/seer/data_models.py b/prediction_market_agent_tooling/markets/seer/data_models.py index 6dbfa081..9755c01c 100644 --- a/prediction_market_agent_tooling/markets/seer/data_models.py +++ b/prediction_market_agent_tooling/markets/seer/data_models.py @@ -1,10 +1,26 @@ +import re import typing as t +from enum import Enum +from urllib.parse import urljoin -from eth_typing import HexAddress from pydantic import BaseModel, ConfigDict, Field +from web3 import Web3 from web3.constants import ADDRESS_ZERO -from prediction_market_agent_tooling.gtypes import HexBytes, Wei +from prediction_market_agent_tooling.config import RPCConfig +from prediction_market_agent_tooling.gtypes import ( + ChecksumAddress, + HexAddress, + HexBytes, + Probability, + Wei, + xdai_type, +) +from prediction_market_agent_tooling.loggers import logger +from prediction_market_agent_tooling.markets.data_models import Resolution +from prediction_market_agent_tooling.tools.cow.cow_manager import CowManager +from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC +from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei class CreateCategoricalMarketsParams(BaseModel): @@ -30,21 +46,184 @@ class CreateCategoricalMarketsParams(BaseModel): token_names: list[str] = Field(..., alias="tokenNames") +class SeerOutcomeEnum(str, Enum): + YES = "yes" + NO = "no" + INVALID = "invalid" + + @classmethod + def from_bool(cls, value: bool) -> "SeerOutcomeEnum": + return cls.YES if value else cls.NO + + @classmethod + def from_string(cls, value: str) -> "SeerOutcomeEnum": + """Convert a string (case-insensitive) to an Outcome enum.""" + normalized = value.strip().lower() + patterns = { + r"^yes$": cls.YES, + r"^no$": cls.NO, + r"^(invalid|invalid result)$": cls.INVALID, + } + + # Search through patterns and return the first match + for pattern, outcome in patterns.items(): + if re.search(pattern, normalized): + return outcome + + raise ValueError(f"Could not map {value=} to an outcome.") + + def to_bool(self) -> bool: + """Convert a SeerOutcomeEnum to a boolean value.""" + if self == self.YES: + return True + elif self == self.NO: + return False + elif self == self.INVALID: + raise ValueError("Cannot convert INVALID outcome to boolean.") + else: + raise ValueError(f"Unknown outcome: {self}") + + class SeerParentMarket(BaseModel): id: HexBytes +SEER_BASE_URL = "https://app.seer.pm" + + class SeerMarket(BaseModel): model_config = ConfigDict(populate_by_name=True) id: HexBytes + creator: HexAddress title: str = Field(alias="marketName") outcomes: list[str] - wrapped_tokens: list[HexBytes] = Field(alias="wrappedTokens") + wrapped_tokens: list[HexAddress] = Field(alias="wrappedTokens") parent_outcome: int = Field(alias="parentOutcome") parent_market: t.Optional[SeerParentMarket] = Field( alias="parentMarket", default=None ) + collateral_token: HexAddress = Field(alias="collateralToken") + condition_id: HexBytes = Field(alias="conditionId") + opening_ts: int = Field(alias="openingTs") + block_timestamp: int = Field(alias="blockTimestamp") + has_answers: bool | None = Field(alias="hasAnswers") + payout_reported: bool = Field(alias="payoutReported") + payout_numerators: list[int] = Field(alias="payoutNumerators") + + @property + def has_valid_answer(self) -> bool: + # We assume that, for the market to be resolved as invalid, it must have both: + # 1. An invalid outcome AND + # 2. Invalid payoutNumerator is 1. + + try: + self.outcome_as_enums[SeerOutcomeEnum.INVALID] + except KeyError: + raise ValueError( + f"Market {self.id.hex()} has no invalid outcome. {self.outcomes}" + ) + + return self.payout_reported and self.payout_numerators[-1] != 1 + + @property + def outcome_as_enums(self) -> dict[SeerOutcomeEnum, int]: + return { + SeerOutcomeEnum.from_string(outcome): idx + for idx, outcome in enumerate(self.outcomes) + } + + @property + def is_resolved(self) -> bool: + return self.payout_reported + + @property + def is_resolved_with_valid_answer(self) -> bool: + return self.is_resolved and self.has_valid_answer + + def get_resolution_enum(self) -> t.Optional[Resolution]: + if not self.is_resolved_with_valid_answer: + return None + + max_idx = self.payout_numerators.index(1) + + outcome: str = self.outcomes[max_idx] + outcome_enum = SeerOutcomeEnum.from_string(outcome) + if outcome_enum.to_bool(): + return Resolution.YES + return Resolution.NO + + @property + def is_binary(self) -> bool: + # 3 because Seer has also third, `Invalid` outcome. + return len(self.outcomes) == 3 + + def boolean_outcome_from_answer(self, answer: HexBytes) -> bool: + if not self.is_binary: + raise ValueError( + f"Market with title {self.title} is not binary, it has {len(self.outcomes)} outcomes." + ) + + outcome: str = self.outcomes[answer.as_int()] + outcome_enum = SeerOutcomeEnum.from_string(outcome) + return outcome_enum.to_bool() + + def get_resolution_enum_from_answer(self, answer: HexBytes) -> Resolution: + if self.boolean_outcome_from_answer(answer): + return Resolution.YES + else: + return Resolution.NO + + @property + def collateral_token_contract_address_checksummed(self) -> ChecksumAddress: + return Web3.to_checksum_address(self.collateral_token) + + @property + def close_time(self) -> DatetimeUTC: + return DatetimeUTC.to_datetime_utc(self.opening_ts) + + @property + def created_time(self) -> DatetimeUTC: + return DatetimeUTC.to_datetime_utc(self.block_timestamp) + + @property + def current_p_yes(self) -> Probability: + price_data = {} + for idx in range(len(self.outcomes)): + wrapped_token = self.wrapped_tokens[idx] + price = self._get_price_for_token( + token=Web3.to_checksum_address(wrapped_token) + ) + price_data[idx] = price + + if sum(price_data.values()) == 0: + logger.warning( + f"Could not get p_yes for market {self.id.hex()}, all price quotes are 0." + ) + return Probability(0) + + yes_idx = self.outcome_as_enums[SeerOutcomeEnum.YES] + price_yes = price_data[yes_idx] / sum(price_data.values()) + return Probability(price_yes) + + def _get_price_for_token(self, token: ChecksumAddress) -> float: + collateral_exchange_amount = xdai_to_wei(xdai_type(1)) + try: + quote = CowManager().get_quote( + collateral_token=self.collateral_token_contract_address_checksummed, + buy_token=token, + sell_amount=collateral_exchange_amount, + ) + except Exception as e: + logger.warning(f"Could not get quote for {token=}, returning price 0. {e=}") + return 0 + + return collateral_exchange_amount / float(quote.quote.buyAmount.root) + + @property + def url(self) -> str: + chain_id = RPCConfig().chain_id + return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}") class SeerToken(BaseModel): diff --git a/prediction_market_agent_tooling/markets/seer/seer.py b/prediction_market_agent_tooling/markets/seer/seer.py index 743dea8e..0dfcf790 100644 --- a/prediction_market_agent_tooling/markets/seer/seer.py +++ b/prediction_market_agent_tooling/markets/seer/seer.py @@ -1,22 +1,290 @@ +import typing as t + from eth_typing import ChecksumAddress from web3 import Web3 from web3.types import TxReceipt from prediction_market_agent_tooling.config import APIKeys -from prediction_market_agent_tooling.gtypes import xDai -from prediction_market_agent_tooling.markets.seer.data_models import NewMarketEvent +from prediction_market_agent_tooling.gtypes import ( + HexAddress, + HexBytes, + OutcomeStr, + Wei, + wei_type, + xDai, + xdai_type, +) +from prediction_market_agent_tooling.loggers import logger +from prediction_market_agent_tooling.markets.agent_market import ( + AgentMarket, + FilterBy, + ProcessedMarket, + ProcessedTradedMarket, + SortBy, +) +from prediction_market_agent_tooling.markets.blockchain_utils import ( + get_total_balance, + store_trades, +) +from prediction_market_agent_tooling.markets.data_models import ( + BetAmount, + Currency, + Position, + TokenAmount, +) +from prediction_market_agent_tooling.markets.market_fees import MarketFees +from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket +from prediction_market_agent_tooling.markets.omen.omen_contracts import sDaiContract +from prediction_market_agent_tooling.markets.seer.data_models import ( + NewMarketEvent, + SeerMarket, + SeerOutcomeEnum, +) from prediction_market_agent_tooling.markets.seer.seer_contracts import ( SeerMarketFactory, ) +from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import ( + SeerSubgraphHandler, +) +from prediction_market_agent_tooling.tools.balances import get_balances from prediction_market_agent_tooling.tools.contract import ( + ContractERC20OnGnosisChain, init_collateral_token_contract, to_gnosis_chain_contract, ) +from prediction_market_agent_tooling.tools.cow.cow_manager import ( + CowManager, + NoLiquidityAvailableOnCowException, +) from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC from prediction_market_agent_tooling.tools.tokens.auto_deposit import ( auto_deposit_collateral_token, ) -from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei +from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai, xdai_to_wei + +# We place a larger bet amount by default than Omen so that cow presents valid quotes. +SEER_TINY_BET_AMOUNT = xdai_type(0.1) + + +class SeerAgentMarket(AgentMarket): + currency = Currency.sDai + wrapped_tokens: list[ChecksumAddress] + creator: HexAddress + collateral_token_contract_address_checksummed: ChecksumAddress + condition_id: HexBytes + seer_outcomes: dict[SeerOutcomeEnum, int] + description: str | None = ( + None # Seer markets don't have a description, so just default to None. + ) + + def store_prediction( + self, + processed_market: ProcessedMarket | None, + keys: APIKeys, + agent_name: str, + ) -> None: + """On Seer, we have to store predictions along with trades, see `store_trades`.""" + + def store_trades( + self, + traded_market: ProcessedTradedMarket | None, + keys: APIKeys, + agent_name: str, + ) -> None: + return store_trades( + market_id=self.id, + traded_market=traded_market, + keys=keys, + agent_name=agent_name, + ) + + def _convert_bet_amount_into_wei(self, bet_amount: BetAmount) -> Wei: + if bet_amount.currency == self.currency: + return xdai_to_wei(xdai_type(bet_amount.amount)) + raise ValueError( + f"Currencies don't match. Currency bet amount {bet_amount.currency} currency market: {self.currency}" + ) + + def get_buy_token_amount( + self, bet_amount: BetAmount, direction: bool + ) -> TokenAmount: + """Returns number of outcome tokens returned for a given bet expressed in collateral units.""" + + outcome_token = self.get_wrapped_token_for_outcome(direction) + + bet_amount_in_wei = self._convert_bet_amount_into_wei(bet_amount=bet_amount) + + quote = CowManager().get_quote( + buy_token=outcome_token, + sell_amount=bet_amount_in_wei, + collateral_token=self.collateral_token_contract_address_checksummed, + ) + sell_amount = wei_to_xdai(wei_type(quote.quote.buyAmount.root)) + return TokenAmount(amount=sell_amount, currency=bet_amount.currency) + + def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr: + outcome_translated = SeerOutcomeEnum.from_bool(outcome) + idx = self.seer_outcomes[outcome_translated] + return OutcomeStr(self.outcomes[idx]) + + @staticmethod + def get_trade_balance(api_keys: APIKeys) -> float: + return OmenAgentMarket.get_trade_balance(api_keys=api_keys) + + @classmethod + def get_tiny_bet_amount(cls) -> BetAmount: + return BetAmount(amount=SEER_TINY_BET_AMOUNT, currency=cls.currency) + + def get_position(self, user_id: str, web3: Web3 | None = None) -> Position | None: + """ + Fetches position from the user in a given market. + We ignore the INVALID balances since we are only interested in binary outcomes. + """ + + amounts = {} + + for outcome in [True, False]: + wrapped_token = self.get_wrapped_token_for_outcome(outcome) + + outcome_token_balance = ContractERC20OnGnosisChain( + address=wrapped_token + ).balanceOf(for_address=Web3.to_checksum_address(user_id), web3=web3) + outcome_str = self.get_outcome_str_from_bool(outcome=outcome) + amounts[outcome_str] = TokenAmount( + amount=wei_to_xdai(outcome_token_balance), currency=self.currency + ) + + return Position(market_id=self.id, amounts=amounts) + + @staticmethod + def get_user_id(api_keys: APIKeys) -> str: + return OmenAgentMarket.get_user_id(api_keys) + + @staticmethod + def redeem_winnings(api_keys: APIKeys) -> None: + # ToDo - implement me (https://github.com/gnosis/prediction-market-agent-tooling/issues/499) + pass + + @staticmethod + def verify_operational_balance(api_keys: APIKeys) -> bool: + return get_total_balance( + api_keys.public_key, + # Use `public_key`, not `bet_from_address` because transaction costs are paid from the EOA wallet. + sum_wxdai=False, + ) > xdai_type(0.001) + + @staticmethod + def from_data_model(model: SeerMarket) -> "SeerAgentMarket": + return SeerAgentMarket( + id=model.id.hex(), + question=model.title, + creator=model.creator, + created_time=model.created_time, + outcomes=model.outcomes, + collateral_token_contract_address_checksummed=model.collateral_token_contract_address_checksummed, + condition_id=model.condition_id, + url=model.url, + close_time=model.close_time, + wrapped_tokens=[Web3.to_checksum_address(i) for i in model.wrapped_tokens], + fees=MarketFees.get_zero_fees(), + outcome_token_pool=None, + resolution=model.get_resolution_enum(), + volume=None, + current_p_yes=model.current_p_yes, + seer_outcomes=model.outcome_as_enums, + ) + + @staticmethod + def get_binary_markets( + limit: int, + sort_by: SortBy, + filter_by: FilterBy = FilterBy.OPEN, + created_after: t.Optional[DatetimeUTC] = None, + excluded_questions: set[str] | None = None, + ) -> t.Sequence["SeerAgentMarket"]: + return [ + SeerAgentMarket.from_data_model(m) + for m in SeerSubgraphHandler().get_binary_markets( + limit=limit, + sort_by=sort_by, + filter_by=filter_by, + ) + ] + + def has_liquidity_for_outcome(self, outcome: bool) -> bool: + outcome_token = self.get_wrapped_token_for_outcome(outcome) + try: + CowManager().get_quote( + collateral_token=self.collateral_token_contract_address_checksummed, + buy_token=outcome_token, + sell_amount=xdai_to_wei( + xdai_type(1) + ), # we take 1 xDai as a baseline value for common trades the agents take. + ) + return True + except NoLiquidityAvailableOnCowException: + logger.info( + f"Could not get a quote for {outcome_token=} {outcome=}, returning no liquidity" + ) + return False + + def has_liquidity(self) -> bool: + # We conservatively define a market as having liquidity if it has liquidity for the `True` outcome token AND the `False` outcome token. + return self.has_liquidity_for_outcome(True) and self.has_liquidity_for_outcome( + False + ) + + def get_wrapped_token_for_outcome(self, outcome: bool) -> ChecksumAddress: + outcome_from_enum = SeerOutcomeEnum.from_bool(outcome) + outcome_idx = self.seer_outcomes[outcome_from_enum] + outcome_token = self.wrapped_tokens[outcome_idx] + return outcome_token + + def place_bet( + self, + outcome: bool, + amount: BetAmount, + auto_deposit: bool = True, + web3: Web3 | None = None, + api_keys: APIKeys | None = None, + ) -> str: + api_keys = api_keys if api_keys is not None else APIKeys() + if not self.can_be_traded(): + raise ValueError( + f"Market {self.id} is not open for trading. Cannot place bet." + ) + + if amount.currency != self.currency: + raise ValueError(f"Seer bets are made in xDai. Got {amount.currency}.") + + collateral_contract = sDaiContract() + if auto_deposit: + # We convert the deposit amount (in sDai) to assets in order to convert. + asset_amount = collateral_contract.convertToAssets( + xdai_to_wei(xdai_type(amount.amount)) + ) + auto_deposit_collateral_token( + collateral_contract, asset_amount, api_keys, web3 + ) + + # We require that amount is given in sDAI. + collateral_balance = get_balances(address=api_keys.bet_from_address, web3=web3) + if collateral_balance.sdai < amount.amount: + raise ValueError( + f"Balance {collateral_balance.sdai} not enough for bet size {amount.amount}" + ) + + outcome_token = self.get_wrapped_token_for_outcome(outcome) + # Sell sDAI using token address + order_metadata = CowManager().swap( + amount=xdai_type(amount.amount), + sell_token=collateral_contract.address, + buy_token=Web3.to_checksum_address(outcome_token), + api_keys=api_keys, + web3=web3, + ) + + return order_metadata.uid.root def seer_create_market_tx( diff --git a/prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py b/prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py index 76131fc4..b398d0f4 100644 --- a/prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +++ b/prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py @@ -1,8 +1,11 @@ +import sys +import typing as t from typing import Any from subgrounds import FieldPath from web3.constants import ADDRESS_ZERO +from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy from prediction_market_agent_tooling.markets.base_subgraph_handler import ( BaseSubgraphHandler, ) @@ -11,8 +14,7 @@ SeerPool, ) from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes - -INVALID_OUTCOME = "Invalid result" +from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow class SeerSubgraphHandler(BaseSubgraphHandler): @@ -45,21 +47,26 @@ def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]: markets_field.id, markets_field.factory, markets_field.creator, + markets_field.conditionId, markets_field.marketName, markets_field.parentOutcome, markets_field.outcomes, + markets_field.payoutReported, + markets_field.payoutNumerators, + markets_field.hasAnswers, + markets_field.blockTimestamp, markets_field.parentMarket.id, + markets_field.openingTs, markets_field.finalizeTs, markets_field.wrappedTokens, + markets_field.collateralToken, ] return fields @staticmethod def filter_bicategorical_markets(markets: list[SeerMarket]) -> list[SeerMarket]: # We do an extra check for the invalid outcome for safety. - return [ - m for m in markets if len(m.outcomes) == 3 and INVALID_OUTCOME in m.outcomes - ] + return [m for m in markets if len(m.outcomes) == 3] @staticmethod def filter_binary_markets(markets: list[SeerMarket]) -> list[SeerMarket]: @@ -70,35 +77,110 @@ def filter_binary_markets(markets: list[SeerMarket]) -> list[SeerMarket]: ] @staticmethod - def build_filter_for_conditional_markets( - include_conditional_markets: bool = True, + def _build_where_statements( + filter_by: FilterBy, + include_conditional_markets: bool = False, ) -> dict[Any, Any]: - return ( - {} - if include_conditional_markets - else {"parentMarket": ADDRESS_ZERO.lower()} - ) + now = to_int_timestamp(utcnow()) + + and_stms: dict[str, t.Any] = {} + + match filter_by: + case FilterBy.OPEN: + and_stms["openingTs_gt"] = now + and_stms["hasAnswers"] = False + case FilterBy.RESOLVED: + # We consider RESOLVED == CLOSED (on Seer UI) + and_stms["payoutReported"] = True + case FilterBy.NONE: + pass + case _: + raise ValueError(f"Unknown filter {filter_by}") + + if not include_conditional_markets: + and_stms["parentMarket"] = ADDRESS_ZERO.lower() + + # We are only interested in binary markets of type YES/NO/Invalid. + or_stms = {} + or_stms["or"] = [ + {"outcomes_contains": ["YES"]}, + {"outcomes_contains": ["Yes"]}, + {"outcomes_contains": ["yes"]}, + ] + + where_stms: dict[str, t.Any] = {"and": [and_stms, or_stms]} + return where_stms + + def _build_sort_params( + self, sort_by: SortBy + ) -> tuple[str | None, FieldPath | None]: + sort_direction: str | None + sort_by_field: FieldPath | None + + match sort_by: + case SortBy.NEWEST: + sort_direction = "desc" + sort_by_field = self.seer_subgraph.Market.block_timestamp + case SortBy.CLOSING_SOONEST: + sort_direction = "asc" + sort_by_field = self.seer_subgraph.Market.opening_ts + # ToDo - Implement liquidity conditions by looking up Swapr subgraph. + case SortBy.NONE | SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY: + sort_direction = None + sort_by_field = None + case _: + raise ValueError(f"Unknown sort_by: {sort_by}") + + return sort_direction, sort_by_field def get_bicategorical_markets( - self, include_conditional_markets: bool = True + self, + filter_by: FilterBy, + limit: int | None = None, + sort_by_field: FieldPath | None = None, + sort_direction: str | None = None, + include_conditional_markets: bool = True, ) -> list[SeerMarket]: """Returns markets that contain 2 categories plus an invalid outcome.""" # Binary markets on Seer contain 3 outcomes: OutcomeA, outcomeB and an Invalid option. - query_filter = self.build_filter_for_conditional_markets( - include_conditional_markets + where_stms = self._build_where_statements( + filter_by=filter_by, include_conditional_markets=include_conditional_markets + ) + + # These values can not be set to `None`, but they can be omitted. + optional_params = {} + if sort_by_field is not None: + optional_params["orderBy"] = sort_by_field + if sort_direction is not None: + optional_params["orderDirection"] = sort_direction + + markets_field = self.seer_subgraph.Query.markets( + first=( + limit if limit else sys.maxsize + ), # if not limit, we fetch all possible markets, + where=where_stms, + **optional_params, ) - query_filter["outcomes_contains"] = [INVALID_OUTCOME] - markets_field = self.seer_subgraph.Query.markets(where=query_filter) fields = self._get_fields_for_markets(markets_field) markets = self.do_query(fields=fields, pydantic_model=SeerMarket) two_category_markets = self.filter_bicategorical_markets(markets) return two_category_markets def get_binary_markets( - self, include_conditional_markets: bool = True + self, + filter_by: FilterBy, + sort_by: SortBy = SortBy.NONE, + limit: int | None = None, + include_conditional_markets: bool = True, ) -> list[SeerMarket]: + sort_direction, sort_by_field = self._build_sort_params(sort_by) + two_category_markets = self.get_bicategorical_markets( - include_conditional_markets=include_conditional_markets + limit=limit, + include_conditional_markets=include_conditional_markets, + sort_direction=sort_direction, + sort_by_field=sort_by_field, + filter_by=filter_by, ) # Now we additionally filter markets based on YES/NO being the only outcomes. binary_markets = self.filter_binary_markets(two_category_markets) @@ -133,8 +215,8 @@ def get_swapr_pools_for_market(self, market: SeerMarket) -> list[SeerPool]: for wrapped_token in market.wrapped_tokens: wheres.extend( [ - {"token0": wrapped_token.hex().lower()}, - {"token1": wrapped_token.hex().lower()}, + {"token0": wrapped_token.lower()}, + {"token1": wrapped_token.lower()}, ] ) pools_field = self.swapr_algebra_subgraph.Query.pools(where={"or": wheres}) diff --git a/prediction_market_agent_tooling/tools/cow/cow_manager.py b/prediction_market_agent_tooling/tools/cow/cow_manager.py new file mode 100644 index 00000000..ddce23f5 --- /dev/null +++ b/prediction_market_agent_tooling/tools/cow/cow_manager.py @@ -0,0 +1,113 @@ +import asyncio + +from cowdao_cowpy.common.api.errors import UnexpectedResponseError +from cowdao_cowpy.common.config import SupportedChainId +from cowdao_cowpy.cow.swap import get_order_quote +from cowdao_cowpy.order_book.api import OrderBookApi +from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory +from cowdao_cowpy.order_book.generated.model import ( + Address, + OrderMetaData, + OrderQuoteRequest, + OrderQuoteResponse, + OrderQuoteSide1, + OrderQuoteSideKindSell, +) +from cowdao_cowpy.order_book.generated.model import TokenAmount as TokenAmountCow +from tenacity import retry, retry_if_not_exception_type, stop_after_attempt, wait_fixed +from web3 import Web3 +from web3.constants import ADDRESS_ZERO + +from prediction_market_agent_tooling.config import APIKeys +from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei, xDai +from prediction_market_agent_tooling.loggers import logger +from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting +from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei + +COW_ENV: Envs = "prod" + + +class NoLiquidityAvailableOnCowException(Exception): + """Custom exception for handling case where no liquidity available.""" + + +class CowManager: + def __init__(self) -> None: + self.order_book_api = OrderBookApi( + OrderBookAPIConfigFactory.get_config(COW_ENV, SupportedChainId.GNOSIS_CHAIN) + ) + self.precision = 18 # number of token decimals from ERC1155 wrapped tokens. + + @retry( + stop=stop_after_attempt(2), + wait=wait_fixed(2), + retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException), + ) + def get_quote( + self, + collateral_token: ChecksumAddress, + buy_token: ChecksumAddress, + sell_amount: Wei, + ) -> OrderQuoteResponse: + """ + Quote the price for a sell order. + + Parameters: + - collateral_token: The token being sold. + - buy_token: The token being bought. + - sell_amount: The amount of collateral to sell in atoms. + + Returns: + - An OrderQuoteResponse containing the quote information. + + Raises: + - NoLiquidityAvailableOnCowException if no liquidity is available on CoW. + """ + + order_quote_request = OrderQuoteRequest( + buyToken=Address(buy_token), + sellToken=Address(collateral_token), + from_=Address(ADDRESS_ZERO), + ) + + order_side = OrderQuoteSide1( + kind=OrderQuoteSideKindSell.sell, + sellAmountBeforeFee=TokenAmountCow(str(sell_amount)), + ) + try: + return asyncio.run( + get_order_quote( + order_quote_request=order_quote_request, + order_side=order_side, + order_book_api=self.order_book_api, + ) + ) + + except UnexpectedResponseError as e1: + if "NoLiquidity" in e1.message: + raise NoLiquidityAvailableOnCowException(e1.message) + logger.warning(f"Found unexpected Cow response error: {e1}") + raise + except Exception as e: + logger.warning(f"Found unhandled Cow response error: {e}") + raise + + @staticmethod + def swap( + amount: xDai, + sell_token: ChecksumAddress, + buy_token: ChecksumAddress, + api_keys: APIKeys, + web3: Web3 | None = None, + ) -> OrderMetaData: + order_metadata = swap_tokens_waiting( + amount_wei=xdai_to_wei(amount), + sell_token=sell_token, + buy_token=buy_token, + api_keys=api_keys, + web3=web3, + ) + logger.debug( + f"Purchased {buy_token} in exchange for {sell_token}. Order details {order_metadata}" + ) + return order_metadata diff --git a/tests/monitor/test_monitor.py b/tests/monitor/test_monitor.py index 29a36310..85aa71cf 100644 --- a/tests/monitor/test_monitor.py +++ b/tests/monitor/test_monitor.py @@ -9,16 +9,20 @@ @pytest.mark.parametrize("market_type", list(MarketType)) def test_monitor_mapping_contains_all_types(market_type: MarketType) -> None: - assert ( - market_type in MARKET_TYPE_TO_DEPLOYED_AGENT - ), f"Add {market_type} to the MARKET_TYPE_TO_DEPLOYED_AGENT." + # ToDo - Add Seer once Agent goes live. + if market_type != MarketType.SEER: + assert ( + market_type in MARKET_TYPE_TO_DEPLOYED_AGENT + ), f"Add {market_type} to the MARKET_TYPE_TO_DEPLOYED_AGENT." @pytest.mark.parametrize("market_type", list(MarketType)) def test_monitor_market(market_type: MarketType) -> None: - cls = MARKET_TYPE_TO_DEPLOYED_AGENT[market_type] - agents = cls.from_all_gcp_functions() - if len(agents) == 0: - pytest.skip(f"No deployed agents found for {market_type}") + if market_type != MarketType.SEER: + # Seer agent not deployed yet. + cls = MARKET_TYPE_TO_DEPLOYED_AGENT[market_type] + agents = cls.from_all_gcp_functions() + if len(agents) == 0: + pytest.skip(f"No deployed agents found for {market_type}") - monitor_agent(agents[0]) + monitor_agent(agents[0]) diff --git a/tests/test_betting_strategy.py b/tests/test_betting_strategy.py index 1731d103..06726f13 100644 --- a/tests/test_betting_strategy.py +++ b/tests/test_betting_strategy.py @@ -12,6 +12,7 @@ HexAddress, HexBytes, HexStr, + OutcomeStr, Probability, ) from prediction_market_agent_tooling.markets.data_models import ( @@ -23,6 +24,8 @@ ) from prediction_market_agent_tooling.markets.omen.data_models import ( OMEN_BINARY_MARKET_OUTCOMES, + OMEN_FALSE_OUTCOME, + OMEN_TRUE_OUTCOME, ) from prediction_market_agent_tooling.markets.omen.omen import ( Condition, @@ -50,6 +53,10 @@ def test_answer_decision( assert direction == expected_direction +def mock_outcome_str(x: bool) -> OutcomeStr: + return OutcomeStr(OMEN_TRUE_OUTCOME) if x else OutcomeStr(OMEN_FALSE_OUTCOME) + + def test_rebalance() -> None: tiny_amount = TokenAmount(amount=0.0001, currency=Currency.xDai) mock_amount = TokenAmount(amount=5, currency=Currency.xDai) @@ -57,8 +64,8 @@ def test_rebalance() -> None: mock_existing_position = Position( market_id="0x123", amounts={ - OmenAgentMarket.get_outcome_str_from_bool(True): mock_amount, - OmenAgentMarket.get_outcome_str_from_bool(False): mock_amount, + OutcomeStr(OMEN_TRUE_OUTCOME): mock_amount, + OutcomeStr(OMEN_FALSE_OUTCOME): mock_amount, }, ) bet_amount = tiny_amount.amount + mock_existing_position.total_amount.amount @@ -69,6 +76,7 @@ def test_rebalance() -> None: mock_market.get_liquidity.return_value = liquidity_amount mock_market.get_tiny_bet_amount.return_value = tiny_amount mock_market.get_buy_token_amount.return_value = buy_token_amount + mock_market.get_outcome_str_from_bool.side_effect = mock_outcome_str mock_market.current_p_yes = 0.5 mock_market.currency = Currency.xDai mock_market.id = "0x123" diff --git a/tests_integration/markets/seer/conftest.py b/tests_integration/markets/seer/conftest.py new file mode 100644 index 00000000..c80578d1 --- /dev/null +++ b/tests_integration/markets/seer/conftest.py @@ -0,0 +1,18 @@ +import typing as t + +import pytest + +from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import ( + SeerSubgraphHandler, +) +from prediction_market_agent_tooling.tools.cow.cow_manager import CowManager + + +@pytest.fixture(scope="module") +def seer_subgraph_handler_test() -> t.Generator[SeerSubgraphHandler, None, None]: + yield SeerSubgraphHandler() + + +@pytest.fixture(scope="module") +def cow_manager() -> t.Generator[CowManager, None, None]: + yield CowManager() diff --git a/tests_integration/markets/seer/test_seer_data_model.py b/tests_integration/markets/seer/test_seer_data_model.py new file mode 100644 index 00000000..b7ec7de8 --- /dev/null +++ b/tests_integration/markets/seer/test_seer_data_model.py @@ -0,0 +1,12 @@ +from prediction_market_agent_tooling.gtypes import HexBytes +from prediction_market_agent_tooling.markets.data_models import Resolution +from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import ( + SeerSubgraphHandler, +) + + +def test_resolution() -> None: + resolved_market_id = HexBytes("0xd32068304199c1885b02d3ac91e0da06b9568409") + market = SeerSubgraphHandler().get_market_by_id(market_id=resolved_market_id) + resolution = market.get_resolution_enum() + assert resolution == Resolution.YES diff --git a/tests_integration/markets/seer/test_seer_outcome.py b/tests_integration/markets/seer/test_seer_outcome.py new file mode 100644 index 00000000..9c8111e6 --- /dev/null +++ b/tests_integration/markets/seer/test_seer_outcome.py @@ -0,0 +1,17 @@ +import pytest + +from prediction_market_agent_tooling.markets.seer.data_models import SeerOutcomeEnum + + +@pytest.mark.parametrize("outcome", ["YES", "NO", "INVALID"]) +def test_seer_outcome(outcome: str) -> None: + assert SeerOutcomeEnum.from_string(outcome.lower()) == SeerOutcomeEnum.from_string( + outcome + ) + assert SeerOutcomeEnum.from_string( + outcome.capitalize() + ) == SeerOutcomeEnum.from_string(outcome) + + +def test_seer_outcome_invalid() -> None: + assert SeerOutcomeEnum.from_string("Invalid result") == SeerOutcomeEnum.INVALID diff --git a/tests_integration/markets/seer/test_seer_subgraph_handler.py b/tests_integration/markets/seer/test_seer_subgraph_handler.py index b1375d64..831629e7 100644 --- a/tests_integration/markets/seer/test_seer_subgraph_handler.py +++ b/tests_integration/markets/seer/test_seer_subgraph_handler.py @@ -1,63 +1,68 @@ -import typing as t - -import pytest - +from prediction_market_agent_tooling.gtypes import HexBytes +from prediction_market_agent_tooling.markets.agent_market import FilterBy from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import ( SeerSubgraphHandler, ) -from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes -CONDITIONAL_MARKET_ID = HexBytes("0xe12f48ecdd6e64d95d1d8f1d5d7aa37e14f2888b") +CONDITIONAL_MARKET_ID = HexBytes("0xfe2cc518b4d8c1d5db682db553c3de750d901ce0") BINARY_MARKET_ID = HexBytes("0x7d72aa56ecdda207005fd7a02dbfd33f92d0def7") BINARY_CONDITIONAL_MARKET_ID = HexBytes("0xbc82402814f7db8736980c0debb01df6aad8846e") -@pytest.fixture(scope="module") -def handler() -> t.Generator[SeerSubgraphHandler, None, None]: - yield SeerSubgraphHandler() - - -def test_get_all_seer_markets(handler: SeerSubgraphHandler) -> None: - markets = handler.get_bicategorical_markets() +def test_get_all_seer_markets(seer_subgraph_handler_test: SeerSubgraphHandler) -> None: + markets = seer_subgraph_handler_test.get_bicategorical_markets( + filter_by=FilterBy.NONE + ) assert len(markets) > 1 -def test_get_seer_market_by_id(handler: SeerSubgraphHandler) -> None: +def test_get_seer_market_by_id(seer_subgraph_handler_test: SeerSubgraphHandler) -> None: market_id = HexBytes("0x03cbd8e3a45c727643b015318fff883e13937fdd") - market = handler.get_market_by_id(market_id) + market = seer_subgraph_handler_test.get_market_by_id(market_id) assert market is not None assert market.id == market_id -def test_conditional_market_not_retrieved(handler: SeerSubgraphHandler) -> None: - markets = handler.get_bicategorical_markets(include_conditional_markets=False) +def test_conditional_market_not_retrieved( + seer_subgraph_handler_test: SeerSubgraphHandler, +) -> None: + markets = seer_subgraph_handler_test.get_bicategorical_markets( + include_conditional_markets=False, filter_by=FilterBy.NONE + ) market_ids = [m.id for m in markets] assert CONDITIONAL_MARKET_ID not in market_ids -@pytest.mark.skip(reason="Needes fix from another PR.") -def test_conditional_market_retrieved(handler: SeerSubgraphHandler) -> None: - markets = handler.get_bicategorical_markets(include_conditional_markets=True) +def test_conditional_market_retrieved( + seer_subgraph_handler_test: SeerSubgraphHandler, +) -> None: + markets = seer_subgraph_handler_test.get_bicategorical_markets( + include_conditional_markets=True, filter_by=FilterBy.NONE + ) market_ids = [m.id for m in markets] assert CONDITIONAL_MARKET_ID in market_ids -def test_binary_market_retrieved(handler: SeerSubgraphHandler) -> None: - markets = handler.get_binary_markets(include_conditional_markets=True) +def test_binary_market_retrieved( + seer_subgraph_handler_test: SeerSubgraphHandler, +) -> None: + markets = seer_subgraph_handler_test.get_binary_markets( + include_conditional_markets=True, filter_by=FilterBy.NONE + ) market_ids = [m.id for m in markets] assert BINARY_MARKET_ID in market_ids assert BINARY_CONDITIONAL_MARKET_ID in market_ids -def test_get_pools_for_market(handler: SeerSubgraphHandler) -> None: +def test_get_pools_for_market(seer_subgraph_handler_test: SeerSubgraphHandler) -> None: us_election_market_id = HexBytes("0x43d881f5920ed29fc5cd4917d6817496abbba6d9") - market = handler.get_market_by_id(us_election_market_id) + market = seer_subgraph_handler_test.get_market_by_id(us_election_market_id) - pools = handler.get_swapr_pools_for_market(market) + pools = seer_subgraph_handler_test.get_swapr_pools_for_market(market) assert len(pools) > 1 for pool in pools: # one of the tokens must be a wrapped token assert ( - pool.token0.id in market.wrapped_tokens - or pool.token1.id in market.wrapped_tokens + pool.token0.id.hex() in market.wrapped_tokens + or pool.token1.id.hex() in market.wrapped_tokens ) diff --git a/tests_integration/tools/cow/test_cow_manager.py b/tests_integration/tools/cow/test_cow_manager.py new file mode 100644 index 00000000..66bc2e56 --- /dev/null +++ b/tests_integration/tools/cow/test_cow_manager.py @@ -0,0 +1,29 @@ +import pytest +from web3 import Web3 + +from prediction_market_agent_tooling.gtypes import xdai_type +from prediction_market_agent_tooling.markets.omen.omen_contracts import sDaiContract +from prediction_market_agent_tooling.tools.cow.cow_manager import ( + CowManager, + NoLiquidityAvailableOnCowException, +) +from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei + + +@pytest.fixture(scope="module") +def test_manager() -> CowManager: + return CowManager() + + +def test_nonexistent_quote(test_manager: CowManager) -> None: + collateral_token = sDaiContract().address + token_without_liquidity = Web3.to_checksum_address( + "0x7cefb84cf95640132adb31ed635e31d40d7e3322" + ) + + with pytest.raises(NoLiquidityAvailableOnCowException): + test_manager.get_quote( + collateral_token=collateral_token, + buy_token=token_without_liquidity, + sell_amount=xdai_to_wei(xdai_type(1)), + ) diff --git a/tests_integration_with_local_chain/markets/omen/test_omen.py b/tests_integration_with_local_chain/markets/omen/test_omen.py index c2683adc..bf702e46 100644 --- a/tests_integration_with_local_chain/markets/omen/test_omen.py +++ b/tests_integration_with_local_chain/markets/omen/test_omen.py @@ -436,7 +436,7 @@ def test_place_bet_with_autodeposit( market.place_bet( outcome=True, amount=bet_amount, - omen_auto_deposit=True, + auto_deposit=True, web3=local_web3, api_keys=test_keys, ) diff --git a/tests_integration_with_local_chain/markets/seer/test_seer_agent_market.py b/tests_integration_with_local_chain/markets/seer/test_seer_agent_market.py new file mode 100644 index 00000000..6e550b55 --- /dev/null +++ b/tests_integration_with_local_chain/markets/seer/test_seer_agent_market.py @@ -0,0 +1,57 @@ +import pytest +from cowdao_cowpy.order_book.generated.model import ( + Address, + AppDataHash, + OrderKind, + OrderParameters, + OrderQuoteResponse, +) +from cowdao_cowpy.order_book.generated.model import TokenAmount as TokenAmountCow +from web3 import Web3 + +from prediction_market_agent_tooling.config import APIKeys +from prediction_market_agent_tooling.gtypes import xdai_type +from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy +from prediction_market_agent_tooling.markets.data_models import TokenAmount +from prediction_market_agent_tooling.markets.seer.seer import SeerAgentMarket +from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import ( + SeerSubgraphHandler, +) +from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei + +MOCK_APP_DATA = "0x0000000000000000000000000000000000000000000000000000000000000000" # web3-private-key-ok +MOCK_QUOTE = OrderQuoteResponse( + quote=OrderParameters( + buyAmount=TokenAmountCow(str(xdai_to_wei(xdai_type(2)))), # 0.5 odds + sellToken=Address("0xabc"), + buyToken=Address("0xdef"), + sellAmount=TokenAmountCow("0.5"), + validTo=1739474477, + appData=AppDataHash(MOCK_APP_DATA), + feeAmount=TokenAmountCow("0.5"), + kind=OrderKind.buy, + partiallyFillable=False, + ), + expiration="1985-03-10T18:35:18.814523Z", + verified=False, +) + + +def test_seer_place_bet(local_web3: Web3, test_keys: APIKeys) -> None: + # We fetch the market with the highest liquidity because we expect quotes to be available for all outcome tokens. + markets = SeerSubgraphHandler().get_binary_markets( + filter_by=FilterBy.OPEN, limit=1, sort_by=SortBy.HIGHEST_LIQUIDITY + ) + market_data_model = markets[0] + agent_market = SeerAgentMarket.from_data_model(market_data_model) + amount = 1 + with pytest.raises(Exception) as e: + # We expect an exception from Cow since test accounts don't have enough funds. + agent_market.place_bet( + api_keys=test_keys, + outcome=True, + amount=TokenAmount(amount=amount, currency=agent_market.currency), + auto_deposit=True, + web3=local_web3, + ) + assert "InsufficientBalance" in str(e)