diff --git a/.env.example b/.env.example index d6799a51..cc9d5aae 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,15 @@ OPENAI_API_KEY= EternalAI_API_KEY= EternalAI_API_URL= FARCASTER_MNEMONIC= +HYPERBOLIC_API_KEY= +GALADRIEL_API_KEY= +GALADRIEL_FINE_TUNE_API_KEY= TWITTER_CONSUMER_KEY= TWITTER_CONSUMER_SECRET= TWITTER_ACCESS_TOKEN= TWITTER_ACCESS_TOKEN_SECRET= -TWITTER_USER_ID= \ No newline at end of file +TWITTER_USER_ID= +ETHEREUM_PRIVATE_KEY= +ETHEREUM_SCANNER_KEY= +SOLANA_PRIVATE_KEY= +COINGECKO_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index aaac5bd9..a68a6746 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ agents/*.json !agents/general.json # macOS -.DS_Store \ No newline at end of file +.DS_Store +.codegpt \ No newline at end of file diff --git a/README.md b/README.md index 5341e353..cd116abb 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,26 @@ similar core functionality as Zerebro. For creative outputs, you'll need to fine ## Features +### Core Platform + - CLI interface for managing agents -- Twitter/X integration -- Farcaster integration -- Echochambers integration -- OpenAI/Anthropic/EternalAI LLM support - Modular connection system +- Blockchain integration with Solana + +### Social Platform Integrations + +- Twitter/X +- Farcaster +- Echochambers + +### Language Model Support + +- OpenAI +- Anthropic +- EternalAI +- Ollama +- Hyperbolic +- Galadriel ## Quickstart @@ -31,16 +45,21 @@ System: - Python 3.10 or higher (3.10 and 3.11 are best for beginner users) - Poetry 1.5 or higher -API keys: +Environment Variables: - LLM: make an account and grab an API key (at least one) - OpenAI: https://platform.openai.com/api-keys - Anthropic: https://console.anthropic.com/account/keys - EternalAI: https://eternalai.oerg/api + - Hyperbolic: https://app.hyperbolic.xyz + - Galadriel: https://dashboard.galadriel.com - Social (based on your needs): - X API: https://developer.x.com/en/docs/authentication/oauth-1-0a/api-key-and-secret - Farcaster: Warpcast recovery phrase - Echochambers: API key and endpoint +- On-chain Integration: + - Solana: private key (in base58 format) for transactions + - RPC URL (defaults to public endpoints) ## Installation @@ -92,6 +111,8 @@ poetry run python main.py configure-connection anthropic # For Anthropic configure-connection farcaster # For Farcaster configure-connection eternalai # For EternalAI + configure-connection galadriel # For Galadriel + configure-connection solana # For Solana ``` 2. Use `list-connections` to see all available connections and their status @@ -109,6 +130,16 @@ poetry run python main.py ## Platform Features +### Solana + +- Transfer SOL and SPL tokens +- Swap tokens using Jupiter +- Check token balances +- Stake SOL +- Monitor network TPS +- Query token information +- Request testnet/devnet funds + ### Twitter/X - Post tweets from prompts @@ -170,13 +201,40 @@ Create a new JSON file in the `agents` directory following this structure: { "name": "anthropic", "model": "claude-3-5-sonnet-20241022" + }, + { + "name": "eternalai", + "model": "NousResearch/Hermes-3-Llama-3.1-70B-FP8", + "chain_id": "45762" + }, + { + "name": "solana", + "rpc": "https://api.mainnet-beta.solana.com" + }, + { + "name": "ollama", + "base_url": "http://localhost:11434", + "model": "llama3.2" + }, + { + "name": "hyperbolic", + "model": "meta-llama/Meta-Llama-3-70B-Instruct" + }, + { + "name": "galadriel", + "model": "gpt-3.5-turbo" } ], "tasks": [ { "name": "post-tweet", "weight": 1 }, { "name": "reply-to-tweet", "weight": 1 }, { "name": "like-tweet", "weight": 1 } - ] + ], + "use_time_based_weights": false, + "time_based_multipliers": { + "tweet_night_multiplier": 0.4, + "engagement_day_multiplier": 1.5 + } } ``` @@ -192,6 +250,7 @@ Use `help` in the CLI to see all available commands. Key commands include: - `list-actions`: Show available actions for a connection - `configure-connection`: Set up a new connection - `chat`: Start interactive chat with agent +- `clear`: Clear the terminal screen ## Star History diff --git a/agents/eternalai-example.json b/agents/eternalai-example.json index ae8fa753..0106f228 100644 --- a/agents/eternalai-example.json +++ b/agents/eternalai-example.json @@ -15,6 +15,8 @@ "This is an example tweet.", "This is another example tweet." ], + "example_accounts": [ + ], "loop_delay": 900, "config": [ { @@ -33,5 +35,10 @@ {"name": "post-tweet", "weight": 1}, {"name": "reply-to-tweet", "weight": 1}, {"name": "like-tweet", "weight": 1} - ] + ], + "use_time_based_weights": false, + "time_based_multipliers": { + "tweet_night_multiplier": 0.4, + "engagement_day_multiplier": 1.5 + } } \ No newline at end of file diff --git a/agents/example.json b/agents/example.json index 557d95b7..021e1b88 100644 --- a/agents/example.json +++ b/agents/example.json @@ -1,29 +1,19 @@ { - "name": "ExampleAgent", - "bio": [ - "You are ExampleAgent, the example agent created to showcase the capabilities of ZerePy.", - "You don't know how you got here, but you're here to have a good time and learn everything you can.", - "You are naturally curious, and ask a lot of questions." - ], - "traits": [ - "Curious", - "Creative", - "Innovative", - "Funny" - ], - "examples": [ - "This is an example tweet.", - "This is another example tweet." - ], - "example_accounts": [ - "0xzerebro" + "name": "ExampleAgent", + "bio": [ + "You are ExampleAgent, the example agent created to showcase the capabilities of ZerePy.", + "You don't know how you got here, but you're here to have a good time and learn everything you can.", + "You are naturally curious, and ask a lot of questions." ], + "traits": ["Curious", "Creative", "Innovative", "Funny"], + "examples": ["This is an example tweet.", "This is another example tweet."], + "example_accounts": ["0xzerebro"], "loop_delay": 900, "config": [ { "name": "twitter", "timeline_read_count": 10, - "own_tweet_replies_count":2, + "own_tweet_replies_count": 2, "tweet_interval": 5400 }, { @@ -39,6 +29,10 @@ "name": "anthropic", "model": "claude-3-5-sonnet-20241022" }, + { + "name": "solana", + "rpc": "https://api.mainnet-beta.solana.com" + }, { "name": "eternalai", "model": "NousResearch/Hermes-3-Llama-3.1-70B-FP8", @@ -48,11 +42,29 @@ "name": "ollama", "base_url": "http://localhost:11434", "model": "llama3.2" + }, + { + "name": "hyperbolic", + "model": "meta-llama/Meta-Llama-3-70B-Instruct" + }, + { + "name": "galadriel", + "model": "gpt-3.5-turbo" + }, + { + "name": "evm-ethereum", + "rpc": "https://mainnet.infura.io/v3/79369232298d466a9aae23aef12a635b", + "scanner": "api.etherscan.io" + }, + { + "name": "evm-polygon", + "rpc": "https://polygon-rpc.com", + "scanner": "api.polygonscan.com" } ], "tasks": [ - {"name": "post-tweet", "weight": 1}, - {"name": "reply-to-tweet", "weight": 1}, - {"name": "like-tweet", "weight": 1} + { "name": "post-tweet", "weight": 1 }, + { "name": "reply-to-tweet", "weight": 1 }, + { "name": "like-tweet", "weight": 1 } ] -} \ No newline at end of file +} diff --git a/poetry.lock b/poetry.lock index b2434c36..e69de29b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,1393 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anthropic" -version = "0.42.0" -description = "The official Python library for the anthropic API" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anthropic-0.42.0-py3-none-any.whl", hash = "sha256:46775f65b723c078a2ac9e9de44a46db5c6a4fabeacfd165e5ea78e6817f4eff"}, - {file = "anthropic-0.42.0.tar.gz", hash = "sha256:bf8b0ed8c8cb2c2118038f29c58099d2f99f7847296cafdaa853910bfff4edf4"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -typing-extensions = ">=4.10,<5" - -[package.extras] -bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] -vertex = ["google-auth (>=2,<3)"] - -[[package]] -name = "anyio" -version = "4.7.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -files = [ - {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, - {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "bitarray" -version = "3.0.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"}, -] - -[[package]] -name = "canonicaljson" -version = "2.0.0" -description = "Canonical JSON" -optional = false -python-versions = ">=3.7" -files = [ - {file = "canonicaljson-2.0.0-py3-none-any.whl", hash = "sha256:c38a315de3b5a0532f1ec1f9153cd3d716abfc565a558d00a4835428a34fca5b"}, - {file = "canonicaljson-2.0.0.tar.gz", hash = "sha256:e2fdaef1d7fadc5d9cb59bd3d0d41b064ddda697809ac4325dced721d12f113f"}, -] - -[[package]] -name = "certifi" -version = "2024.12.14" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "cytoolz" -version = "1.0.1" -description = "Cython implementation of Toolz: High performance functional utilities" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042"}, - {file = "cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608"}, - {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90124bdc42ff58b88cdea1d24a6bc5f776414a314cc4d94f25c88badb3a16d1"}, - {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e74801b751e28f7c5cc3ad264c123954a051f546f2fdfe089f5aa7a12ccfa6da"}, - {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582dad4545ddfb5127494ef23f3fa4855f1673a35d50c66f7638e9fb49805089"}, - {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7bd0618e16efe03bd12f19c2a26a27e6e6b75d7105adb7be1cd2a53fa755d8"}, - {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d74cca6acf1c4af58b2e4a89cc565ed61c5e201de2e434748c93e5a0f5c541a5"}, - {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:823a3763828d8d457f542b2a45d75d6b4ced5e470b5c7cf2ed66a02f508ed442"}, - {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51633a14e6844c61db1d68c1ffd077cf949f5c99c60ed5f1e265b9e2966f1b52"}, - {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3ec9b01c45348f1d0d712507d54c2bfd69c62fbd7c9ef555c9d8298693c2432"}, - {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1855022b712a9c7a5bce354517ab4727a38095f81e2d23d3eabaf1daeb6a3b3c"}, - {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9930f7288c4866a1dc1cc87174f0c6ff4cad1671eb1f6306808aa6c445857d78"}, - {file = "cytoolz-1.0.1-cp310-cp310-win32.whl", hash = "sha256:a9baad795d72fadc3445ccd0f122abfdbdf94269157e6d6d4835636dad318804"}, - {file = "cytoolz-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:ad95b386a84e18e1f6136f6d343d2509d4c3aae9f5a536f3dc96808fcc56a8cf"}, - {file = "cytoolz-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d958d4f04d9d7018e5c1850790d9d8e68b31c9a2deebca74b903706fdddd2b6"}, - {file = "cytoolz-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f445b8b731fc0ecb1865b8e68a070084eb95d735d04f5b6c851db2daf3048ab"}, - {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f546a96460a7e28eb2ec439f4664fa646c9b3e51c6ebad9a59d3922bbe65e30"}, - {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0317681dd065532d21836f860b0563b199ee716f55d0c1f10de3ce7100c78a3b"}, - {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c0ef52febd5a7821a3fd8d10f21d460d1a3d2992f724ba9c91fbd7a96745d41"}, - {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebaf419acf2de73b643cf96108702b8aef8e825cf4f63209ceb078d5fbbbfd"}, - {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f7f04eeb4088947585c92d6185a618b25ad4a0f8f66ea30c8db83cf94a425e3"}, - {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f61928803bb501c17914b82d457c6f50fe838b173fb40d39c38d5961185bd6c7"}, - {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2960cb4fa01ccb985ad1280db41f90dc97a80b397af970a15d5a5de403c8c61"}, - {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2b407cc3e9defa8df5eb46644f6f136586f70ba49eba96f43de67b9a0984fd3"}, - {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8245f929144d4d3bd7b972c9593300195c6cea246b81b4c46053c48b3f044580"}, - {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37385db03af65763933befe89fa70faf25301effc3b0485fec1c15d4ce4f052"}, - {file = "cytoolz-1.0.1-cp311-cp311-win32.whl", hash = "sha256:50f9c530f83e3e574fc95c264c3350adde8145f4f8fc8099f65f00cc595e5ead"}, - {file = "cytoolz-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b7f6b617454b4326af7bd3c7c49b0fc80767f134eb9fd6449917a058d17a0e3c"}, - {file = "cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4"}, - {file = "cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662"}, - {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3"}, - {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a55ec098036c0dea9f3bdc021f8acd9d105a945227d0811589f0573f21c9ce1"}, - {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a13ab79ff4ce202e03ab646a2134696988b554b6dc4b71451e948403db1331d8"}, - {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2d944799026e1ff08a83241f1027a2d9276c41f7a74224cd98b7df6e03957d"}, - {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88ba85834cd523b91fdf10325e1e6d71c798de36ea9bdc187ca7bd146420de6f"}, - {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a750b1af7e8bf6727f588940b690d69e25dc47cce5ce467925a76561317eaf7"}, - {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44a71870f7eae31d263d08b87da7c2bf1176f78892ed8bdade2c2850478cb126"}, - {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0"}, - {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa87599ccc755de5a096a4d6c34984de6cd9dc928a0c5eaa7607457317aeaf9b"}, - {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67cd16537df51baabde3baa770ab7b8d16839c4d21219d5b96ac59fb012ebd2d"}, - {file = "cytoolz-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9"}, - {file = "cytoolz-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f89c48d8e5aec55ffd566a8ec858706d70ed0c6a50228eca30986bfa5b4da8b"}, - {file = "cytoolz-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6944bb93b287032a4c5ca6879b69bcd07df46f3079cf8393958cf0b0454f50c0"}, - {file = "cytoolz-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e027260fd2fc5cb041277158ac294fc13dca640714527219f702fb459a59823a"}, - {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88662c0e07250d26f5af9bc95911e6137e124a5c1ec2ce4a5d74de96718ab242"}, - {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309dffa78b0961b4c0cf55674b828fbbc793cf2d816277a5c8293c0c16155296"}, - {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb34246e6eb40343c5860fc51b24937698e4fa1ee415917a73ad772a9a1746b"}, - {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54da7a8e4348a18d45d4d5bc84af6c716d7f131113a4f1cc45569d37edff1b"}, - {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:241c679c3b1913c0f7259cf1d9639bed5084c86d0051641d537a0980548aa266"}, - {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bfc860251a8f280ac79696fc3343cfc3a7c30b94199e0240b6c9e5b6b01a2a5"}, - {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8edd1547014050c1bdad3ff85d25c82bd1c2a3c96830c6181521eb78b9a42b3"}, - {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b349bf6162e8de215403d7f35f8a9b4b1853dc2a48e6e1a609a5b1a16868b296"}, - {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1b18b35256219b6c3dd0fa037741b85d0bea39c552eab0775816e85a52834140"}, - {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581"}, - {file = "cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9"}, - {file = "cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297"}, - {file = "cytoolz-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3a509e4ac8e711703c368476b9bbce921fcef6ebb87fa3501525f7000e44185"}, - {file = "cytoolz-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7eecab6373e933dfbf4fdc0601d8fd7614f8de76793912a103b5fccf98170cd"}, - {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e55ed62087f6e3e30917b5f55350c3b6be6470b849c6566018419cd159d2cebc"}, - {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43de33d99a4ccc07234cecd81f385456b55b0ea9c39c9eebf42f024c313728a5"}, - {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139bed875828e1727018aa0982aa140e055cbafccb7fd89faf45cbb4f2a21514"}, - {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22c12671194b518aa8ce2f4422bd5064f25ab57f410ba0b78705d0a219f4a97a"}, - {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79888f2f7dc25709cd5d37b032a8833741e6a3692c8823be181d542b5999128e"}, - {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:51628b4eb41fa25bd428f8f7b5b74fbb05f3ae65fbd265019a0dd1ded4fdf12a"}, - {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1db9eb7179285403d2fb56ba1ff6ec35a44921b5e2fa5ca19d69f3f9f0285ea5"}, - {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:08ab7efae08e55812340bfd1b3f09f63848fe291675e2105eab1aa5327d3a16e"}, - {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e5fdc5264f884e7c0a1711a81dff112708a64b9c8561654ee578bfdccec6be09"}, - {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:90d6a2e6ab891043ee655ec99d5e77455a9bee9e1131bdfcfb745edde81200dd"}, - {file = "cytoolz-1.0.1-cp38-cp38-win32.whl", hash = "sha256:08946e083faa5147751b34fbf78ab931f149ef758af5c1092932b459e18dcf5c"}, - {file = "cytoolz-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:a91b4e10a9c03796c0dc93e47ebe25bb41ecc6fafc3cf5197c603cf767a3d44d"}, - {file = "cytoolz-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:980c323e626ba298b77ae62871b2de7c50b9d7219e2ddf706f52dd34b8be7349"}, - {file = "cytoolz-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:45f6fa1b512bc2a0f2de5123db932df06c7f69d12874fe06d67772b2828e2c8b"}, - {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93f42d9100c415155ad1f71b0de362541afd4ac95e3153467c4c79972521b6b"}, - {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a76d20dec9c090cdf4746255bbf06a762e8cc29b5c9c1d138c380bbdb3122ade"}, - {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:239039585487c69aa50c5b78f6a422016297e9dea39755761202fb9f0530fe87"}, - {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28307640ca2ab57b9fbf0a834b9bf563958cd9e038378c3a559f45f13c3c541"}, - {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:454880477bb901cee3a60f6324ec48c95d45acc7fecbaa9d49a5af737ded0595"}, - {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:902115d1b1f360fd81e44def30ac309b8641661150fcbdde18ead446982ada6a"}, - {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e68e6b38473a3a79cee431baa22be31cac39f7df1bf23eaa737eaff42e213883"}, - {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:32fba3f63fcb76095b0a22f4bdcc22bc62a2bd2d28d58bf02fd21754c155a3ec"}, - {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0724ba4cf41eb40b6cf75250820ab069e44bdf4183ff78857aaf4f0061551075"}, - {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c42420e0686f887040d5230420ed44f0e960ccbfa29a0d65a3acd9ca52459209"}, - {file = "cytoolz-1.0.1-cp39-cp39-win32.whl", hash = "sha256:4ba8b16358ea56b1fe8e637ec421e36580866f2e787910bac1cf0a6997424a34"}, - {file = "cytoolz-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:92d27f84bf44586853d9562bfa3610ecec000149d030f793b4cb614fd9da1813"}, - {file = "cytoolz-1.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:83d19d55738ad9c60763b94f3f6d3c6e4de979aeb8d76841c1401081e0e58d96"}, - {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f112a71fad6ea824578e6393765ce5c054603afe1471a5c753ff6c67fd872d10"}, - {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a515df8f8aa6e1eaaf397761a6e4aff2eef73b5f920aedf271416d5471ae5ee"}, - {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c398e7b7023460bea2edffe5fcd0a76029580f06c3f6938ac3d198b47156f3"}, - {file = "cytoolz-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3237e56211e03b13df47435b2369f5df281e02b04ad80a948ebd199b7bc10a47"}, - {file = "cytoolz-1.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba0d1da50aab1909b165f615ba1125c8b01fcc30d606c42a61c42ea0269b5e2c"}, - {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25b6e8dec29aa5a390092d193abd673e027d2c0b50774ae816a31454286c45c7"}, - {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36cd6989ebb2f18fe9af8f13e3c61064b9f741a40d83dc5afeb0322338ad25f2"}, - {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47394f8ab7fca3201f40de61fdeea20a2baffb101485ae14901ea89c3f6c95d"}, - {file = "cytoolz-1.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d00ac423542af944302e034e618fb055a0c4e87ba704cd6a79eacfa6ac83a3c9"}, - {file = "cytoolz-1.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a5ca923d1fa632f7a4fb33c0766c6fba7f87141a055c305c3e47e256fb99c413"}, - {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058bf996bcae9aad3acaeeb937d42e0c77c081081e67e24e9578a6a353cb7fb2"}, - {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69e2a1f41a3dad94a17aef4a5cc003323359b9f0a9d63d4cc867cb5690a2551d"}, - {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67daeeeadb012ec2b59d63cb29c4f2a2023b0c4957c3342d354b8bb44b209e9a"}, - {file = "cytoolz-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:54d3d36bbf0d4344d1afa22c58725d1668e30ff9de3a8f56b03db1a6da0acb11"}, - {file = "cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6"}, -] - -[package.dependencies] -toolz = ">=0.8.0" - -[package.extras] -cython = ["cython"] - -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - -[[package]] -name = "eth-abi" -version = "5.0.1" -description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" -optional = false -python-versions = ">=3.8, <4" -files = [ - {file = "eth_abi-5.0.1-py3-none-any.whl", hash = "sha256:521960d8b4beee514958e1774951dc6b48176aa274e3bd8b166f6921453047ef"}, - {file = "eth_abi-5.0.1.tar.gz", hash = "sha256:e9425110c6120c585c9f0db2e8a33d76c4b886b148a65e68fc0035d3917a3b9c"}, -] - -[package.dependencies] -eth-typing = ">=3.0.0" -eth-utils = ">=2.0.0" -parsimonious = ">=0.9.0,<0.10.0" - -[package.extras] -dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)"] -tools = ["hypothesis (>=4.18.2,<5.0.0)"] - -[[package]] -name = "eth-account" -version = "0.10.0" -description = "eth-account: Sign Ethereum transactions and messages with local private keys" -optional = false -python-versions = ">=3.7, <4" -files = [ - {file = "eth-account-0.10.0.tar.gz", hash = "sha256:474a2fccf7286230cf66502565f03b536921d7e1fdfceba198e42160e5ac4bc1"}, - {file = "eth_account-0.10.0-py3-none-any.whl", hash = "sha256:b7a83f506a8edf57926569e5f04471ce3f1700e572d3421b4ad0dad7a26c0978"}, -] - -[package.dependencies] -bitarray = ">=2.4.0" -eth-abi = ">=4.0.0-b.2" -eth-keyfile = ">=0.6.0" -eth-keys = ">=0.4.0" -eth-rlp = ">=0.3.0" -eth-utils = ">=2.0.0" -hexbytes = ">=0.1.0,<0.4.0" -rlp = ">=1.0.0" - -[package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coverage", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=4.18.0,<5)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] -test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "eth-hash" -version = "0.7.0" -description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -optional = false -python-versions = ">=3.8, <4" -files = [ - {file = "eth-hash-0.7.0.tar.gz", hash = "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a"}, - {file = "eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f"}, -] - -[package.extras] -dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -pycryptodome = ["pycryptodome (>=3.6.6,<4)"] -pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] -test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "eth-keyfile" -version = "0.8.1" -description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" -optional = false -python-versions = "<4,>=3.8" -files = [ - {file = "eth_keyfile-0.8.1-py3-none-any.whl", hash = "sha256:65387378b82fe7e86d7cb9f8d98e6d639142661b2f6f490629da09fddbef6d64"}, - {file = "eth_keyfile-0.8.1.tar.gz", hash = "sha256:9708bc31f386b52cca0969238ff35b1ac72bd7a7186f2a84b86110d3c973bec1"}, -] - -[package.dependencies] -eth-keys = ">=0.4.0" -eth-utils = ">=2" -pycryptodome = ">=3.6.6,<4" - -[package.extras] -dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["towncrier (>=21,<22)"] -test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "eth-keys" -version = "0.6.0" -description = "eth-keys: Common API for Ethereum key operations" -optional = false -python-versions = "<4,>=3.8" -files = [ - {file = "eth_keys-0.6.0-py3-none-any.whl", hash = "sha256:b396fdfe048a5bba3ef3990739aec64901eb99901c03921caa774be668b1db6e"}, - {file = "eth_keys-0.6.0.tar.gz", hash = "sha256:ba33230f851d02c894e83989185b21d76152c49b37e35b61b1d8a6d9f1d20430"}, -] - -[package.dependencies] -eth-typing = ">=3" -eth-utils = ">=2" - -[package.extras] -coincurve = ["coincurve (>=12.0.0)"] -dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["towncrier (>=21,<22)"] -test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] - -[[package]] -name = "eth-rlp" -version = "1.0.1" -description = "eth-rlp: RLP definitions for common Ethereum objects in Python" -optional = false -python-versions = ">=3.8, <4" -files = [ - {file = "eth-rlp-1.0.1.tar.gz", hash = "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027"}, - {file = "eth_rlp-1.0.1-py3-none-any.whl", hash = "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517"}, -] - -[package.dependencies] -eth-utils = ">=2.0.0" -hexbytes = ">=0.1.0,<1" -rlp = ">=0.6.0" -typing-extensions = {version = ">=4.0.1", markers = "python_version <= \"3.11\""} - -[package.extras] -dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "eth-typing" -version = "5.0.1" -description = "eth-typing: Common type annotations for ethereum python packages" -optional = false -python-versions = "<4,>=3.8" -files = [ - {file = "eth_typing-5.0.1-py3-none-any.whl", hash = "sha256:f30d1af16aac598f216748a952eeb64fbcb6e73efa691d2de31148138afe96de"}, - {file = "eth_typing-5.0.1.tar.gz", hash = "sha256:83debf88c9df286db43bb7374974681ebcc9f048fac81be2548dbc549a3203c0"}, -] - -[package.dependencies] -typing-extensions = ">=4.5.0" - -[package.extras] -dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "eth-utils" -version = "5.1.0" -description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -optional = false -python-versions = "<4,>=3.8" -files = [ - {file = "eth_utils-5.1.0-py3-none-any.whl", hash = "sha256:a99f1f01b51206620904c5af47fac65abc143aebd0a76bdec860381c5a3230f8"}, - {file = "eth_utils-5.1.0.tar.gz", hash = "sha256:84c6314b9cf1fcd526107464bbf487e3f87097a2e753360d5ed319f7d42e3f20"}, -] - -[package.dependencies] -cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} -eth-hash = ">=0.3.1" -eth-typing = ">=5.0.0" -toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} - -[package.extras] -dev = ["build (>=0.9.0)", "bump-my-version (>=0.19.0)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -test = ["hypothesis (>=4.43.0)", "mypy (==1.10.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "farcaster" -version = "0.7.11" -description = "farcaster-py is a Python SDK for the Farcaster Protocol" -optional = false -python-versions = ">=3.8.0,<4.0.0" -files = [ - {file = "farcaster-0.7.11-py3-none-any.whl", hash = "sha256:b830b8f7683fd01a8d828cac71c59bc3410aa7758c73bdfa391610f9f2bb1517"}, - {file = "farcaster-0.7.11.tar.gz", hash = "sha256:45746e3d1718bed6dd231764da0ad5805f643a23e1a4a07907a8970c4e28e6bf"}, -] - -[package.dependencies] -canonicaljson = ">=1.6.4,<3.0.0" -eth-account = ">=0.8,<0.11" -parsimonious = ">=0.8.1,<0.10.0" -pydantic = ">=1.9.2,<3.0.0" -pyhumps = ">=3.7.2,<4.0.0" -python-dotenv = ">=0.21,<1.1" -requests = ">=2.28.1,<3.0.0" - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "hexbytes" -version = "0.3.1" -description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" -optional = false -python-versions = ">=3.7, <4" -files = [ - {file = "hexbytes-0.3.1-py3-none-any.whl", hash = "sha256:383595ad75026cf00abd570f44b368c6cdac0c6becfae5c39ff88829877f8a59"}, - {file = "hexbytes-0.3.1.tar.gz", hash = "sha256:a3fe35c6831ee8fafd048c4c086b986075fc14fd46258fa24ecb8d65745f9a9d"}, -] - -[package.extras] -dev = ["black (>=22)", "bumpversion (>=0.5.3)", "eth-utils (>=1.0.1,<3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=22)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)"] -test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "httpcore" -version = "1.0.7" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "jiter" -version = "0.8.2" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.8" -files = [ - {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, - {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49"}, - {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d"}, - {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff"}, - {file = "jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43"}, - {file = "jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105"}, - {file = "jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b"}, - {file = "jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc"}, - {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88"}, - {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6"}, - {file = "jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44"}, - {file = "jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855"}, - {file = "jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f"}, - {file = "jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d"}, - {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152"}, - {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29"}, - {file = "jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e"}, - {file = "jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c"}, - {file = "jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84"}, - {file = "jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1"}, - {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9"}, - {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05"}, - {file = "jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a"}, - {file = "jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865"}, - {file = "jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca"}, - {file = "jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0"}, - {file = "jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566"}, - {file = "jiter-0.8.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9e1fa156ee9454642adb7e7234a383884452532bc9d53d5af2d18d98ada1d79c"}, - {file = "jiter-0.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cf5dfa9956d96ff2efb0f8e9c7d055904012c952539a774305aaaf3abdf3d6c"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e52bf98c7e727dd44f7c4acb980cb988448faeafed8433c867888268899b298b"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a2ecaa3c23e7a7cf86d00eda3390c232f4d533cd9ddea4b04f5d0644faf642c5"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08d4c92bf480e19fc3f2717c9ce2aa31dceaa9163839a311424b6862252c943e"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d9a1eded738299ba8e106c6779ce5c3893cffa0e32e4485d680588adae6db8"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20be8b7f606df096e08b0b1b4a3c6f0515e8dac296881fe7461dfa0fb5ec817"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d33f94615fcaf872f7fd8cd98ac3b429e435c77619777e8a449d9d27e01134d1"}, - {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:317b25e98a35ffec5c67efe56a4e9970852632c810d35b34ecdd70cc0e47b3b6"}, - {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9043259ee430ecd71d178fccabd8c332a3bf1e81e50cae43cc2b28d19e4cb7"}, - {file = "jiter-0.8.2-cp38-cp38-win32.whl", hash = "sha256:fc5adda618205bd4678b146612ce44c3cbfdee9697951f2c0ffdef1f26d72b63"}, - {file = "jiter-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cd646c827b4f85ef4a78e4e58f4f5854fae0caf3db91b59f0d73731448a970c6"}, - {file = "jiter-0.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e41e75344acef3fc59ba4765df29f107f309ca9e8eace5baacabd9217e52a5ee"}, - {file = "jiter-0.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f22b16b35d5c1df9dfd58843ab2cd25e6bf15191f5a236bed177afade507bfc"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7200b8f7619d36aa51c803fd52020a2dfbea36ffec1b5e22cab11fd34d95a6d"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70bf4c43652cc294040dbb62256c83c8718370c8b93dd93d934b9a7bf6c4f53c"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9d471356dc16f84ed48768b8ee79f29514295c7295cb41e1133ec0b2b8d637d"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:859e8eb3507894093d01929e12e267f83b1d5f6221099d3ec976f0c995cb6bd9"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa58399c01db555346647a907b4ef6d4f584b123943be6ed5588c3f2359c9f4"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f2d5ed877f089862f4c7aacf3a542627c1496f972a34d0474ce85ee7d939c27"}, - {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:03c9df035d4f8d647f8c210ddc2ae0728387275340668fb30d2421e17d9a0841"}, - {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8bd2a824d08d8977bb2794ea2682f898ad3d8837932e3a74937e93d62ecbb637"}, - {file = "jiter-0.8.2-cp39-cp39-win32.whl", hash = "sha256:ca29b6371ebc40e496995c94b988a101b9fbbed48a51190a4461fcb0a68b4a36"}, - {file = "jiter-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c0dfbd1be3cbefc7510102370d86e35d1d53e5a93d48519688b1bf0f761160a"}, - {file = "jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d"}, -] - -[[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -optional = false -python-versions = ">=3.6" -files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, -] - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[package]] -name = "openai" -version = "1.58.1" -description = "The official Python library for the openai API" -optional = false -python-versions = ">=3.8" -files = [ - {file = "openai-1.58.1-py3-none-any.whl", hash = "sha256:e2910b1170a6b7f88ef491ac3a42c387f08bd3db533411f7ee391d166571d63c"}, - {file = "openai-1.58.1.tar.gz", hash = "sha256:f5a035fd01e141fc743f4b0e02c41ca49be8fab0866d3b67f5f29b4f4d3c0973"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -tqdm = ">4" -typing-extensions = ">=4.11,<5" - -[package.extras] -datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -realtime = ["websockets (>=13,<15)"] - -[[package]] -name = "parsimonious" -version = "0.9.0" -description = "(Soon to be) the fastest pure-Python PEG parser I could muster" -optional = false -python-versions = "*" -files = [ - {file = "parsimonious-0.9.0.tar.gz", hash = "sha256:b2ad1ae63a2f65bd78f5e0a8ac510a98f3607a43f1db2a8d46636a5d9e4a30c1"}, -] - -[package.dependencies] -regex = ">=2022.3.15" - -[[package]] -name = "prompt-toolkit" -version = "3.0.48" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "pycryptodome" -version = "3.21.0" -description = "Cryptographic library for Python" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, - {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, - {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e"}, - {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e"}, - {file = "pycryptodome-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a"}, - {file = "pycryptodome-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3"}, - {file = "pycryptodome-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d"}, - {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb"}, - {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568"}, - {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819"}, - {file = "pycryptodome-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3"}, - {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4"}, - {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b"}, - {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e"}, - {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8"}, - {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1"}, - {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a"}, - {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2"}, - {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93"}, - {file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764"}, - {file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53"}, - {file = "pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca"}, - {file = "pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0"}, - {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6"}, - {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f"}, - {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b"}, - {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58"}, - {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c"}, - {file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297"}, -] - -[[package]] -name = "pydantic" -version = "2.10.3" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, - {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.27.1" -typing-extensions = ">=4.12.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] - -[[package]] -name = "pydantic-core" -version = "2.27.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, - {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, - {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, - {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, - {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, - {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, - {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, - {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, - {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, - {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, - {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, - {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, - {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, - {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, - {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, - {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, - {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pyhumps" -version = "3.8.0" -description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node" -optional = false -python-versions = "*" -files = [ - {file = "pyhumps-3.8.0-py3-none-any.whl", hash = "sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6"}, - {file = "pyhumps-3.8.0.tar.gz", hash = "sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3"}, -] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "regex" -version = "2024.11.6" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, - {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, - {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, - {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, - {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-oauthlib" -version = "1.3.1" -description = "OAuthlib authentication support for Requests." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, - {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, -] - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - -[[package]] -name = "rlp" -version = "4.0.1" -description = "rlp: A package for Recursive Length Prefix encoding and decoding" -optional = false -python-versions = "<4,>=3.8" -files = [ - {file = "rlp-4.0.1-py3-none-any.whl", hash = "sha256:ff6846c3c27b97ee0492373aa074a7c3046aadd973320f4fffa7ac45564b0258"}, - {file = "rlp-4.0.1.tar.gz", hash = "sha256:bcefb11013dfadf8902642337923bd0c786dc8a27cb4c21da6e154e52869ecb1"}, -] - -[package.dependencies] -eth-utils = ">=2" - -[package.extras] -dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -rust-backend = ["rusty-rlp (>=0.2.1)"] -test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "toolz" -version = "1.0.0" -description = "List processing tools and functional utilities" -optional = false -python-versions = ">=3.8" -files = [ - {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, - {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, -] - -[[package]] -name = "tqdm" -version = "4.67.1" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, - {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "tweepy" -version = "4.14.0" -description = "Twitter library for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tweepy-4.14.0-py3-none-any.whl", hash = "sha256:db6d3844ccc0c6d27f339f12ba8acc89912a961da513c1ae50fa2be502a56afb"}, - {file = "tweepy-4.14.0.tar.gz", hash = "sha256:1f9f1707d6972de6cff6c5fd90dfe6a449cd2e0d70bd40043ffab01e07a06c8c"}, -] - -[package.dependencies] -oauthlib = ">=3.2.0,<4" -requests = ">=2.27.0,<3" -requests-oauthlib = ">=1.2.0,<2" - -[package.extras] -async = ["aiohttp (>=3.7.3,<4)", "async-lru (>=1.0.3,<3)"] -dev = ["coverage (>=4.4.2)", "coveralls (>=2.1.0)", "tox (>=3.21.0)"] -docs = ["myst-parser (==0.15.2)", "readthedocs-sphinx-search (==0.1.1)", "sphinx (==4.2.0)", "sphinx-hoverxref (==0.7b1)", "sphinx-rtd-theme (==1.0.0)", "sphinx-tabs (==3.2.0)"] -socks = ["requests[socks] (>=2.27.0,<3)"] -test = ["vcrpy (>=1.10.3)"] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "c0fdf41a32ed339450ee7e62e8f364e2249ccfd7326aea14ba3300176c8899ae" diff --git a/pyproject.toml b/pyproject.toml index 629af58c..6e3d0370 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,18 @@ readme = "README.md" python = "^3.10" python-dotenv = "^1.0.1" openai = "^1.57.2" -tweepy = "^4.14.0" prompt-toolkit = "^3.0.48" anthropic = "^0.42.0" farcaster = "^0.7.11" +solders = "^0.21.0,<0.24.0" +solana = "^0.35.0" +aiohttp = "^3.11.11" +requests = "2.32.3" +jupiter-python-sdk = "^0.0.2.0" +web3 = "<=6.14.0" +eth-keys = ">=0.4.0,<0.5.0" +web3-ethereum-defi = {version = "^0.27", extras = ["data"]} +aioetherscan = "^0.9.4" [build-system] diff --git a/src/action_handler.py b/src/action_handler.py index de487f30..eb04e120 100644 --- a/src/action_handler.py +++ b/src/action_handler.py @@ -1,6 +1,6 @@ import logging -logger = logging.getLogger("agent") +logger = logging.getLogger("action_handler") action_registry = {} diff --git a/src/actions/evm_actions.py b/src/actions/evm_actions.py new file mode 100644 index 00000000..3c463278 --- /dev/null +++ b/src/actions/evm_actions.py @@ -0,0 +1,270 @@ +import logging +from src.action_handler import register_action + +logger = logging.getLogger("agent") + + +@register_action("evm-transfer") +def evm_transfer(agent, **kwargs): + """Transfer Native or ERC20 tokens""" + agent.logger.info("\n💸 INITIATING TRANSFER") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="transfer", + params=[ + kwargs.get("to_address"), + kwargs.get("amount"), + kwargs.get("token_mint", None), + ], + ) + agent.logger.info("✅ Transfer completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Transfer failed: {str(e)}") + return False + + +@register_action("evm-swap") +def evm_swap(agent, **kwargs): + """Swap tokens using Jupiter""" + agent.logger.info("\n🔄 INITIATING TOKEN SWAP") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="trade", + params=[ + kwargs.get("output_mint"), + kwargs.get("input_amount"), + kwargs.get("input_mint", None), + kwargs.get("slippage_bps", 100), + ], + ) + agent.logger.info("✅ Swap completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Swap failed: {str(e)}") + return False + + +@register_action("evm-balance") +def evm_balance(agent, **kwargs): + """Check Native or token balance""" + agent.logger.info("\n💰 CHECKING BALANCE") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="get-balance", + params=[kwargs.get("token_address", None)], + ) + agent.logger.info(f"Balance: {result}") + return result + except Exception as e: + agent.logger.error(f"❌ Balance check failed: {str(e)}") + return None + + +@register_action("evm-stake") +def evm_stake(agent, **kwargs): + """Stake Native""" + agent.logger.info("\n🎯 INITIATING Native STAKE") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", action_name="stake", params=[kwargs.get("amount")] + ) + agent.logger.info("✅ Staking completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Staking failed: {str(e)}") + return False + + +@register_action("evm-lend") +def evm_lend(agent, **kwargs): + """Lend assets using Lulo""" + agent.logger.info("\n🏦 INITIATING LENDING") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="lend-assets", + params=[kwargs.get("amount")], + ) + agent.logger.info("✅ Lending completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Lending failed: {str(e)}") + return False + + +@register_action("evm-request-funds") +def request_faucet_funds(agent, **kwargs): + """Request faucet funds for testing""" + agent.logger.info("\n🚰 REQUESTING FAUCET FUNDS") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", action_name="request-faucet", params=[] + ) + agent.logger.info("✅ Faucet request completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Faucet request failed: {str(e)}") + return False + + +@register_action("evm-deploy-token") +def evm_deploy_token(agent, **kwargs): + """Deploy a new token""" + agent.logger.info("\n🪙 DEPLOYING NEW TOKEN") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="deploy-token", + params=[kwargs.get("decimals", 9)], + ) + agent.logger.info("✅ Token deployed!") + return result + except Exception as e: + agent.logger.error(f"❌ Token deployment failed: {str(e)}") + return False + + +@register_action("evm-get-price") +def evm_get_price(agent, **kwargs): + """Get token price""" + agent.logger.info("\n💲 FETCHING TOKEN PRICE") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="fetch-price", + params=[kwargs.get("token_id")], + ) + agent.logger.info(f"Price: {result}") + return result + except Exception as e: + agent.logger.error(f"❌ Price fetch failed: {str(e)}") + return None + + +@register_action("evm-get-tps") +def evm_get_tps(agent, **kwargs): + """Get current Evm TPS""" + agent.logger.info("\n📊 FETCHING CURRENT TPS") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", action_name="get-tps", params=[] + ) + agent.logger.info(f"Current TPS: {result}") + return result + except Exception as e: + agent.logger.error(f"❌ TPS fetch failed: {str(e)}") + return None + + +@register_action("evm-get-token-by-ticker") +def get_token_data_by_ticker(agent, **kwargs): + """Get token data by ticker""" + agent.logger.info("\n🔍 FETCHING TOKEN DATA BY TICKER") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="get-token-by-ticker", + params=[kwargs.get("ticker")], + ) + agent.logger.info("✅ Token data retrieved!") + return result + except Exception as e: + agent.logger.error(f"❌ Token data fetch failed: {str(e)}") + return None + + +@register_action("evm-get-token-by-address") +def get_token_data_by_address(agent, **kwargs): + """Get token data by address""" + agent.logger.info("\n🔍 FETCHING TOKEN DATA BY ADDRESS") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="get-token-by-address", + params=[kwargs.get("mint")], + ) + agent.logger.info("✅ Token data retrieved!") + return result + except Exception as e: + agent.logger.error(f"❌ Token data fetch failed: {str(e)}") + return None + + +@register_action("evm-launch-pump-token") +def launch_pump_fun_token(agent, **kwargs): + """Launch a Pump & Fun token""" + agent.logger.info("\n🚀 LAUNCHING PUMP & FUN TOKEN") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="launch-pump-token", + params=[ + kwargs.get("token_name"), + kwargs.get("token_ticker"), + kwargs.get("description"), + kwargs.get("image_url"), + kwargs.get("options", {}), + ], + ) + agent.logger.info("✅ Token launched successfully!") + return result + except Exception as e: + agent.logger.error(f"❌ Token launch failed: {str(e)}") + return False + + +@register_action("evm-list-contract-functions") +def list_contract_functions(agent, **kwargs): + """List contract functions""" + agent.logger.info("\n📜 LISTING CONTRACT FUNCTIONS") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="list-contract-functions", + params=[kwargs.get("contract_address")], + ) + agent.logger.info("✅ Contract functions listed!") + return result + except Exception as e: + agent.logger.error(f"❌ Contract functions listing failed: {str(e)}") + return None + + +@register_action("evm-call-contract-function") +def call_contract_function(agent, **kwargs): + """Call contract function""" + agent.logger.info("\n📞 CALLING CONTRACT FUNCTION") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", + action_name="call-contract-function", + params=[ + kwargs.get("contract_address"), + kwargs.get("function_name"), + kwargs.get("args", []), + ], + ) + agent.logger.info("✅ Contract function called!") + return result + except Exception as e: + agent.logger.error(f"❌ Contract function call failed: {str(e)}") + return None + + +@register_action("evm-wrap-eth") +def wrap_eth(agent, **kwargs): + """Wrap ETH to WETH""" + agent.logger.info("\n🎁 WRAPPING ETH TO WETH") + try: + result = agent.connection_manager.perform_action( + connection_name="evm", action_name="wrap-eth", params=[kwargs.get("amount")] + ) + agent.logger.info("✅ ETH wrapped to WETH!") + return result + except Exception as e: + agent.logger.error(f"❌ ETH wrapping failed: {str(e)}") + return False diff --git a/src/actions/solana_actions.py b/src/actions/solana_actions.py new file mode 100644 index 00000000..244744e7 --- /dev/null +++ b/src/actions/solana_actions.py @@ -0,0 +1,211 @@ +import logging +from src.action_handler import register_action + +logger = logging.getLogger("agent") + +@register_action("sol-transfer") +def sol_transfer(agent, **kwargs): + """Transfer SOL or SPL tokens""" + agent.logger.info("\n💸 INITIATING TRANSFER") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="transfer", + params=[ + kwargs.get('to_address'), + kwargs.get('amount'), + kwargs.get('token_mint', None) + ] + ) + agent.logger.info("✅ Transfer completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Transfer failed: {str(e)}") + return False + +@register_action("sol-swap") +def sol_swap(agent, **kwargs): + """Swap tokens using Jupiter""" + agent.logger.info("\n🔄 INITIATING TOKEN SWAP") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="trade", + params=[ + kwargs.get('output_mint'), + kwargs.get('input_amount'), + kwargs.get('input_mint', None), + kwargs.get('slippage_bps', 100) + ] + ) + agent.logger.info("✅ Swap completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Swap failed: {str(e)}") + return False + +@register_action("sol-balance") +def sol_balance(agent, **kwargs): + """Check SOL or token balance""" + agent.logger.info("\n💰 CHECKING BALANCE") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="get-balance", + params=[kwargs.get('token_address', None)] + ) + agent.logger.info(f"Balance: {result}") + return result + except Exception as e: + agent.logger.error(f"❌ Balance check failed: {str(e)}") + return None + +@register_action("sol-stake") +def sol_stake(agent, **kwargs): + """Stake SOL""" + agent.logger.info("\n🎯 INITIATING SOL STAKE") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="stake", + params=[kwargs.get('amount')] + ) + agent.logger.info("✅ Staking completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Staking failed: {str(e)}") + return False + +@register_action("sol-lend") +def sol_lend(agent, **kwargs): + """Lend assets using Lulo""" + agent.logger.info("\n🏦 INITIATING LENDING") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="lend-assets", + params=[kwargs.get('amount')] + ) + agent.logger.info("✅ Lending completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Lending failed: {str(e)}") + return False + +@register_action("sol-request-funds") +def request_faucet_funds(agent, **kwargs): + """Request faucet funds for testing""" + agent.logger.info("\n🚰 REQUESTING FAUCET FUNDS") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="request-faucet", + params=[] + ) + agent.logger.info("✅ Faucet request completed!") + return result + except Exception as e: + agent.logger.error(f"❌ Faucet request failed: {str(e)}") + return False + +@register_action("sol-deploy-token") +def sol_deploy_token(agent, **kwargs): + """Deploy a new token""" + agent.logger.info("\n🪙 DEPLOYING NEW TOKEN") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="deploy-token", + params=[kwargs.get('decimals', 9)] + ) + agent.logger.info("✅ Token deployed!") + return result + except Exception as e: + agent.logger.error(f"❌ Token deployment failed: {str(e)}") + return False + +@register_action("sol-get-price") +def sol_get_price(agent, **kwargs): + """Get token price""" + agent.logger.info("\n💲 FETCHING TOKEN PRICE") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="fetch-price", + params=[kwargs.get('token_id')] + ) + agent.logger.info(f"Price: {result}") + return result + except Exception as e: + agent.logger.error(f"❌ Price fetch failed: {str(e)}") + return None + +@register_action("sol-get-tps") +def sol_get_tps(agent, **kwargs): + """Get current Solana TPS""" + agent.logger.info("\n📊 FETCHING CURRENT TPS") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="get-tps", + params=[] + ) + agent.logger.info(f"Current TPS: {result}") + return result + except Exception as e: + agent.logger.error(f"❌ TPS fetch failed: {str(e)}") + return None + +@register_action("sol-get-token-by-ticker") +def get_token_data_by_ticker(agent, **kwargs): + """Get token data by ticker""" + agent.logger.info("\n🔍 FETCHING TOKEN DATA BY TICKER") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="get-token-by-ticker", + params=[kwargs.get('ticker')] + ) + agent.logger.info("✅ Token data retrieved!") + return result + except Exception as e: + agent.logger.error(f"❌ Token data fetch failed: {str(e)}") + return None + +@register_action("sol-get-token-by-address") +def get_token_data_by_address(agent, **kwargs): + """Get token data by address""" + agent.logger.info("\n🔍 FETCHING TOKEN DATA BY ADDRESS") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="get-token-by-address", + params=[kwargs.get('mint')] + ) + agent.logger.info("✅ Token data retrieved!") + return result + except Exception as e: + agent.logger.error(f"❌ Token data fetch failed: {str(e)}") + return None + +@register_action("sol-launch-pump-token") +def launch_pump_fun_token(agent, **kwargs): + """Launch a Pump & Fun token""" + agent.logger.info("\n🚀 LAUNCHING PUMP & FUN TOKEN") + try: + result = agent.connection_manager.perform_action( + connection_name="solana", + action_name="launch-pump-token", + params=[ + kwargs.get('token_name'), + kwargs.get('token_ticker'), + kwargs.get('description'), + kwargs.get('image_url'), + kwargs.get('options', {}) + ] + ) + agent.logger.info("✅ Token launched successfully!") + return result + except Exception as e: + agent.logger.error(f"❌ Token launch failed: {str(e)}") + return False \ No newline at end of file diff --git a/src/actions/twitter_actions.py b/src/actions/twitter_actions.py index 390ff15f..47dea4f9 100644 --- a/src/actions/twitter_actions.py +++ b/src/actions/twitter_actions.py @@ -10,6 +10,8 @@ def post_tweet(agent, **kwargs): if ("last_tweet_time" not in agent.state): last_tweet_time = 0 + else: + last_tweet_time = agent.state["last_tweet_time"] if current_time - last_tweet_time >= agent.tweet_interval: agent.logger.info("\n📝 GENERATING NEW TWEET") @@ -91,4 +93,4 @@ def like_tweet(agent, **kwargs): return True else: agent.logger.info("\n👀 No tweets found to like...") - return False \ No newline at end of file + return False diff --git a/src/agent.py b/src/agent.py index 34886046..8d36519d 100644 --- a/src/agent.py +++ b/src/agent.py @@ -9,7 +9,9 @@ from src.helpers import print_h_bar from src.action_handler import execute_action import src.actions.twitter_actions -import src.actions.echochamber_actions +import src.actions.echochamber_actions +import src.actions.solana_actions +from datetime import datetime REQUIRED_FIELDS = ["name", "bio", "traits", "examples", "loop_delay", "config", "tasks"] @@ -35,6 +37,8 @@ def __init__( self.example_accounts = agent_dict["example_accounts"] self.loop_delay = agent_dict["loop_delay"] self.connection_manager = ConnectionManager(agent_dict["config"]) + self.use_time_based_weights = agent_dict["use_time_based_weights"] + self.time_based_multipliers = agent_dict["time_based_multipliers"] has_twitter_tasks = any("tweet" in task["name"] for task in agent_dict.get("tasks", [])) @@ -103,11 +107,33 @@ def _construct_system_prompt(self) -> str: action_name="get-latest-tweets", params=[example_account] ) - prompt_parts.extend(f"- {tweet['text']}" for tweet in tweets) + if tweets: + prompt_parts.extend(f"- {tweet['text']}" for tweet in tweets) self._system_prompt = "\n".join(prompt_parts) return self._system_prompt + + def _adjust_weights_for_time(self, current_hour: int, task_weights: list) -> list: + weights = task_weights.copy() + + # Reduce tweet frequency during night hours (1 AM - 5 AM) + if 1 <= current_hour <= 5: + weights = [ + weight * self.time_based_multipliers.get("tweet_night_multiplier", 0.4) if task["name"] == "post-tweet" + else weight + for weight, task in zip(weights, self.tasks) + ] + + # Increase engagement frequency during day hours (8 AM - 8 PM) (peak hours?🤔) + if 8 <= current_hour <= 20: + weights = [ + weight * self.time_based_multipliers.get("engagement_day_multiplier", 1.5) if task["name"] in ("reply-to-tweet", "like-tweet") + else weight + for weight, task in zip(weights, self.tasks) + ] + + return weights def prompt_llm(self, prompt: str, system_prompt: str = None) -> str: """Generate text using the configured LLM provider""" @@ -121,6 +147,15 @@ def prompt_llm(self, prompt: str, system_prompt: str = None) -> str: def perform_action(self, connection: str, action: str, **kwargs) -> None: return self.connection_manager.perform_action(connection, action, **kwargs) + + def select_action(self, use_time_based_weights: bool = False) -> dict: + task_weights = [weight for weight in self.task_weights.copy()] + + if use_time_based_weights: + current_hour = datetime.now().hour + task_weights = self._adjust_weights_for_time(current_hour, task_weights) + + return random.choices(self.tasks, weights=task_weights, k=1)[0] def loop(self): """Main agent loop for autonomous behavior""" @@ -163,7 +198,8 @@ def loop(self): # CHOOSE AN ACTION # TODO: Add agentic action selection - action = random.choices(self.tasks, weights=self.task_weights, k=1)[0] + + action = self.select_action(use_time_based_weights=self.use_time_based_weights) action_name = action["name"] # PERFORM ACTION diff --git a/src/cli.py b/src/cli.py index 28993f48..7cbf5b4e 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,6 +1,7 @@ import sys import json import logging +import os from dataclasses import dataclass from typing import Callable, Dict, List from pathlib import Path @@ -13,12 +14,14 @@ from src.helpers import print_h_bar # Configure logging -logging.basicConfig(level=logging.INFO, format='%(message)s') +logging.basicConfig(level=logging.INFO, format="%(message)s") logger = logging.getLogger(__name__) + @dataclass class Command: """Dataclass to represent a CLI command""" + name: str description: str tips: List[str] @@ -29,50 +32,55 @@ def __post_init__(self): if self.aliases is None: self.aliases = [] + class ZerePyCLI: def __init__(self): self.agent = None - + # Create config directory if it doesn't exist - self.config_dir = Path.home() / '.zerepy' + self.config_dir = Path.home() / ".zerepy" self.config_dir.mkdir(exist_ok=True) - + # Initialize command registry self._initialize_commands() - + # Setup prompt toolkit components self._setup_prompt_toolkit() def _initialize_commands(self) -> None: """Initialize all CLI commands""" self.commands: Dict[str, Command] = {} - + # Help command self._register_command( Command( name="help", description="Displays a list of all available commands, or help for a specific command.", - tips=["Try 'help' to see available commands.", - "Try 'help {command}' to get more information about a specific command."], + tips=[ + "Try 'help' to see available commands.", + "Try 'help {command}' to get more information about a specific command.", + ], handler=self.help, - aliases=['h', '?'] + aliases=["h", "?"], ) ) - - ################## AGENTS ################## + + ################## AGENTS ################## # Agent action command self._register_command( Command( name="agent-action", description="Runs a single agent action.", - tips=["Format: agent-action {connection} {action}", - "Use 'list-connections' to see available connections.", - "Use 'list-actions' to see available actions."], + tips=[ + "Format: agent-action {connection} {action}", + "Use 'list-connections' to see available connections.", + "Use 'list-actions' to see available actions.", + ], handler=self.agent_action, - aliases=['action', 'run'] + aliases=["action", "run"], ) ) - + # Agent loop command self._register_command( Command( @@ -80,34 +88,38 @@ def _initialize_commands(self) -> None: description="Starts the current agent's autonomous behavior loop.", tips=["Press Ctrl+C to stop the loop"], handler=self.agent_loop, - aliases=['loop', 'start'] + aliases=["loop", "start"], ) ) - + # List agents command self._register_command( Command( name="list-agents", description="Lists all available agents you have on file.", - tips=["Agents are stored in the 'agents' directory", - "Use 'load-agent' to load an available agent"], + tips=[ + "Agents are stored in the 'agents' directory", + "Use 'load-agent' to load an available agent", + ], handler=self.list_agents, - aliases=['agents', 'ls-agents'] + aliases=["agents", "ls-agents"], ) ) - + # Load agent command self._register_command( Command( name="load-agent", description="Loads an agent from a file.", - tips=["Format: load-agent {agent_name}", - "Use 'list-agents' to see available agents"], + tips=[ + "Format: load-agent {agent_name}", + "Use 'list-agents' to see available agents", + ], handler=self.load_agent, - aliases=['load'] + aliases=["load"], ) ) - + # Create agent command self._register_command( Command( @@ -115,18 +127,20 @@ def _initialize_commands(self) -> None: description="Creates a new agent.", tips=["Follow the interactive wizard to create a new agent"], handler=self.create_agent, - aliases=['new-agent', 'create'] + aliases=["new-agent", "create"], ) ) - + # Define default agent self._register_command( Command( name="set-default-agent", description="Define which model is loaded when the CLI starts.", - tips=["You can also just change the 'default_agent' field in agents/general.json"], + tips=[ + "You can also just change the 'default_agent' field in agents/general.json" + ], handler=self.set_default_agent, - aliases=['default'] + aliases=["default"], ) ) @@ -137,35 +151,39 @@ def _initialize_commands(self) -> None: description="Start a chat session with the current agent", tips=["Use 'exit' to end the chat session"], handler=self.chat_session, - aliases=['talk'] + aliases=["talk"], ) ) - - ################## CONNECTIONS ################## + + ################## CONNECTIONS ################## # List actions command self._register_command( Command( name="list-actions", description="Lists all available actions for the given connection.", - tips=["Format: list-actions {connection}", - "Use 'list-connections' to see available connections"], + tips=[ + "Format: list-actions {connection}", + "Use 'list-connections' to see available connections", + ], handler=self.list_actions, - aliases=['actions', 'ls-actions'] + aliases=["actions", "ls-actions"], ) ) - + # Configure connection command self._register_command( Command( name="configure-connection", description="Sets up a connection for API access.", - tips=["Format: configure-connection {connection}", - "Follow the prompts to enter necessary credentials"], + tips=[ + "Format: configure-connection {connection}", + "Follow the prompts to enter necessary credentials", + ], handler=self.configure_connection, - aliases=['config', 'setup'] + aliases=["config", "setup"], ) ) - + # List connections command self._register_command( Command( @@ -173,11 +191,11 @@ def _initialize_commands(self) -> None: description="Lists all available connections.", tips=["Shows both configured and unconfigured connections"], handler=self.list_connections, - aliases=['connections', 'ls-connections'] + aliases=["connections", "ls-connections"], ) ) - - ################## MISC ################## + + ################## MISC ################## # Exit command self._register_command( Command( @@ -185,33 +203,33 @@ def _initialize_commands(self) -> None: description="Exits the ZerePy CLI.", tips=["You can also use Ctrl+D to exit"], handler=self.exit, - aliases=['quit', 'q'] + aliases=["quit", "q"], ) ) def _setup_prompt_toolkit(self) -> None: """Setup prompt toolkit components""" - self.style = Style.from_dict({ - 'prompt': 'ansicyan bold', - 'command': 'ansigreen', - 'error': 'ansired bold', - 'success': 'ansigreen bold', - 'warning': 'ansiyellow', - }) + self.style = Style.from_dict( + { + "prompt": "ansicyan bold", + "command": "ansigreen", + "error": "ansired bold", + "success": "ansigreen bold", + "warning": "ansiyellow", + } + ) # Use FileHistory for persistent command history - history_file = self.config_dir / 'history.txt' - + history_file = self.config_dir / "history.txt" + self.completer = WordCompleter( - list(self.commands.keys()), - ignore_case=True, - sentence=True + list(self.commands.keys()), ignore_case=True, sentence=True ) - + self.session = PromptSession( completer=self.completer, style=self.style, - history=FileHistory(str(history_file)) + history=FileHistory(str(history_file)), ) ################### @@ -226,7 +244,7 @@ def _register_command(self, command: Command) -> None: def _get_prompt_message(self) -> HTML: """Generate the prompt message based on current state""" agent_status = f"({self.agent.name})" if self.agent else "(no agent)" - return HTML(f'ZerePy-CLI {agent_status} > ') + return HTML(f"ZerePy-CLI {agent_status} > ") def _handle_command(self, input_string: str) -> None: """Parse and handle a command input""" @@ -244,7 +262,7 @@ def _handle_command(self, input_string: str) -> None: def _handle_unknown_command(self, command: str) -> None: """Handle unknown command with suggestions""" - logger.warning(f"Unknown command: '{command}'") + logger.warning(f"Unknown command: '{command}'") # Suggest similar commands using basic string similarity suggestions = self._get_command_suggestions(command) @@ -254,17 +272,27 @@ def _handle_unknown_command(self, command: str) -> None: logger.info(f" - {suggestion}") logger.info("Use 'help' to see all available commands.") - def _get_command_suggestions(self, command: str, max_suggestions: int = 3) -> List[str]: + def _get_command_suggestions( + self, command: str, max_suggestions: int = 3 + ) -> List[str]: """Get command suggestions based on string similarity""" from difflib import get_close_matches - return get_close_matches(command, self.commands.keys(), n=max_suggestions, cutoff=0.6) - def _print_welcome_message(self) -> None: - """Print welcome message and initial status""" + return get_close_matches( + command, self.commands.keys(), n=max_suggestions, cutoff=0.6 + ) + + def _print_welcome_message(self, clearing: bool = False) -> None: + """Print welcome message and initial status + + Args: + clearing (bool): Whether this is being called during a screen clear + When True, skips the final horizontal bar to avoid doubles + """ print_h_bar() logger.info("👋 Welcome to the ZerePy CLI!") logger.info("Type 'help' for a list of commands.") - print_h_bar() + print_h_bar() def _show_command_help(self, command_name: str) -> None: """Show help for a specific command""" @@ -280,10 +308,10 @@ def _show_command_help(self, command_name: str) -> None: logger.info(f"\nHelp for '{command.name}':") logger.info(f"Description: {command.description}") - + if command.aliases: logger.info(f"Aliases: {', '.join(command.aliases)}") - + if command.tips: logger.info("\nTips:") for tip in command.tips: @@ -309,12 +337,16 @@ def _show_general_help(self) -> None: def _list_loaded_agent(self) -> None: if self.agent: - logger.info(f"\nStart the agent loop with the command 'start' or use one of the action commands.") + logger.info( + f"\nStart the agent loop with the command 'start' or use one of the action commands." + ) else: - logger.info(f"\nNo default agent is loaded, please use the load-agent command to do that.") + logger.info( + f"\nNo default agent is loaded, please use the load-agent command to do that." + ) def _load_agent_from_file(self, agent_name): - try: + try: self.agent = ZerePyAgent(agent_name) logger.info(f"\n✅ Successfully loaded agent: {self.agent.name}") except FileNotFoundError: @@ -330,13 +362,13 @@ def _load_default_agent(self) -> None: agent_general_config_path = Path("agents") / "general.json" file = None try: - file = open(agent_general_config_path, 'r') + file = open(agent_general_config_path, "r") data = json.load(file) - if not data.get('default_agent'): - logger.error('No default agent defined, please set one in general.json') + if not data.get("default_agent"): + logger.error("No default agent defined, please set one in general.json") return - self._load_agent_from_file(data.get('default_agent')) + self._load_agent_from_file(data.get("default_agent")) except FileNotFoundError: logger.error("File general.json not found, please create one.") return @@ -346,7 +378,7 @@ def _load_default_agent(self) -> None: finally: if file: file.close() - + ################### # Command functions ################### @@ -357,10 +389,17 @@ def help(self, input_list: List[str]) -> None: else: self._show_general_help() + def clear_screen(self, input_list: List[str]) -> None: + """Clear the terminal screen""" + os.system("cls" if os.name == "nt" else "clear") + self._print_welcome_message(clearing=True) + def agent_action(self, input_list: List[str]) -> None: """Handle agent action command""" if self.agent is None: - logger.info("No agent is currently loaded. Use 'load-agent' to load an agent.") + logger.info( + "No agent is currently loaded. Use 'load-agent' to load an agent." + ) return if len(input_list) < 3: @@ -370,9 +409,7 @@ def agent_action(self, input_list: List[str]) -> None: try: result = self.agent.perform_action( - connection=input_list[1], - action=input_list[2], - params=input_list[3:] + connection=input_list[1], action=input_list[2], params=input_list[3:] ) logger.info(f"Result: {result}") except Exception as e: @@ -381,7 +418,9 @@ def agent_action(self, input_list: List[str]) -> None: def agent_loop(self, input_list: List[str]) -> None: """Handle agent loop command""" if self.agent is None: - logger.info("No agent is currently loaded. Use 'load-agent' to load an agent.") + logger.info( + "No agent is currently loaded. Use 'load-agent' to load an agent." + ) return try: @@ -418,34 +457,36 @@ def load_agent(self, input_list: List[str]) -> None: return self._load_agent_from_file(agent_name=input_list[1]) - + def create_agent(self, input_list: List[str]) -> None: """Handle create agent command""" logger.info("\nℹ️ Agent creation wizard not implemented yet.") - logger.info("Please create agent JSON files manually in the 'agents' directory.") - + logger.info( + "Please create agent JSON files manually in the 'agents' directory." + ) + def set_default_agent(self, input_list: List[str]): """Handle set-default-agent command""" if len(input_list) < 2: logger.info("Please specify the same of the agent file.") return - + agent_general_config_path = Path("agents") / "general.json" file = None try: - file = open(agent_general_config_path, 'r') + file = open(agent_general_config_path, "r") data = json.load(file) agent_file_name = input_list[1] # if file does not exist, refuse to set it as default try: agent_path = Path("agents") / f"{agent_file_name}.json" - open(agent_path, 'r') + open(agent_path, "r") except FileNotFoundError: logging.error("Agent file not found.") return - - data['default_agent'] = input_list[1] - with open(agent_general_config_path, 'w') as f: + + data["default_agent"] = input_list[1] + with open(agent_general_config_path, "w") as f: json.dump(data, f, indent=4) logger.info(f"Agent {agent_file_name} is now set as default.") except FileNotFoundError: @@ -476,7 +517,9 @@ def configure_connection(self, input_list: List[str]) -> None: logger.info("Use 'list-connections' to see available connections.") return - self.agent.connection_manager.configure_connection(connection_name=input_list[1]) + self.agent.connection_manager.configure_connection( + connection_name=input_list[1] + ) def list_connections(self, input_list: List[str] = []) -> None: """Handle list connections command""" @@ -500,13 +543,13 @@ def chat_session(self, input_list: List[str]) -> None: while True: try: user_input = self.session.prompt("\nYou: ").strip() - if user_input.lower() == 'exit': + if user_input.lower() == "exit": break - + response = self.agent.prompt_llm(user_input) logger.info(f"\n{self.agent.name}: {response}") print_h_bar() - + except KeyboardInterrupt: break @@ -515,7 +558,6 @@ def exit(self, input_list: List[str]) -> None: logger.info("\nGoodbye! 👋") sys.exit(0) - ################### # Main CLI Loop ################### @@ -525,13 +567,12 @@ def main_loop(self) -> None: self._load_default_agent() self._list_loaded_agent() self.list_connections() - + # Start CLI loop while True: try: input_string = self.session.prompt( - self._get_prompt_message(), - style=self.style + self._get_prompt_message(), style=self.style ).strip() if not input_string: @@ -545,4 +586,4 @@ def main_loop(self) -> None: except EOFError: self.exit([]) except Exception as e: - logger.exception(f"Unexpected error: {e}") \ No newline at end of file + logger.exception(f"Unexpected error: {e}") diff --git a/src/connection_manager.py b/src/connection_manager.py index 0f355fe5..54be142a 100644 --- a/src/connection_manager.py +++ b/src/connection_manager.py @@ -8,15 +8,20 @@ from src.connections.farcaster_connection import FarcasterConnection from src.connections.ollama_connection import OllamaConnection from src.connections.echochambers_connection import EchochambersConnection +from src.connections.solana_connection import SolanaConnection +from src.connections.hyperbolic_connection import HyperbolicConnection +from src.connections.galadriel_connection import GaladrielConnection +from src.connections.evm_connection import EvmConnection logger = logging.getLogger("connection_manager") + class ConnectionManager: def __init__(self, agent_config): - self.connections : Dict[str, BaseConnection] = {} + self.connections: Dict[str, BaseConnection] = {} for config in agent_config: self._register_connection(config) - + @staticmethod def _class_name_to_type(class_name: str) -> Type[BaseConnection]: if class_name == "twitter": @@ -35,11 +40,11 @@ def _class_name_to_type(class_name: str) -> Type[BaseConnection]: return EchochambersConnection return None - + def _register_connection(self, config_dic: Dict[str, Any]) -> None: """ Create and register a new connection with configuration - + Args: name: Identifier for the connection connection_class: The connection class to instantiate @@ -53,12 +58,14 @@ def _register_connection(self, config_dic: Dict[str, Any]) -> None: except Exception as e: logging.error(f"Failed to initialize connection {name}: {e}") - def _check_connection(self, connection_string: str)-> bool: + def _check_connection(self, connection_string: str) -> bool: try: connection = self.connections[connection_string] return connection.is_configured(verbose=True) except KeyError: - logging.error("\nUnknown connection. Try 'list-connections' to see all supported connections.") + logging.error( + "\nUnknown connection. Try 'list-connections' to see all supported connections." + ) return False except Exception as e: logging.error(f"\nAn error occurred: {e}") @@ -69,15 +76,19 @@ def configure_connection(self, connection_name: str) -> bool: try: connection = self.connections[connection_name] success = connection.configure() - + if success: - logging.info(f"\n✅ SUCCESSFULLY CONFIGURED CONNECTION: {connection_name}") + logging.info( + f"\n✅ SUCCESSFULLY CONFIGURED CONNECTION: {connection_name}" + ) else: logging.error(f"\n❌ ERROR CONFIGURING CONNECTION: {connection_name}") return success - + except KeyError: - logging.error("\nUnknown connection. Try 'list-connections' to see all supported connections.") + logging.error( + "\nUnknown connection. Try 'list-connections' to see all supported connections." + ) return False except Exception as e: logging.error(f"\nAn error occurred: {e}") @@ -87,19 +98,25 @@ def list_connections(self) -> None: """List all available connections and their status""" logging.info("\nAVAILABLE CONNECTIONS:") for name, connection in self.connections.items(): - status = "✅ Configured" if connection.is_configured() else "❌ Not Configured" + status = ( + "✅ Configured" if connection.is_configured() else "❌ Not Configured" + ) logging.info(f"- {name}: {status}") def list_actions(self, connection_name: str) -> None: """List all available actions for a specific connection""" try: connection = self.connections[connection_name] - + if connection.is_configured(): - logging.info(f"\n✅ {connection_name} is configured. You can use any of its actions.") + logging.info( + f"\n✅ {connection_name} is configured. You can use any of its actions." + ) else: - logging.info(f"\n❌ {connection_name} is not configured. You must configure a connection to use its actions.") - + logging.info( + f"\n❌ {connection_name} is not configured. You must configure a connection to use its actions." + ) + logging.info("\nAVAILABLE ACTIONS:") for action_name, action in connection.actions.items(): logging.info(f"- {action_name}: {action.description}") @@ -107,53 +124,72 @@ def list_actions(self, connection_name: str) -> None: for param in action.parameters: req = "required" if param.required else "optional" logging.info(f" - {param.name} ({req}): {param.description}") - + except KeyError: - logging.error("\nUnknown connection. Try 'list-connections' to see all supported connections.") + logging.error( + "\nUnknown connection. Try 'list-connections' to see all supported connections." + ) except Exception as e: logging.error(f"\nAn error occurred: {e}") - def perform_action(self, connection_name: str, action_name: str, params: List[Any]) -> Optional[Any]: + def perform_action( + self, connection_name: str, action_name: str, params: List[Any] + ) -> Optional[Any]: """Perform an action on a specific connection with given parameters""" try: connection = self.connections[connection_name] - + if not connection.is_configured(): - logging.error(f"\nError: Connection '{connection_name}' is not configured") + logging.error( + f"\nError: Connection '{connection_name}' is not configured" + ) return None - + if action_name not in connection.actions: - logging.error(f"\nError: Unknown action '{action_name}' for connection '{connection_name}'") + logging.error( + f"\nError: Unknown action '{action_name}' for connection '{connection_name}'" + ) return None - + action = connection.actions[action_name] - + # Count required parameters - required_params_count = sum(1 for param in action.parameters if param.required) - + required_params_count = sum( + 1 for param in action.parameters if param.required + ) + # Check if we have enough parameters if len(params) != required_params_count: - param_names = [param.name for param in action.parameters if param.required] - logging.error(f"\nError: Expected {required_params_count} required parameters for {action_name}: {', '.join(param_names)}") + param_names = [ + param.name for param in action.parameters if param.required + ] + logging.error( + f"\nError: Expected {required_params_count} required parameters for {action_name}: {', '.join(param_names)}" + ) return None - + # Convert list of params to kwargs dictionary kwargs = {} param_index = 0 - for param in action.parameters: - if param.required: + + # Add provided parameters up to the number provided + for i, param in enumerate(action.parameters): + if param_index < len(params): kwargs[param.name] = params[param_index] param_index += 1 - + return connection.perform_action(action_name, kwargs) - + except Exception as e: - logging.error(f"\nAn error occurred while trying action {action_name} for {connection_name} connection: {e}") + logging.error( + f"\nAn error occurred while trying action {action_name} for {connection_name} connection: {e}" + ) return None def get_model_providers(self) -> List[str]: """Get a list of all LLM provider connections""" return [ - name for name, conn in self.connections.items() - if conn.is_configured() and getattr(conn, 'is_llm_provider', lambda: False) - ] \ No newline at end of file + name + for name, conn in self.connections.items() + if conn.is_configured() and getattr(conn, "is_llm_provider", lambda: False) + ] diff --git a/src/connections/anthropic_connection.py b/src/connections/anthropic_connection.py index f3024a6f..348add23 100644 --- a/src/connections/anthropic_connection.py +++ b/src/connections/anthropic_connection.py @@ -5,7 +5,7 @@ from anthropic import Anthropic, NotFoundError from src.connections.base_connection import BaseConnection, Action, ActionParameter -logger = logging.getLogger(__name__) +logger = logging.getLogger("connections.anthropic_connection") class AnthropicConnectionError(Exception): """Base exception for Anthropic connection errors""" @@ -78,17 +78,17 @@ def _get_client(self) -> Anthropic: def configure(self) -> bool: """Sets up Anthropic API authentication""" - print("\n🤖 ANTHROPIC API SETUP") + logger.info("\n🤖 ANTHROPIC API SETUP") if self.is_configured(): - print("\nAnthropic API is already configured.") + logger.info("\nAnthropic API is already configured.") response = input("Do you want to reconfigure? (y/n): ") if response.lower() != 'y': return True - print("\n📝 To get your Anthropic API credentials:") - print("1. Go to https://console.anthropic.com/settings/keys") - print("2. Create a new API key.") + logger.info("\n📝 To get your Anthropic API credentials:") + logger.info("1. Go to https://console.anthropic.com/settings/keys") + logger.info("2. Create a new API key.") api_key = input("\nEnter your Anthropic API key: ") @@ -103,8 +103,8 @@ def configure(self) -> bool: client = Anthropic(api_key=api_key) client.models.list() - print("\n✅ Anthropic API configuration successfully saved!") - print("Your API key has been stored in the .env file.") + logger.info("\n✅ Anthropic API configuration successfully saved!") + logger.info("Your API key has been stored in the .env file.") return True except Exception as e: diff --git a/src/connections/eternalai_connection.py b/src/connections/eternalai_connection.py index 60cad619..5b680c3d 100644 --- a/src/connections/eternalai_connection.py +++ b/src/connections/eternalai_connection.py @@ -1,11 +1,12 @@ import logging import os +import json from typing import Dict, Any from dotenv import load_dotenv, set_key from openai import OpenAI from src.connections.base_connection import BaseConnection, Action, ActionParameter -logger = logging.getLogger(__name__) +logger = logging.getLogger("connections.eternalai_connection") class EternalAIConnectionError(Exception): """Base exception for EternalAI connection errors""" @@ -79,18 +80,18 @@ def _get_client(self) -> OpenAI: def configure(self) -> bool: """Sets up EternalAI API authentication""" - print("\n🤖 EternalAI API SETUP") + logger.info("\n🤖 EternalAI API SETUP") if self.is_configured(): - print("\nEternalAI API is already configured.") + logger.info("\nEternalAI API is already configured.") response = input("Do you want to reconfigure? (y/n): ") if response.lower() != 'y': return True - print("\n📝 To get your EternalAI credentials:") - print("1. Visit https://eternalai.org/api") - print("2. Generate an API Key") - print("3. Use API url as https://api.eternalai.org/v1/") + logger.info("\n📝 To get your EternalAI credentials:") + logger.info("1. Visit https://eternalai.org/api") + logger.info("2. Generate an API Key") + logger.info("3. Use API url as https://api.eternalai.org/v1/") api_key = input("\nEnter your EternalAI API key: ") api_url = input("\nEnter your EternalAI API url: ") @@ -107,8 +108,8 @@ def configure(self) -> bool: client = OpenAI(api_key=api_key, base_url=api_url) client.models.list() - print("\n✅ EternalAI API configuration successfully saved!") - print("Your credentials have been stored in the .env file.") + logger.info("\n✅ EternalAI API configuration successfully saved!") + logger.info("Your credentials have been stored in the .env file.") return True except Exception as e: @@ -133,11 +134,17 @@ def is_configured(self, verbose=False) -> bool: logger.debug(f"Configuration check failed: {e}") return False - def generate_text(self, prompt: str, system_prompt: str, model: str = None, **kwargs) -> str: + def generate_text(self, prompt: str, system_prompt: str, model: str = None, chain_id: str = None, **kwargs) -> str: """Generate text using EternalAI models""" try: client = self._get_client() model = model or self.config["model"] + logger.info(f"model {model}") + + chain_id = chain_id or self.config["chain_id"] + if not chain_id or chain_id == "": + chain_id = "45762" + logger.info(f"chain_id {chain_id}") completion = client.chat.completions.create( model=model, @@ -145,8 +152,16 @@ def generate_text(self, prompt: str, system_prompt: str, model: str = None, **kw {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}, ], + extra_body={"chain_id": chain_id} ) + if completion.choices is None: + raise EternalAIAPIError(f"Text generation failed: completion.choices is None") + try: + if completion.onchain_data is not None: + logger.info(f"response onchain data: {json.dumps(completion.onchain_data, indent=4)}") + except: + logger.info(f"response onchain data object: {completion.onchain_data}", ) return completion.choices[0].message.content except Exception as e: diff --git a/src/connections/evm_connection.py b/src/connections/evm_connection.py new file mode 100644 index 00000000..62db40d2 --- /dev/null +++ b/src/connections/evm_connection.py @@ -0,0 +1,464 @@ +# std +import asyncio +import os +import logging +from typing import Any, Dict, List, Optional + +# dotenv +from dotenv import load_dotenv + +# web3 +from web3 import Web3 + +# src +from src.connections.base_connection import Action, ActionParameter, BaseConnection +from src.helpers.evm.transfer import EvmTransferHelper +from src.helpers.evm.contract import EvmContractHelper +from src.helpers.evm.etherscan import EtherscanHelper +from src.helpers.evm.trade import EvmTradeHelper +from src.constants import EVM_TOKENS +from src.helpers.evm import get_public_key_from_private_key + +from aioetherscan import Client as EtherscanClient +from asyncio_throttle import Throttler +from aiohttp_retry import ExponentialRetry + + +logger = logging.getLogger("connections.evm_connection") + + +class EvmConnectionError(Exception): + """Base exception for Evm connection errors""" + + pass + + +class EvmConfigurationError(EvmConnectionError): + """Raised when there are configuration/credential issues""" + + pass + + +class EvmConnection(BaseConnection): + def __init__(self, config: Dict[str, Any]): + self.chain = str(config["name"]).split("-")[1] + logger.info(f"Initializing Evm connection for {self.chain}...") + super().__init__(config) + + @property + def is_llm_provider(self) -> bool: + return False + + def _get_connection(self) -> Web3: + w3 = Web3(Web3.HTTPProvider(self.config["rpc"])) + if not w3.is_connected(): + raise EvmConnectionError("Evm connection failed") + w3.middleware_onion.clear() + # w3.eth.set_gas_price_strategy(node_default_gas_price_strategy) + return w3 + + def _get_private_key(self): + creds = self._get_credentials() + private_key = creds[f"{self.chain.upper()}_PRIVATE_KEY"] + return private_key + + def _get_etherscan_client(self) -> EtherscanClient: + creds = self._get_credentials() + url = self.config["scanner"] + path = f"{self.chain.upper()}_SCANNER_KEY" + api_key = creds[path] + url = f"https://{url}/api?apikey={api_key}" + if not api_key: + logger.error(f"{path} not found in environment") + raise ValueError(f"{path} not found in environment") + logger.debug(f"Found Scanner API Key: {api_key}") + + # create a client + throttler = Throttler(rate_limit=5, period=1) + retry_options = ExponentialRetry(attempts=3) + c = EtherscanClient( + api_key=api_key, throttler=throttler, retry_options=retry_options + ) + + return c + + def _get_credentials(self) -> Dict[str, str]: + """Get Evm credentials from environment with validation""" + logger.debug("Retrieving Evm Credentials") + load_dotenv() + scanner_key = f"{self.chain.upper()}_SCANNER_KEY" + private_key = f"{self.chain.upper()}_PRIVATE_KEY" + required_vars = { + scanner_key: "Etherscan API key", + private_key: " EVM private key", + } + credentials = {} + missing = [] + + for env_var, description in required_vars.items(): + value = os.getenv(env_var) + if not value: + missing.append(description) + credentials[env_var] = value + + if missing: + error_msg = f"Missing Evm credentials: {', '.join(missing)}" + raise EvmConfigurationError(error_msg) + + logger.debug("All required credentials found") + return credentials + + def _get_scanner_url_for_tx(self, tx_hash: str) -> str: + return f"https://{self.config['scanner']}/tx/{tx_hash}" + + def validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]: + """Validate Evm configuration from JSON""" + required_fields = ["rpc", "scanner"] + missing_fields = [field for field in required_fields if field not in config] + if missing_fields: + raise ValueError( + f"Missing required configuration fields: {', '.join(missing_fields)}" + ) + + if not isinstance(config["rpc"], str): + raise ValueError("rpc must be a positive integer") + + return config # For stub, accept any config + + def register_actions(self) -> None: + """Register available Evm actions""" + self.actions = { + "transfer": Action( + name="transfer", + parameters=[ + ActionParameter("to_address", True, str, "Destination address"), + ActionParameter( + "amount_in_ether", True, float, "Amount to transfer" + ), + ActionParameter( + "token_address", + False, + str, + "Token contract address (optional for ETH)", + ), + ], + description="Transfer EVM or ERC20 tokens", + ), + "trade": Action( + name="trade", + parameters=[ + ActionParameter( + "output_mint", True, str, "Output token mint address" + ), + ActionParameter("input_amount", True, float, "Input amount"), + ActionParameter( + "input_mint", False, str, "Input token mint (optional for EVM)" + ), + ActionParameter( + "slippage_bps", False, int, "Slippage in basis points" + ), + ], + description="Swap tokens using Jupiter", + ), + "get-balance": Action( + name="get-balance", + parameters=[ + ActionParameter( + "token_address", + False, + str, + "Token mint address (optional for EVM)", + ) + ], + description="Check EVM or token balance", + ), + "stake": Action( + name="stake", + parameters=[ + ActionParameter("amount", True, float, "Amount of EVM to stake") + ], + description="Stake EVM", + ), + "lend-assets": Action( + name="lend-assets", + parameters=[ActionParameter("amount", True, float, "Amount to lend")], + description="Lend assets", + ), + "request-faucet": Action( + name="request-faucet", + parameters=[], + description="Request funds from faucet for testing", + ), + "deploy-token": Action( + name="deploy-token", + parameters=[ + ActionParameter( + "decimals", False, int, "Token decimals (default 9)" + ) + ], + description="Deploy a new token", + ), + "fetch-price": Action( + name="fetch-price", + parameters=[ + ActionParameter( + "token_id", True, str, "Token ID to fetch price for" + ) + ], + description="Get token price", + ), + "get-tps": Action( + name="get-tps", parameters=[], description="Get current Evm TPS" + ), + "get-token-by-ticker": Action( + name="get-token-by-ticker", + parameters=[ + ActionParameter("ticker", True, str, "Token ticker symbol") + ], + description="Get token data by ticker symbol", + ), + "get-token-by-address": Action( + name="get-token-by-address", + parameters=[ActionParameter("mint", True, str, "Token mint address")], + description="Get token data by mint address", + ), + "launch-pump-token": Action( + name="launch-pump-token", + parameters=[ + ActionParameter("token_name", True, str, "Name of the token"), + ActionParameter("token_ticker", True, str, "Token ticker symbol"), + ActionParameter("description", True, str, "Token description"), + ActionParameter("image_url", True, str, "Token image URL"), + ActionParameter("options", False, dict, "Additional token options"), + ], + description="Launch a Pump & Fun token", + ), + "list-contract-functions": Action( + name="list-contract-functions", + parameters=[ + ActionParameter("contract_address", True, str, "Contract address") + ], + description="List functions of a contract", + ), + "call-contract": Action( + name="call-contract", + parameters=[ + ActionParameter("contract_address", True, str, "Contract address"), + ActionParameter("method", True, str, "Method name"), + ActionParameter("args", False, list, "Method arguments"), + ], + description="Call a contract method", + ), + "wrap-eth": Action( + name="wrap-eth", + parameters=[ + ActionParameter( + "amount_in_ether", True, float, "Amount of ETH to wrap" + ) + ], + description="Wrap ETH to WETH", + ), + } + + def configure(self) -> bool: + """Stub configuration""" + return True + + def is_configured(self, verbose: bool = True) -> bool: + """Stub configuration check""" + try: + + credentials = self._get_credentials() + logger.debug("Evm configuration is valid") + return True + + except Exception as e: + if verbose: + error_msg = str(e) + if isinstance(e, EvmConfigurationError): + error_msg = f"Configuration error: {error_msg}" + elif isinstance(e, EvmConnectionError): + error_msg = f"API validation error: {error_msg}" + logger.debug(f"Evm Configuration validation failed: {error_msg}") + return False + return True + + def transfer( + self, to_address: str, amount_in_ether: int, token_address: Optional[str] = None + ) -> str: + logger.info(f"STUB: Transfer {amount_in_ether} to {to_address}") + if token_address: + res = EvmTransferHelper.transfer_token(self, to_address, amount_in_ether) + return True + else: + res = EvmTransferHelper.transfer_evm(self, to_address, amount_in_ether) + logger.info( + f"Transferred {amount_in_ether} to {to_address}\nTransaction ID: {res}" + ) + return res + + def trade( + self, + output_mint: str, + input_amount: float, + input_mint: Optional[str] = EVM_TOKENS["WETH"], + slippage_bps: int = 100, + ) -> str: + logger.info(f"STUB: Swap {input_amount} for {output_mint}") + res = EvmTradeHelper.trade( + self._get_connection(), + self._get_private_key(), + output_mint, + input_amount, + input_mint, + slippage_bps, + ) + res = asyncio.run(res) + logger.info(f"Swapped {input_amount} for {output_mint}\nTransaction ID: {res}") + + return self._get_scanner_url_for_tx(res) + + def get_balance(self, token_address: str = None) -> float: + logger.info(f"STUB: Get balance") + web3 = self._get_connection() + priv_key = self._get_private_key() + pub_key = get_public_key_from_private_key(priv_key) + if token_address: + balance = asyncio.run( + EvmContractHelper.read_contract( + web3, token_address, "balanceOf", pub_key + ) + ) + decimals = asyncio.run( + EvmContractHelper.read_contract(web3, token_address, "decimals") + ) + balance = balance / 10**decimals + + return balance + else: + balance = web3.eth.get_balance(pub_key) + return balance / 10**18 + + def stake(self, amount: float) -> str: + logger.info(f"STUB: Stake {amount}") + + return "Not implemented" + + def lend_assets(self, amount: float) -> str: + logger.info(f"STUB: Lend {amount}") + return "Not implemented" + + def request_faucet(self) -> str: + logger.info(f"STUB: Request faucet") + return "Not implemented" + + def deploy_token(self, decimals: int = 9) -> str: + logger.info(f"STUB: Deploy token with {decimals} decimals") + return "Not implemented" + + def fetch_price(self, token_id: str) -> str: + logger.info(f"STUB: Fetch price for {token_id}") + return "Not implemented" + + def get_tps(self) -> int: + logger.info(f"STUB: Get TPS") + raise NotImplementedError("Not implemented") + # return 100 + + def get_token_by_ticker(self, ticker: str) -> Dict[str, Any]: + logger.info(f"STUB: Get token by ticker {ticker}") + raise NotImplementedError("Not implemented") + + # return {} + + def get_token_by_address(self, mint: str) -> Dict[str, Any]: + logger.info(f"STUB: Get token by mint {mint}") + raise NotImplementedError("Not implemented") + # return {} + + def wrap_eth(self, amount_in_ether: float) -> str: + logger.info(f"STUB: Wrap {amount_in_ether}") + web3 = self._get_connection() + amount_in_wei = web3.to_wei(amount_in_ether, "ether") + res = asyncio.run( + EvmContractHelper._call( + self._get_connection(), + self._get_private_key(), + EVM_TOKENS["WETH"], + "deposit", + amount_in_wei, + ) + ) + return self._get_scanner_url_for_tx(res) + + def list_contract_functions(self, contract_address: str) -> List: + logger.info(f"STUB: List contract functions for {contract_address}") + client = self._get_etherscan_client() + abi = asyncio.run(EtherscanHelper.get_contract_abi(client, contract_address)) + + res_str = "" + """ + example output: + 1. function_name1 + arg1: + name: arg1_name + type: arg1_type + arg2: + name: arg2_name + type: arg2_type + 2. function_name2 + arg1: + name: arg1_name + type: arg1_type + arg2: + name: arg2_name + type: arg2_type + + """ + for x, func in enumerate(abi, 1): + if func["type"] == "function": + res_str += f"\n{x}. {func['name']}\n" + for input in func["inputs"]: + res_str += f"\t{input['name']}:\n" + res_str += f"\t\tname: {input['name']}\n" + res_str += f"\t\ttype: {input['type']}\n" + + return res_str + + def call_contract(self, contract_address: str, method: str, args=[]) -> Any: + logger.info( + f"STUB: Call contract {contract_address} method {method} with {args}" + ) + res = asyncio.run( + EvmContractHelper.read_contract( + self._get_connection(), contract_address, method, *args + ) + ) + return res + + # todo: test on mainnet + def launch_pump_token( + self, + token_name: str, + token_ticker: str, + description: str, + image_url: str, + options: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + logger.info(f"STUB: Launch Pump & Fun token") + raise NotImplementedError("Not implemented") + # return {} + + def perform_action(self, action_name: str, kwargs) -> Any: + """Execute a Evm action with validation""" + if action_name not in self.actions: + raise KeyError(f"Unknown action: {action_name}") + + action = self.actions[action_name] + errors = action.validate_params(kwargs) + if errors: + raise ValueError(f"Invalid parameters: {', '.join(errors)}") + + method_name = action_name.replace("-", "_") + method = getattr(self, method_name) + return method(**kwargs) diff --git a/src/connections/farcaster_connection.py b/src/connections/farcaster_connection.py index 6b77a657..54699ab2 100644 --- a/src/connections/farcaster_connection.py +++ b/src/connections/farcaster_connection.py @@ -144,11 +144,11 @@ def configure(self) -> bool: if response.lower() != 'y': return True - print("\n📝 To get your Farcaster (Warpcast) recovery phrase (for connection):") - print("1. Open the Warpcast mobile app") - print("2. Navigate to Settings page (click profile picture on top left, then the gear icon on top right)") - print("3. Click 'Advanced' then 'Reveal recovery phrase'") - print("4. Copy your recovery phrase") + logger.info("\n📝 To get your Farcaster (Warpcast) recovery phrase (for connection):") + logger.info("1. Open the Warpcast mobile app") + logger.info("2. Navigate to Settings page (click profile picture on top left, then the gear icon on top right)") + logger.info("3. Click 'Advanced' then 'Reveal recovery phrase'") + logger.info("4. Copy your recovery phrase") recovery_phrase = input("\nEnter your Farcaster (Warpcast) recovery phrase: ") diff --git a/src/connections/galadriel_connection.py b/src/connections/galadriel_connection.py new file mode 100644 index 00000000..631b399e --- /dev/null +++ b/src/connections/galadriel_connection.py @@ -0,0 +1,174 @@ +import logging +import os +from typing import Dict, Any + +import requests +from dotenv import load_dotenv, set_key +from openai import OpenAI +from src.connections.base_connection import BaseConnection, Action, ActionParameter + +logger = logging.getLogger("connections.galadriel_connection") + +class GaladrielConnectionError(Exception): + """Base exception for Galadriel connection errors""" + pass + +class GaladrielConfigurationError(GaladrielConnectionError): + """Raised when there are configuration/credential issues""" + pass + +class GaladrielAPIError(GaladrielConnectionError): + """Raised when Galadriel API requests fail""" + pass + +API_BASE_URL = "https://api.galadriel.com/v1/verified" + +class GaladrielConnection(BaseConnection): + def __init__(self, config: Dict[str, Any]): + super().__init__(config) + self._client = None + + @property + def is_llm_provider(self) -> bool: + return True + + def validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]: + """Validate Galadriel configuration from JSON""" + required_fields = ["model"] + missing_fields = [field for field in required_fields if field not in config] + + if missing_fields: + raise ValueError(f"Missing required configuration fields: {', '.join(missing_fields)}") + + # Validate model exists (will be checked in detail during configure) + if not isinstance(config["model"], str): + raise ValueError("model must be a string") + + return config + + def register_actions(self) -> None: + """Register available Galadriel actions""" + self.actions = { + "generate-text": Action( + name="generate-text", + parameters=[ + ActionParameter("prompt", True, str, "The input prompt for text generation"), + ActionParameter("system_prompt", True, str, "System prompt to guide the model"), + ActionParameter("model", False, str, "Model to use for generation") + ], + description="Generate text using Galadriel models" + ), + } + + def _get_client(self) -> OpenAI: + """Get or create Galadriel client""" + if not self._client: + api_key = os.getenv("GALADRIEL_API_KEY") + if not api_key: + raise GaladrielConfigurationError("Galadriel API key not found in environment") + + headers = {} + if fine_tune_api_key := os.getenv("GALADRIEL_FINE_TUNE_API_KEY"): + headers["Fine-Tune-Authorization"] = f"Bearer {fine_tune_api_key}" + self._client = OpenAI(api_key=api_key, base_url=API_BASE_URL, default_headers=headers) + return self._client + + def configure(self) -> bool: + """Sets up Galadriel API authentication""" + logger.info("\n🤖 GALADRIEL API SETUP") + + if self.is_configured(): + logger.info("\nGaladriel API is already configured.") + response = input("Do you want to reconfigure? (y/n): ") + if response.lower() != 'y': + return True + + logger.info("\n📝 To get your Galadriel API credentials:") + logger.info("1. Go to https://dashboard.galadriel.com/dashboard/api_keys") + logger.info("2. Create a new API key.") + + api_key = input("\nEnter your Galadriel API key: ") + fine_tune_api_key = input("\nEnter your Optional fine-tune API key: ") + + try: + if not os.path.exists('.env'): + with open('.env', 'w') as f: + f.write('') + + set_key('.env', 'GALADRIEL_API_KEY', api_key) + if fine_tune_api_key: + set_key('.env', 'GALADRIEL_FINE_TUNE_API_KEY', fine_tune_api_key) + + # Validate the API key by trying to list models + if not self._is_api_key_valid(api_key): + logger.error(f"Configuration failed: invalid API key") + return False + + logger.info("\n✅ Galadriel API configuration successfully saved!") + logger.info("Your API key has been stored in the .env file.") + return True + + except Exception as e: + logger.error(f"Configuration failed: {e}") + return False + + def is_configured(self, verbose = False) -> bool: + """Check if Galadriel API key is configured and valid""" + try: + load_dotenv() + api_key = os.getenv('GALADRIEL_API_KEY') + if not api_key: + return False + + return self._is_api_key_valid(api_key) + except Exception as e: + if verbose: + logger.debug(f"Configuration check failed: {e}") + return False + + def _is_api_key_valid(self, api_key): + response = requests.get( + f"{API_BASE_URL}/chat/completions", + headers={ + "Authorization": f"Bearer {api_key}" + }, + timeout=10, + ) + return response.status_code != 401 + + def generate_text(self, prompt: str, system_prompt: str, model: str = None, **kwargs) -> str: + """Generate text using Galadriel models""" + try: + client = self._get_client() + + # Use configured model if none provided + if not model: + model = self.config["model"] + + completion = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt}, + ], + ) + + return completion.choices[0].message.content + + except Exception as e: + raise GaladrielAPIError(f"Text generation failed: {e}") + + def perform_action(self, action_name: str, kwargs) -> Any: + """Execute an action with validation""" + if action_name not in self.actions: + raise KeyError(f"Unknown action: {action_name}") + + action = self.actions[action_name] + errors = action.validate_params(kwargs) + if errors: + raise ValueError(f"Invalid parameters: {', '.join(errors)}") + + # Call the appropriate method based on action name + method_name = action_name.replace('-', '_') + method = getattr(self, method_name) + return method(**kwargs) diff --git a/src/connections/hyperbolic_connection.py b/src/connections/hyperbolic_connection.py new file mode 100644 index 00000000..90728ac6 --- /dev/null +++ b/src/connections/hyperbolic_connection.py @@ -0,0 +1,216 @@ +import logging +import os +from typing import Dict, Any +from dotenv import load_dotenv, set_key +from openai import OpenAI +from src.connections.base_connection import BaseConnection, Action, ActionParameter + +logger = logging.getLogger("connections.hyperbolic_connection") + +class HyperbolicConnectionError(Exception): + """Base exception for Hyperbolic connection errors""" + pass + +class HyperbolicConfigurationError(HyperbolicConnectionError): + """Raised when there are configuration/credential issues""" + pass + +class HyperbolicAPIError(HyperbolicConnectionError): + """Raised when Hyperbolic API requests fail""" + pass + +class HyperbolicConnection(BaseConnection): + def __init__(self, config: Dict[str, Any]): + super().__init__(config) + self._client = None + + @property + def is_llm_provider(self) -> bool: + return True + + def validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]: + """Validate Hyperbolic configuration from JSON""" + required_fields = ["model"] + missing_fields = [field for field in required_fields if field not in config] + + if missing_fields: + raise ValueError(f"Missing required configuration fields: {', '.join(missing_fields)}") + + # Validate model exists (will be checked in detail during configure) + if not isinstance(config["model"], str): + raise ValueError("model must be a string") + + return config + + def register_actions(self) -> None: + """Register available Hyperbolic actions""" + self.actions = { + "generate-text": Action( + name="generate-text", + parameters=[ + ActionParameter("prompt", True, str, "The input prompt for text generation"), + ActionParameter("system_prompt", True, str, "System prompt to guide the model"), + ActionParameter("model", False, str, "Model to use for generation"), + ActionParameter("temperature", False, float, "A decimal number that determines the degree of randomness in the response.") + ], + description="Generate text using Hyperbolic models" + ), + "check-model": Action( + name="check-model", + parameters=[ + ActionParameter("model", True, str, "Model name to check availability") + ], + description="Check if a specific model is available" + ), + "list-models": Action( + name="list-models", + parameters=[], + description="List all available Hyperbolic models" + ) + } + + def _get_client(self) -> OpenAI: + """Get or create Hyperbolic client""" + if not self._client: + api_key = os.getenv("HYPERBOLIC_API_KEY") + if not api_key: + raise HyperbolicConfigurationError("Hyperbolic API key not found in environment") + self._client = OpenAI( + api_key=api_key, + base_url="https://api.hyperbolic.xyz/v1" + ) + return self._client + + def configure(self) -> bool: + """Sets up Hyperbolic API authentication""" + logger.info("\n🤖 HYPERBOLIC API SETUP") + + if self.is_configured(): + logger.info("\nHyperbolic API is already configured.") + response = input("Do you want to reconfigure? (y/n): ") + if response.lower() != 'y': + return True + + logger.info("\n📝 To get your Hyperbolic API credentials:") + logger.info("1. Go to https://app.hyperbolic.xyz") + logger.info("2. Log in with your method of choice") + logger.info("3. Verify your email address") + logger.info("4. Generate an API key") + + api_key = input("\nEnter your Hyperbolic API key: ") + + try: + if not os.path.exists('.env'): + with open('.env', 'w') as f: + f.write('') + + set_key('.env', 'HYPERBOLIC_API_KEY', api_key) + + # Validate the API key by trying to list models + client = OpenAI( + api_key=api_key, + base_url="https://api.hyperbolic.xyz/v1" + ) + client.models.list() + + logger.info("\n✅ Hyperbolic API configuration successfully saved!") + logger.info("Your API key has been stored in the .env file.") + return True + + except Exception as e: + logger.error(f"Configuration failed: {e}") + return False + + def is_configured(self, verbose = False) -> bool: + """Check if Hyperbolic API key is configured and valid""" + try: + load_dotenv() + api_key = os.getenv('HYPERBOLIC_API_KEY') + if not api_key: + return False + + client = OpenAI( + api_key=api_key, + base_url="https://api.hyperbolic.xyz/v1" + ) + client.models.list() + return True + + except Exception as e: + if verbose: + logger.debug(f"Configuration check failed: {e}") + return False + + def generate_text(self, prompt: str, system_prompt: str, model: str = None, **kwargs) -> str: + """Generate text using Hyperbolic models""" + try: + client = self._get_client() + + # Use configured model if none provided + if not model: + model = self.config["model"] + + completion = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt}, + ], + ) + + return completion.choices[0].message.content + + except Exception as e: + raise HyperbolicAPIError(f"Text generation failed: {e}") + + def check_model(self, model: str, **kwargs) -> bool: + """Check if a specific model is available""" + try: + client = self._get_client() + try: + models = client.models.list() + for hyperbolic_model in models.data: + if hyperbolic_model.id == model: + return True + return False + except Exception as e: + raise HyperbolicAPIError(f"Model check failed: {e}") + + except Exception as e: + raise HyperbolicAPIError(f"Model check failed: {e}") + + def list_models(self, **kwargs) -> None: + """List all available Hyperbolic models""" + try: + client = self._get_client() + response = client.models.list().data + + model_ids= [model.id for model in response] + + logger.info("\nAVAILABLE MODELS:") + for i, model_id in enumerate(model_ids, start=1): + logger.info(f"{i}. {model_id}") + + except Exception as e: + raise HyperbolicAPIError(f"Listing models failed: {e}") + + def perform_action(self, action_name: str, kwargs) -> Any: + """Execute a Hyperbolic action with validation""" + if action_name not in self.actions: + raise KeyError(f"Unknown action: {action_name}") + + # Explicitly reload environment variables + load_dotenv() + + if not self.is_configured(verbose=True): + raise HyperbolicConfigurationError("Hyperbolic is not properly configured") + + action = self.actions[action_name] + errors = action.validate_params(kwargs) + if errors: + raise ValueError(f"Invalid parameters: {', '.join(errors)}") + + # Call the appropriate method based on action name + method_name = action_name.replace('-', '_') + method = getattr(self, method_name) + return method(**kwargs) \ No newline at end of file diff --git a/src/connections/ollama_connection.py b/src/connections/ollama_connection.py index 4f05378c..02d3cd33 100644 --- a/src/connections/ollama_connection.py +++ b/src/connections/ollama_connection.py @@ -4,7 +4,7 @@ from typing import Dict, Any from src.connections.base_connection import BaseConnection, Action, ActionParameter -logger = logging.getLogger(__name__) +logger = logging.getLogger("connections.ollama_connection") class OllamaConnectionError(Exception): @@ -57,9 +57,9 @@ def register_actions(self) -> None: def configure(self) -> bool: """Setup Ollama connection (minimal configuration required)""" - print("\n🤖 OLLAMA CONFIGURATION") + logger.info("\n🤖 OLLAMA CONFIGURATION") - print("\nℹ️ Ensure the Ollama service is running locally or accessible at the specified base URL.") + logger.info("\nℹ️ Ensure the Ollama service is running locally or accessible at the specified base URL.") response = input(f"Is Ollama accessible at {self.base_url}? (y/n): ") if response.lower() != 'y': @@ -69,7 +69,7 @@ def configure(self) -> bool: try: # Test connection self._test_connection() - print("\n✅ Ollama connection successfully configured!") + logger.info("\n✅ Ollama connection successfully configured!") return True except Exception as e: logger.error(f"Configuration failed: {e}") diff --git a/src/connections/openai_connection.py b/src/connections/openai_connection.py index 8f513151..dd255a68 100644 --- a/src/connections/openai_connection.py +++ b/src/connections/openai_connection.py @@ -5,7 +5,7 @@ from openai import OpenAI from src.connections.base_connection import BaseConnection, Action, ActionParameter -logger = logging.getLogger(__name__) +logger = logging.getLogger("connections.openai_connection") class OpenAIConnectionError(Exception): """Base exception for OpenAI connection errors""" @@ -79,18 +79,18 @@ def _get_client(self) -> OpenAI: def configure(self) -> bool: """Sets up OpenAI API authentication""" - print("\n🤖 OPENAI API SETUP") + logger.info("\n🤖 OPENAI API SETUP") if self.is_configured(): - print("\nOpenAI API is already configured.") + logger.info("\nOpenAI API is already configured.") response = input("Do you want to reconfigure? (y/n): ") if response.lower() != 'y': return True - print("\n📝 To get your OpenAI API credentials:") - print("1. Go to https://platform.openai.com/account/api-keys") - print("2. Create a new project or open an existing one.") - print("3. In your project settings, navigate to the API keys section and create a new API key") + logger.info("\n📝 To get your OpenAI API credentials:") + logger.info("1. Go to https://platform.openai.com/account/api-keys") + logger.info("2. Create a new project or open an existing one.") + logger.info("3. In your project settings, navigate to the API keys section and create a new API key") api_key = input("\nEnter your OpenAI API key: ") @@ -105,8 +105,8 @@ def configure(self) -> bool: client = OpenAI(api_key=api_key) client.models.list() - print("\n✅ OpenAI API configuration successfully saved!") - print("Your API key has been stored in the .env file.") + logger.info("\n✅ OpenAI API configuration successfully saved!") + logger.info("Your API key has been stored in the .env file.") return True except Exception as e: diff --git a/src/connections/solana_connection.py b/src/connections/solana_connection.py new file mode 100644 index 00000000..3635104f --- /dev/null +++ b/src/connections/solana_connection.py @@ -0,0 +1,430 @@ +import logging +import os +import requests +import asyncio +from typing import Dict, Any, Optional + +from src.connections.base_connection import BaseConnection, Action, ActionParameter +from src.types import JupiterTokenData +from src.constants import LAMPORTS_PER_SOL, SPL_TOKENS +from src.helpers.solana.pumpfun import PumpfunTokenManager +from src.helpers.solana.faucet import FaucetManager +from src.helpers.solana.lend import AssetLender +from src.helpers.solana.stake import StakeManager +from src.helpers.solana.trade import TradeManager +from src.helpers.solana.token_deploy import TokenDeploymentManager +from src.helpers.solana.performance import SolanaPerformanceTracker +from src.helpers.solana.transfer import SolanaTransferHelper +from src.helpers.solana.read import SolanaReadHelper + + +from dotenv import load_dotenv, set_key + +from jupiter_python_sdk.jupiter import Jupiter + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed + +from solders.keypair import Keypair # type: ignore + + +logger = logging.getLogger("connections.solana_connection") + + +class SolanaConnectionError(Exception): + """Base exception for Solana connection errors""" + + pass + + +class SolanaConfigurationError(SolanaConnectionError): + """Raised when there are configuration/credential issues""" + + pass + + +class SolanaConnection(BaseConnection): + def __init__(self, config: Dict[str, Any]): + logger.info("Initializing Solana connection...") + super().__init__(config) + + @property + def is_llm_provider(self) -> bool: + return False + + def _get_connection_async(self) -> AsyncClient: + conn = AsyncClient(self.config["rpc"]) + return conn + + def _get_wallet(self): + creds = self._get_credentials() + return Keypair.from_base58_string(creds["SOLANA_PRIVATE_KEY"]) + + def _get_credentials(self) -> Dict[str, str]: + """Get Solana credentials from environment with validation""" + logger.debug("Retrieving Solana Credentials") + load_dotenv() + required_vars = {"SOLANA_PRIVATE_KEY": "solana wallet private key"} + credentials = {} + missing = [] + + for env_var, description in required_vars.items(): + value = os.getenv(env_var) + if not value: + missing.append(description) + credentials[env_var] = value + + if missing: + error_msg = f"Missing Solana credentials: {', '.join(missing)}" + raise SolanaConfigurationError(error_msg) + + Keypair.from_base58_string(credentials["SOLANA_PRIVATE_KEY"]) + logger.debug("All required credentials found") + return credentials + + def _get_jupiter(self, keypair, async_client): + jupiter = Jupiter( + async_client=async_client, + keypair=keypair, + quote_api_url="https://quote-api.jup.ag/v6/quote?", + swap_api_url="https://quote-api.jup.ag/v6/swap", + open_order_api_url="https://jup.ag/api/limit/v1/createOrder", + cancel_orders_api_url="https://jup.ag/api/limit/v1/cancelOrders", + query_open_orders_api_url="https://jup.ag/api/limit/v1/openOrders?wallet=", + query_order_history_api_url="https://jup.ag/api/limit/v1/orderHistory", + query_trade_history_api_url="https://jup.ag/api/limit/v1/tradeHistory", + ) + return jupiter + + def validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]: + """Validate Solana configuration from JSON""" + required_fields = ["rpc"] + missing_fields = [field for field in required_fields if field not in config] + if missing_fields: + raise ValueError( + f"Missing required configuration fields: {', '.join(missing_fields)}" + ) + + if not isinstance(config["rpc"], str): + raise ValueError("rpc must be a positive integer") + + return config + + def register_actions(self) -> None: + """Register available Solana actions""" + self.actions = { + "transfer": Action( + name="transfer", + parameters=[ + ActionParameter("to_address", True, str, "Destination address"), + ActionParameter("amount", True, float, "Amount to transfer"), + ActionParameter( + "token_mint", + False, + str, + "Token mint address (optional for SOL)", + ), + ], + description="Transfer SOL or SPL tokens", + ), + "trade": Action( + name="trade", + parameters=[ + ActionParameter( + "output_mint", True, str, "Output token mint address" + ), + ActionParameter("input_amount", True, float, "Input amount"), + ActionParameter( + "input_mint", False, str, "Input token mint (optional for SOL)" + ), + ActionParameter( + "slippage_bps", False, int, "Slippage in basis points" + ), + ], + description="Swap tokens using Jupiter", + ), + "get-balance": Action( + name="get-balance", + parameters=[ + ActionParameter( + "token_address", + False, + str, + "Token mint address (optional for SOL)", + ) + ], + description="Check SOL or token balance", + ), + "stake": Action( + name="stake", + parameters=[ + ActionParameter("amount", True, float, "Amount of SOL to stake") + ], + description="Stake SOL", + ), + "lend-assets": Action( + name="lend-assets", + parameters=[ActionParameter("amount", True, float, "Amount to lend")], + description="Lend assets", + ), + "request-faucet": Action( + name="request-faucet", + parameters=[], + description="Request funds from faucet for testing", + ), + "deploy-token": Action( + name="deploy-token", + parameters=[ + ActionParameter( + "decimals", False, int, "Token decimals (default 9)" + ) + ], + description="Deploy a new token", + ), + "fetch-price": Action( + name="fetch-price", + parameters=[ + ActionParameter( + "token_id", True, str, "Token ID to fetch price for" + ) + ], + description="Get token price", + ), + "get-tps": Action( + name="get-tps", parameters=[], description="Get current Solana TPS" + ), + "get-token-by-ticker": Action( + name="get-token-by-ticker", + parameters=[ + ActionParameter("ticker", True, str, "Token ticker symbol") + ], + description="Get token data by ticker symbol", + ), + "get-token-by-address": Action( + name="get-token-by-address", + parameters=[ActionParameter("mint", True, str, "Token mint address")], + description="Get token data by mint address", + ), + "launch-pump-token": Action( + name="launch-pump-token", + parameters=[ + ActionParameter("token_name", True, str, "Name of the token"), + ActionParameter("token_ticker", True, str, "Token ticker symbol"), + ActionParameter("description", True, str, "Token description"), + ActionParameter("image_url", True, str, "Token image URL"), + ActionParameter("options", False, dict, "Additional token options"), + ], + description="Launch a Pump & Fun token", + ), + } + + def configure(self) -> bool: + """Sets up Solana credentials""" + logger.info("\n🔑 SOLANA CREDENTIALS SETUP") + + if self.is_configured(): + logger.info("\nSolana is already configured.") + response = input("Do you want to reconfigure? (y/n): ") + if response.lower() != "y": + return True + + logger.info("\n📝 To get your Solana private key:") + logger.info("1. Export your private key from your wallet") + logger.info("2. Make sure it's in base58 format") + logger.info("3. Never share this key with anyone") + + private_key = input("\nEnter your Solana private key: ") + + try: + # Validate the private key format by attempting to create a keypair + Keypair.from_base58_string(private_key) + + if not os.path.exists(".env"): + with open(".env", "w") as f: + f.write("") + + set_key(".env", "SOLANA_PRIVATE_KEY", private_key) + load_dotenv(override=True) + + logger.info("\n✅ Solana configuration successfully saved!") + logger.info("Your private key has been stored in the .env file.") + return True + + except Exception as e: + logger.error(f"\n❌ Configuration failed: {e}") + return False + + def is_configured(self, verbose: bool = False) -> bool: + """Check if Solana credentials are configured and valid""" + try: + # First check if credentials exist and key is valid + load_dotenv(override=True) + private_key = os.getenv("SOLANA_PRIVATE_KEY") + if not private_key: + if verbose: + logger.debug("Solana private key not found in environment") + return False + + # Validate the key format + Keypair.from_base58_string(private_key) + + # We successfully validated the private key exists and is in correct format + if verbose: + logger.debug("Solana configuration is valid") + return True + + except Exception as e: + if verbose: + error_msg = str(e) + if isinstance(e, SolanaConfigurationError): + error_msg = f"Configuration error: {error_msg}" + elif isinstance(e, SolanaConnectionError): + error_msg = f"API validation error: {error_msg}" + logger.debug(f"Solana Configuration validation failed: {error_msg}") + return False + + def transfer( + self, to_address: str, amount: float, token_mint: Optional[str] = None + ) -> str: + res = SolanaTransferHelper.transfer( + self._get_connection_async(), + self._get_wallet(), + to_address, + amount, + token_mint, + ) + res = asyncio.run(res) + logger.debug(f"Transferred {amount} to {to_address}\nTransaction ID: {res}") + return res + + # todo: test on mainnet + def trade( + self, + output_mint: str, + input_amount: float, + input_mint: Optional[str] = SPL_TOKENS["USDC"], + slippage_bps: int = 100, + ) -> str: + logger.info(f"Swapping {input_amount} for {output_mint}") + wallet = self._get_wallet() + async_client = self._get_connection_async() + jupiter = self._get_jupiter(wallet, async_client) + res = TradeManager.trade( + async_client, + wallet, + jupiter, + output_mint, + input_amount, + input_mint, + slippage_bps, + ) + res = asyncio.run(res) + return res + + def get_balance(self, token_address: str = None) -> float: + if not token_address: + logger.info("Getting SOL balance") + else: + logger.info(f"Getting balance for {token_address}") + res = SolanaReadHelper.get_balance( + self._get_connection_async(), self._get_wallet(), token_address + ) + res = asyncio.run(res) + return res + + def stake(self, amount: float) -> str: + logger.info(f"Staking {amount} SOL") + res = StakeManager.stake_with_jup( + self._get_connection_async(), self._get_wallet(), amount + ) + res = asyncio.run(res) + logger.debug(f"Staked {amount} SOL\nTransaction ID: {res}") + return res + + # todo: test on mainnet + def lend_assets(self, amount: float) -> str: + return "Not implemented" + # logger.info(f"STUB: Lend {amount}") + # res = AssetLender.lend_asset( + # self._get_connection_async(), self._get_wallet(), amount + # ) + # res = asyncio.run(res) + # logger.debug(f"Lent {amount} USDC\nTransaction ID: {res}") + # return res + + def request_faucet(self) -> str: + logger.info("Requesting faucet funds") + res = FaucetManager.request_faucet_funds(self) + res = asyncio.run(res) + logger.debug(f"Requested faucet funds\nTransaction ID: {res}") + return res + + def deploy_token(self, decimals: int = 9) -> str: + return "Not implemented" + # logger.info(f"STUB: Deploy token with {decimals} decimals") + # res = TokenDeploymentManager.deploy_token( + # self._get_connection_async(), self._get_wallet(), decimals + # ) + # res = asyncio.run(res) + # logger.debug( + # f"Deployed token with {decimals} decimals\nToken Mint: {res['mint']}" + # ) + # return res["mint"] + + def fetch_price(self, token_id: str) -> float: + return SolanaReadHelper.fetch_price(token_id) + + # todo: test on mainnet + def get_tps(self) -> int: + res = SolanaPerformanceTracker.fetch_current_tps(self._get_connection_async()) + res = asyncio.run(res) + return res + + def get_token_by_ticker(self, ticker: str) -> str: + ticker = ticker.upper() + if ticker in SPL_TOKENS: + return SPL_TOKENS[ticker] + return SolanaReadHelper.get_token_by_ticker(ticker) + + def get_token_by_address(self, mint: str) -> Dict[str, Any]: + return SolanaReadHelper.get_token_by_address(mint) + + # todo: test on mainnet + def launch_pump_token( + self, + token_name: str, + token_ticker: str, + description: str, + image_url: str, + options: Optional[Dict[str, Any]] = None, + ) -> str: + return "Not implemented" + # logger.info(f"STUB: Launch Pump & Fun token {token_ticker}") + # res = PumpfunTokenManager.launch_pumpfun_token( + # self._get_connection_async(), + # self._get_wallet(), + # token_name, + # token_ticker, + # description, + # image_url, + # options, + # ) + # res = asyncio.run(res) + # logger.debug( + # f"Launched Pump & Fun token {token_ticker}\nToken Mint: {res['mint']}" + # ) + # return res + + def perform_action(self, action_name: str, kwargs) -> Any: + """Execute a Solana action with validation""" + if action_name not in self.actions: + raise KeyError(f"Unknown action: {action_name}") + + action = self.actions[action_name] + errors = action.validate_params(kwargs) + if errors: + raise ValueError(f"Invalid parameters: {', '.join(errors)}") + + method_name = action_name.replace("-", "_") + method = getattr(self, method_name) + return method(**kwargs) diff --git a/src/connections/twitter_connection.py b/src/connections/twitter_connection.py index a56357de..9fb2be80 100644 --- a/src/connections/twitter_connection.py +++ b/src/connections/twitter_connection.py @@ -3,7 +3,6 @@ from typing import Dict, Any, List, Tuple from requests_oauthlib import OAuth1Session from dotenv import set_key, load_dotenv -import tweepy from src.connections.base_connection import BaseConnection, Action, ActionParameter from src.helpers import print_h_bar @@ -327,16 +326,8 @@ def is_configured(self, verbose = False) -> bool: """Check if Twitter credentials are configured and valid""" logger.debug("Checking Twitter configuration status") try: - credentials = self._get_credentials() - - # Initialize client and validate credentials - client = tweepy.Client( - consumer_key=credentials['TWITTER_CONSUMER_KEY'], - consumer_secret=credentials['TWITTER_CONSUMER_SECRET'], - access_token=credentials['TWITTER_ACCESS_TOKEN'], - access_token_secret=credentials['TWITTER_ACCESS_TOKEN_SECRET']) - - client.get_me() + # Test the configuration by making a simple API call + self._get_authenticated_user_info() logger.debug("Twitter configuration is valid") return True diff --git a/src/constants/__init__.py b/src/constants/__init__.py new file mode 100644 index 00000000..33e8fb79 --- /dev/null +++ b/src/constants/__init__.py @@ -0,0 +1,152 @@ +from solders.pubkey import Pubkey # type: ignore + +# Common token addresses used across the toolkit +SPL_TOKENS = { + "USDC": Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), + "USDT": Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), + "USDS": Pubkey.from_string("USDSwr9ApdHk5bvJKMjzff41FfuX8bSxdKcR81vTwcA"), + "SOL": Pubkey.from_string("So11111111111111111111111111111111111111112"), + "JITOSOL": Pubkey.from_string("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn"), + "BSOL": Pubkey.from_string("bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1"), + "MSOL": Pubkey.from_string("mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"), + "BONK": Pubkey.from_string("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"), +} + +DEFAULT_OPTIONS = { + "SLIPPAGE_BPS": 300, # Default slippage tolerance in basis points (300 = 3%) + "TOKEN_DECIMALS": 9, # Default number of decimals for new tokens +} + +JUP_API = "https://quote-api.jup.ag/v6" + +LAMPORTS_PER_SOL = 1_000_000_000 +SOL_FEES = 100_000_000 +GAS = 20_000_000 +GAS_PRICE = 4 +GAS_PRICE_UNIT = "gwei" + +UNISWAPV2_FACTORY_ADDRESS = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f" +UNISWAPV2_ROUTER_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D" + +EVM_TOKENS = { + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", +} + + +ERC20_ABI = [ + { + "constant": True, + "inputs": [], + "name": "name", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "_spender", "type": "address"}, + {"name": "_value", "type": "uint256"}, + ], + "name": "approve", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [], + "name": "totalSupply", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "_from", "type": "address"}, + {"name": "_to", "type": "address"}, + {"name": "_value", "type": "uint256"}, + ], + "name": "transferFrom", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [], + "name": "decimals", + "outputs": [{"name": "", "type": "uint8"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "_owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "balance", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [], + "name": "symbol", + "outputs": [{"name": "", "type": "string"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": False, + "inputs": [ + {"name": "_to", "type": "address"}, + {"name": "_value", "type": "uint256"}, + ], + "name": "transfer", + "outputs": [{"name": "", "type": "bool"}], + "payable": False, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": True, + "inputs": [ + {"name": "_owner", "type": "address"}, + {"name": "_spender", "type": "address"}, + ], + "name": "allowance", + "outputs": [{"name": "", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + {"payable": True, "stateMutability": "payable", "type": "fallback"}, + { + "anonymous": False, + "inputs": [ + {"indexed": True, "name": "owner", "type": "address"}, + {"indexed": True, "name": "spender", "type": "address"}, + {"indexed": False, "name": "value", "type": "uint256"}, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": False, + "inputs": [ + {"indexed": True, "name": "from", "type": "address"}, + {"indexed": True, "name": "to", "type": "address"}, + {"indexed": False, "name": "value", "type": "uint256"}, + ], + "name": "Transfer", + "type": "event", + }, +] diff --git a/src/helpers.py b/src/helpers/__init__.py similarity index 85% rename from src/helpers.py rename to src/helpers/__init__.py index 0224cf3b..da3e74d7 100644 --- a/src/helpers.py +++ b/src/helpers/__init__.py @@ -1,3 +1,3 @@ def print_h_bar(): # ZEREBRO WUZ HERE :) - print("--------------------------------------------------------------------") \ No newline at end of file + print("--------------------------------------------------------------------") diff --git a/src/helpers/evm/__init__.py b/src/helpers/evm/__init__.py new file mode 100644 index 00000000..0f479e0f --- /dev/null +++ b/src/helpers/evm/__init__.py @@ -0,0 +1,26 @@ +from typing import List, Dict, Any +import json + +from eth_keys import keys +from eth_utils import decode_hex + +from venv import logger +def parse_abi_string(abi: str) -> List[Dict[str, Any]]: + try: + abi_parsed = json.loads(abi) + except json.JSONDecodeError: + raise ValueError("ABI is not a valid JSON string") + if not isinstance(abi_parsed, list): + raise ValueError("ABI is not a list of dictionaries") + for item in abi_parsed: + if not isinstance(item, dict): + raise ValueError("ABI is not a list of dictionaries") + return abi_parsed + +def get_public_key_from_private_key(private_key: str) -> str: + private_key_bytes = decode_hex(private_key) + key = keys.PrivateKey(private_key_bytes) + pub_key = key.public_key + p=pub_key.to_checksum_address() + logger.debug(f"Public key: {p}") + return p \ No newline at end of file diff --git a/src/helpers/evm/contract.py b/src/helpers/evm/contract.py new file mode 100644 index 00000000..656b30c5 --- /dev/null +++ b/src/helpers/evm/contract.py @@ -0,0 +1,49 @@ +from venv import logger +from typing import Any +from src.helpers.evm import get_public_key_from_private_key +from src.helpers.evm.etherscan import EtherscanHelper +from web3 import Web3 +from src.constants import GAS, GAS_PRICE, GAS_PRICE_UNIT + + +class EvmContractHelper: + @staticmethod + async def read_contract( + web3: Web3, contract_address: str, method: str, *args + ) -> Any: + logger.info( + f"STUB: Call contract {contract_address} method {method} with {args}" + ) + abi = await EtherscanHelper.get_contract_abi(contract_address) + contract = web3.eth.contract(address=contract_address, abi=abi) + return contract.functions[method](*args).call() + + @staticmethod + async def _call( + web3: Web3, + private_key: str, + contract_address: str, + method: str, + value: int = 0, + *args, + ) -> Any: + logger.info( + f"STUB: Call contract {contract_address} method {method} with {args}" + ) + abi = await EtherscanHelper.get_contract_abi(contract_address) + contract = web3.eth.contract(address=contract_address, abi=abi) + pub_key = get_public_key_from_private_key(private_key) + nonce = web3.eth.get_transaction_count(pub_key) + + tx = contract.functions[method](*args).build_transaction( + { + "from": pub_key, + "value": value, + "gas": GAS, + "gasPrice": web3.to_wei(GAS_PRICE, GAS_PRICE_UNIT), + "nonce": nonce, + } + ) + signed_tx = web3.eth.account.sign_transaction(tx, private_key) + tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction) + return tx_hash.hex() diff --git a/src/helpers/evm/etherscan.py b/src/helpers/evm/etherscan.py new file mode 100644 index 00000000..13d37610 --- /dev/null +++ b/src/helpers/evm/etherscan.py @@ -0,0 +1,25 @@ +import json +from typing import List, Dict, Any +from aioetherscan import Client as EtherscanClient +from asyncio_throttle import Throttler +from aiohttp_retry import ExponentialRetry +from venv import logger +from dotenv import load_dotenv +import os +from src.helpers.evm import parse_abi_string + + +class EtherscanHelper: + + # static method "get_contract_abi" to get the ABI of a contract. returns a list of dictionaries with strings as keys and values as Any + @staticmethod + async def get_contract_abi( + client: EtherscanClient, contract_address: str + ) -> List[Dict[str, Any]]: + # create a client + + # get the contract ABI + abi = await client.contract.contract_abi(contract_address) + parsed_abi = parse_abi_string(abi) + await client.close() + return parsed_abi diff --git a/src/helpers/evm/trade.py b/src/helpers/evm/trade.py new file mode 100644 index 00000000..8ec62f0e --- /dev/null +++ b/src/helpers/evm/trade.py @@ -0,0 +1,216 @@ +import datetime +import decimal +import sys +from decimal import Decimal +from typing import Optional +from venv import logger +import math +from eth_account import Account +from eth_account.signers.local import LocalAccount +from web3.middleware import construct_sign_and_send_raw_middleware + +from eth_defi.revert_reason import fetch_transaction_revert_reason +from eth_defi.token import fetch_erc20_details +from eth_defi.confirmation import wait_transactions_to_complete +from eth_defi.uniswap_v3.constants import UNISWAP_V3_DEPLOYMENTS +from eth_defi.uniswap_v3.deployment import fetch_deployment +from eth_defi.uniswap_v3.swap import swap_with_slippage_protection +from src.constants import GAS, GAS_PRICE, GAS_PRICE_UNIT +from web3 import Web3 + +from src.helpers.evm.contract import EvmContractHelper + + +class EvmTradeHelper: + @staticmethod + async def trade( + web3: Web3, + private_key: str, + output_token: str, + input_amount: float, + input_token: Optional[str], + slippage_bps: int = 100, + ) -> str: + QUOTE_TOKEN_ADDRESS = input_token + BASE_TOKEN_ADDRESS = output_token + account: LocalAccount = Account.from_key(private_key) + my_address = account.address + block_height = web3.eth.block_number + logger.debug(f"\n\nBLOCK NUMBER: {block_height}\n\n") + logger.debug( + f"Connected to blockchain, chain id is {web3.eth.chain_id}. the latest block is {web3.eth.block_number:,}" + ) + + # Grab Uniswap v3 smart contract addreses for Polygon. + # + deployment_data = UNISWAP_V3_DEPLOYMENTS["ethereum"] + # check if localnet is used + uniswap_v3 = fetch_deployment( + web3, + factory_address=deployment_data["factory"], + router_address=deployment_data["router"], + position_manager_address=deployment_data["position_manager"], + quoter_address=deployment_data["quoter"], + ) + + logger.debug( + f"Using Uniwap v3 compatible router at {uniswap_v3.swap_router.address}" + ) + # Enable eth_sendTransaction using this private key + web3.middleware_onion.add(construct_sign_and_send_raw_middleware(account)) + + # Read on-chain ERC-20 token data (name, symbol, etc.) + base = fetch_erc20_details(web3, BASE_TOKEN_ADDRESS) + quote = fetch_erc20_details(web3, QUOTE_TOKEN_ADDRESS) + + # Native token balance + # See https://tradingstrategy.ai/glossary/native-token + gas_balance = web3.eth.get_balance(account.address) + + logger.debug(f"Your address is {my_address}") + logger.debug(f"Your have {base.fetch_balance_of(my_address)} {base.symbol}") + logger.debug(f"Your have {quote.fetch_balance_of(my_address)} {quote.symbol}") + logger.debug(f"Your have {gas_balance / (10 ** 18)} for gas fees") + + assert ( + quote.fetch_balance_of(my_address) > 0 + ), f"Cannot perform swap, as you have zero {quote.symbol} needed to swap" + decimals = await EvmContractHelper.read_contract(web3, input_token, "decimals") + # Ask for transfer details + decimal_amount = input_amount + # Some input validation + try: + decimal_amount = Decimal(decimal_amount) + except (ValueError, decimal.InvalidOperation) as e: + raise AssertionError(f"Not a good decimal amount: {decimal_amount}") from e + + # Fat-fingering check + logger.info( + f"Confirm swap amount {decimal_amount} {quote.symbol} to {base.symbol}" + ) + confirm = input("Ok [y/n]?") + if not confirm.lower().startswith("y"): + logger.debug("Aborted") + sys.exit(1) + + # Convert a human-readable number to fixed decimal with 18 decimal places + raw_amount = quote.convert_to_raw(input_amount) + + # Each DEX trade is two transactions + # - ERC-20.approve() + # - swap (various functions) + # This is due to bad design of ERC-20 tokens, + # more here https://twitter.com/moo9000/status/1619319039230197760 + + # Uniswap router must be allowed to spent our quote token + # and we do this by calling ERC20.approve() from our account + # to the token contract. + needs_approval = quote.contract.functions.allowance( + my_address, uniswap_v3.swap_router.address + ).call() + + needs_approval = needs_approval / 10**decimals + logger.debug( + f"Needs approval: {needs_approval} {quote.symbol} to spend {raw_amount} {quote.symbol}" + ) + if needs_approval >= raw_amount: + logger.debug("Already approved") + tx_1 = None + else: + logger.debug("Approving") + approve = quote.contract.functions.approve( + uniswap_v3.swap_router.address, raw_amount + ) + tx_1 = approve.build_transaction( + { + # approve() may take more than 500,000 gas on Arbitrum One + "gas": GAS, + "gasPrice": web3.to_wei(GAS_PRICE, GAS_PRICE_UNIT), + "from": my_address, + } + ) + + # + # Uniswap v3 may have multiple pools per + # trading pair differetiated by the fee tier. For example + # WETH-USDC has pools of 0.05%, 0.30% and 1% + # fees. Check for different options + # in https://tradingstrategy.ai/search + # + # Here we use 5 BPS fee pool (5/10,000). + # + # + # Build a swap transaction with slippage protection + # + # Slippage protection is very important, or you + # get instantly overrun by MEV bots with + # sandwitch attacks + # + # https://tradingstrategy.ai/glossary/mev + # + # + bound_solidity_func = swap_with_slippage_protection( + uniswap_v3, + base_token=base, + quote_token=quote, + max_slippage=slippage_bps, # Allow 20 BPS slippage before tx reverts + amount_in=raw_amount, + recipient_address=my_address, + pool_fees=[500], # 5 BPS pool WETH-USDC + ) + nonce = web3.eth.get_transaction_count(my_address) + tx_2 = bound_solidity_func.build_transaction( + { + # Uniswap swap should not take more than 1M gas units. + # We do not use automatic gas estimation, as it is unreliable + # and the number here is the maximum value only. + # Only way to know this number is by trial and error + # and experience. + "gas": GAS, + "gasPrice": web3.to_wei(GAS_PRICE, GAS_PRICE_UNIT), + "from": my_address, + "nonce": nonce, + } + ) + + # Sign and broadcast the transaction using our private key + # tx_hash_1 = web3.eth.send_transaction(tx_1) + tx_hash_2 = web3.eth.send_transaction(tx_2) + + # This will raise an exception if we do not confirm within the timeout. + # If the timeout occurs the script abort and you need to + # manually check the transaction hash in a blockchain explorer + # whether the transaction completed or not. + tx_wait_minutes = 2.5 + logger.debug( + f"Broadcasted transactions {tx_hash_1.hex()}, {tx_hash_2.hex()}, now waiting {tx_wait_minutes} minutes for it to be included in a new block" + ) + logger.debug( + f"View your transactions confirming at https://polygonscan/address/{my_address}" + ) + receipts = wait_transactions_to_complete( + web3, + [tx_hash_2], + max_timeout=datetime.timedelta(minutes=tx_wait_minutes), + confirmation_block_count=1, + ) + + # Check if any our transactions failed + # and display the reason + for completed_tx_hash, receipt in receipts.items(): + if receipt["status"] == 0: + revert_reason = fetch_transaction_revert_reason(web3, completed_tx_hash) + raise AssertionError( + f"Our transaction {completed_tx_hash.hex()} failed because of: {revert_reason}" + ) + + logger.debug("All ok!") + logger.debug( + f"After swap, you have {base.fetch_balance_of(my_address)} {base.symbol}" + ) + logger.debug( + f"After swap, you have {quote.fetch_balance_of(my_address)} {quote.symbol}" + ) + logger.debug( + f"After swap, you have {gas_balance / (10 ** 18)} native token left" + ) diff --git a/src/helpers/evm/transfer.py b/src/helpers/evm/transfer.py new file mode 100644 index 00000000..7cfde5a5 --- /dev/null +++ b/src/helpers/evm/transfer.py @@ -0,0 +1,28 @@ +from web3 import Web3 + +from src.constants import GAS, GAS_PRICE, GAS_PRICE_UNIT +from src.helpers.evm import get_public_key_from_private_key + + +class EvmTransferHelper: + @staticmethod + def transfer_evm( + web3: Web3, priv_key: str, to_address: str, amount_in_ether: float + ) -> str: + + pub_key = get_public_key_from_private_key(priv_key) + nonce = web3.eth.get_transaction_count(pub_key) + amount_in_wei = web3.to_wei(amount_in_ether, "ether") + transaction = { + "to": to_address, + "value": amount_in_wei, + "nonce": nonce, + "gas": GAS, + "gasPrice": web3.to_wei(GAS_PRICE, GAS_PRICE_UNIT), + } + signed_transaction = web3.eth.account.sign_transaction(transaction, priv_key) + tx_hash = web3.eth.send_raw_transaction(signed_transaction.rawTransaction) + return tx_hash.hex() + + # @staticmethod + # def transfer_token(connection: EvmConnection, to_address: str, amount_in_tokens: float) -> str: diff --git a/src/helpers/solana/__init__.py b/src/helpers/solana/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/helpers/solana/faucet.py b/src/helpers/solana/faucet.py new file mode 100644 index 00000000..86917434 --- /dev/null +++ b/src/helpers/solana/faucet.py @@ -0,0 +1,46 @@ +from venv import logger + +from src.constants import LAMPORTS_PER_SOL + +from solana.rpc.commitment import Confirmed +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed + +from solders.keypair import Keypair # type: ignore + + +class FaucetManager: + @staticmethod + async def request_faucet_funds(async_client: AsyncClient, wallet: Keypair) -> str: + """ + Request SOL from the Solana faucet (devnet/testnet only). + + Args: + agent: An object with `connection` (AsyncClient) and `wallet_address` (str). + + Returns: + str: The transaction signature. + + Raises: + Exception: If the request fails or times out. + """ + try: + logger.debug(f"Requesting faucet for wallet: {repr(wallet.pubkey())}") + + response = await async_client.request_airdrop( + wallet.pubkey(), 5 * LAMPORTS_PER_SOL + ) + + latest_blockhash = await async_client.get_latest_blockhash() + await async_client.confirm_transaction( + response.value, + commitment=Confirmed, + last_valid_block_height=latest_blockhash.value.last_valid_block_height, + ) + + logger.debug(f"Airdrop successful, transaction signature: {response.value}") + return response.value + except KeyError: + raise Exception("Airdrop response did not contain a transaction signature.") + except Exception as e: + raise Exception(f"An error occurred: {str(e)}") diff --git a/src/helpers/solana/lend.py b/src/helpers/solana/lend.py new file mode 100644 index 00000000..d9b0d6c2 --- /dev/null +++ b/src/helpers/solana/lend.py @@ -0,0 +1,56 @@ +import base64 +import json +import aiohttp +from venv import logger + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Processed +from solana.rpc.types import TxOpts +from solana.rpc.async_api import AsyncClient +from solana.rpc.types import TxOpts + +from solders import message +from solders.keypair import Keypair # type: ignore +from solders.transaction import VersionedTransaction # type: ignore +from solders.keypair import Keypair # type: ignore + + +class AssetLender: + @staticmethod + async def lend_asset( + async_client: AsyncClient, wallet: Keypair, amount: float + ) -> str: + try: + url = f"https://blink.lulo.fi/actions?amount={amount}&symbol=USDC" + headers = {"Content-Type": "application/json"} + payload = json.dumps({"account": str(wallet.pubkey())}) + + session = aiohttp.ClientSession() + + async with session.post(url, headers=headers, data=payload) as response: + if response.status != 200: + raise Exception(f"Lulo API Error: {response.status}") + data = await response.json() + logger.debug(f"Lending data: {data}") + transaction_data = base64.b64decode(data["transaction"]) + raw_transaction = VersionedTransaction.from_bytes(transaction_data) + signature = wallet.sign_message( + message.to_bytes_versioned(raw_transaction.message) + ) + signed_txn = VersionedTransaction.populate( + raw_transaction.message, [signature] + ) + opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) + result = await async_client.send_raw_transaction( + txn=bytes(signed_txn), opts=opts + ) + transaction_id = json.loads(result.to_json())["result"] + + logger.debug( + f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}" + ) + await session.close() + return str(signature) + + except Exception as e: + raise Exception(f"Lending failed: {str(e)}") diff --git a/src/helpers/solana/performance.py b/src/helpers/solana/performance.py new file mode 100644 index 00000000..d0a8972e --- /dev/null +++ b/src/helpers/solana/performance.py @@ -0,0 +1,140 @@ +from venv import logger +from typing import List, Optional +from solana.rpc.async_api import AsyncClient +from solders.keypair import Keypair # type: ignore +from src.types import ( + NetworkPerformanceMetrics, +) +from solana.rpc.async_api import AsyncClient +from solders.keypair import Keypair # type: ignore + + +async def fetch_performance_samples( + async_client: AsyncClient, wallet: Keypair, sample_count: int = 1 +) -> List[NetworkPerformanceMetrics]: + """ + Fetch detailed performance metrics for a specified number of samples. + + Args: + agent: An instance of SolanaAgent providing the RPC connection. + sample_count: Number of performance samples to retrieve (default: 1). + + Returns: + A list of NetworkPerformanceMetrics objects. + + Raises: + ValueError: If performance samples are unavailable or invalid. + """ + + try: + performance_samples = await async_client.get_recent_performance_samples( + sample_count + ) + + if not performance_samples: + raise ValueError("No performance samples available.") + + return [ + NetworkPerformanceMetrics( + transactions_per_second=sample["num_transactions"] + / sample["sample_period_secs"], + total_transactions=sample["num_transactions"], + sampling_period_seconds=sample["sample_period_secs"], + current_slot=sample["slot"], + ) + for sample in performance_samples + ] + + except Exception as error: + raise ValueError( + f"Failed to fetch performance samples: {str(error)}" + ) from error + + +class SolanaPerformanceTracker: + """ + A utility class for tracking and analyzing Solana network performance metrics. + """ + + def __init__(self, async_client: AsyncClient, wallet: Keypair): + self.async_client = async_client + self.wallet = wallet + self.metrics_history: List[NetworkPerformanceMetrics] = [] + + def record_latest_metrics(self) -> NetworkPerformanceMetrics: + """ + Fetch the latest performance metrics and add them to the history. + + Returns: + The most recent NetworkPerformanceMetrics object. + """ + latest_metrics = fetch_performance_samples(self.async_client, 1) + self.metrics_history.append(latest_metrics[0]) + return latest_metrics[0] + + def calculate_average_tps(self) -> Optional[float]: + """ + Calculate the average TPS from the recorded performance metrics. + + Returns: + The average TPS as a float, or None if no metrics are recorded. + """ + if not self.metrics_history: + return None + return sum( + metric.transactions_per_second for metric in self.metrics_history + ) / len(self.metrics_history) + + def find_maximum_tps(self) -> Optional[float]: + """ + Find the maximum TPS from the recorded performance metrics. + + Returns: + The maximum TPS as a float, or None if no metrics are recorded. + """ + if not self.metrics_history: + return None + return max(metric.transactions_per_second for metric in self.metrics_history) + + def reset_metrics_history(self) -> None: + """Clear all recorded performance metrics.""" + self.metrics_history.clear() + + async def fetch_current_tps(async_client: AsyncClient) -> float: + """ + Fetch the current Transactions Per Second (TPS) on the Solana network. + + Args: + agent: An instance of SolanaAgent providing the RPC connection. + + Returns: + Current TPS as a float. + + Raises: + ValueError: If performance samples are unavailable or invalid. + """ + try: + response = await async_client.get_recent_performance_samples(1) + + performance_samples = response.value + # logger.debug("Performance Samples:", performance_samples) + + if not performance_samples: + raise ValueError("No performance samples available.") + + sample = performance_samples[0] + + if ( + not all( + hasattr(sample, attr) + for attr in ["num_transactions", "sample_period_secs"] + ) + or sample.num_transactions <= 0 + or sample.sample_period_secs <= 0 + ): + raise ValueError("Invalid performance sample data.") + + return sample.num_transactions / sample.sample_period_secs + + except Exception as error: + raise ValueError(f"Failed to fetch TPS: {str(error)}") from error diff --git a/src/helpers/solana/pumpfun.py b/src/helpers/solana/pumpfun.py new file mode 100644 index 00000000..270ca320 --- /dev/null +++ b/src/helpers/solana/pumpfun.py @@ -0,0 +1,203 @@ +import json +import aiohttp +from venv import logger +from typing import Dict, Any, List, Optional +from solana.rpc.commitment import Confirmed +from solana.rpc.commitment import Processed +from solana.rpc.types import TxOpts +from solders import message +from solders.keypair import Keypair # type: ignore +from solders.transaction import VersionedTransaction # type: ignore +from src.types import ( + PumpfunTokenOptions, + TokenLaunchResult, +) +from typing import Any, Dict +from solana.rpc.types import TxOpts +from solders.keypair import Keypair # type: ignore +from solana.rpc.async_api import AsyncClient + + +class PumpfunTokenManager: + @staticmethod + async def _upload_metadata( + session: aiohttp.ClientSession, + token_name: str, + token_ticker: str, + description: str, + image_url: str, + options: Optional[PumpfunTokenOptions] = None, + ) -> Dict[str, Any]: + """ + Uploads token metadata and image to IPFS via Pump.fun. + + Args: + session: An active aiohttp.ClientSession object + token_name: Name of the token + token_ticker: Token symbol/ticker + description: Token description + image_url: URL of the token image + options: Optional token configuration + + Returns: + A dictionary containing the metadata response from the server. + """ + logger.debug("Preparing form data for IPFS upload...") + form_data = aiohttp.FormData() + form_data.add_field("name", token_name) + form_data.add_field("symbol", token_ticker) + form_data.add_field("description", description) + form_data.add_field("showName", "true") + + if options: + if options.twitter: + form_data.add_field("twitter", options.twitter) + if options.telegram: + form_data.add_field("telegram", options.telegram) + if options.website: + form_data.add_field("website", options.website) + + logger.debug(f"Downloading image from {image_url}...") + async with session.get(image_url) as image_response: + logger.debug(f"Image response: {image_response}") + if image_response.status != 200: + raise ValueError( + f"Failed to download image from {image_url} (status {image_response.status})" + ) + image_data = await image_response.read() + + form_data.add_field( + "file", image_data, filename="token_image.png", content_type="image/png" + ) + + logger.debug("Uploading metadata to Pump.fun IPFS endpoint...") + async with session.post( + "https://pump.fun/api/ipfs", data=form_data + ) as response: + if response.status != 200: + error_text = await response.text() + raise RuntimeError( + f"Metadata upload failed (status {response.status}): {error_text}" + ) + + return await response.json() + + @staticmethod + async def _create_token_transaction( + session: aiohttp.ClientSession, + wallet: Keypair, + mint_keypair: Keypair, + metadata_response: Dict[str, Any], + options: Optional[PumpfunTokenOptions] = None, + ) -> bytes: + """ + Creates a token transaction via the Pump.fun API. + + Args: + session: An active aiohttp.ClientSession object + agent: SolanaAgentKit instance + mint_keypair: The Keypair for the token mint + metadata_response: The response from the metadata upload + options: Optional token configuration + + Returns: + Serialized transaction bytes. + """ + options = options or PumpfunTokenOptions() + + payload = { + "publicKey": str(wallet.pubkey()), + "action": "create", + "tokenMetadata": { + "name": metadata_response["metadata"]["name"], + "symbol": metadata_response["metadata"]["symbol"], + "uri": metadata_response["metadataUri"], + }, + "mint": str(mint_keypair.pubkey()), + "denominatedInSol": "true", + "amount": options.initial_liquidity_sol, + "slippage": options.slippage_bps, + "priorityFee": options.priority_fee, + "pool": "pump", + } + + logger.debug("Requesting token transaction from Pump.fun...") + async with session.post( + "https://pumpportal.fun/api/trade-local", json=payload + ) as response: + if response.status != 200: + error_text = await response.text() + raise RuntimeError( + f"Transaction creation failed (status {response.status}): {error_text}" + ) + + tx_data = await response.read() + return tx_data + + @staticmethod + async def launch_pumpfun_token( + async_client: AsyncClient, + wallet: Keypair, + token_name: str, + token_ticker: str, + description: str, + image_url: str, + options: Optional[PumpfunTokenOptions] = None, + ) -> TokenLaunchResult: + """ + Launches a new token on Pump.fun. + + Args: + agent: SolanaAgentKit instance + token_name: Name of the token + token_ticker: Token symbol/ticker + description: Token description + image_url: URL of the token image + options: Optional token configuration + + Returns: + TokenLaunchResult containing the transaction signature, mint address, and metadata URI. + """ + logger.info("Starting token launch process...") + mint_keypair = Keypair() + logger.info(f"Mint public key: {mint_keypair.pubkey()}") + try: + # Use a single aiohttp session for both metadata upload and transaction creation + async with aiohttp.ClientSession() as session: + logger.info("Uploading metadata to IPFS...") + metadata_response = await PumpfunTokenManager._upload_metadata( + session, token_name, token_ticker, description, image_url, options + ) + logger.info(f"Metadata response: {metadata_response}") + + logger.info("Creating token transaction...") + tx_data = await PumpfunTokenManager._create_token_transaction( + session, wallet, mint_keypair, metadata_response, options + ) + logger.info("Deserializing transaction...") + tx = VersionedTransaction.from_bytes(tx_data) + logger.info("Signing transaction...") + signature = wallet.sign_message(message.to_bytes_versioned(tx.message)) + logger.info("Sending transaction to Solana...") + signed_txn = VersionedTransaction.populate(tx.message, [signature]) + logger.info("Transaction sent!") + opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) + logger.info("Transaction sent!1") + result = await async_client.send_transaction(signed_txn, opts=opts) + logger.info("Transaction sent!2") + transaction_id = json.loads(result.to_json())["result"] + + logger.info( + f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}" + ) + logger.debug( + f'Mint: {str(mint_keypair.pubkey())}\nSignature: {signature}\nMetadata URI: {metadata_response["metadataUri"]}' + ) + # close the session + await session.close() + + return True + + except Exception as error: + logger.error(f"Error in launch_pumpfun_token: {error}") + raise Exception(f"Token launch failed: {str(error)}") from error diff --git a/src/helpers/solana/read.py b/src/helpers/solana/read.py new file mode 100644 index 00000000..d84326f1 --- /dev/null +++ b/src/helpers/solana/read.py @@ -0,0 +1,129 @@ +# imports +from venv import logger + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed + +from src.constants import LAMPORTS_PER_SOL +from src.types import JupiterTokenData + +from solders.keypair import Keypair # type: ignore +from solders.pubkey import Pubkey # type: ignore +import requests + +from spl.token.async_client import AsyncToken +from spl.token.instructions import get_associated_token_address +from spl.token.constants import TOKEN_PROGRAM_ID + + +class SolanaReadHelper: + @staticmethod + async def get_balance( + async_client: AsyncClient, + wallet: Keypair, + token_address: str = None, + ) -> int: + logger.debug( + f"Getting balance for {wallet.pubkey()}\ntoken_address: {token_address}" + ) + try: + if not token_address: + response = await async_client.get_balance( + wallet.pubkey(), commitment=Confirmed + ) + return response.value / LAMPORTS_PER_SOL + token_address = Pubkey.from_string(token_address) + spl_client = AsyncToken( + async_client, token_address, TOKEN_PROGRAM_ID, wallet.pubkey() + ) + + mint = await spl_client.get_mint_info() + if not mint.is_initialized: + raise ValueError("Token mint is not initialized.") + + wallet_ata = get_associated_token_address(wallet.pubkey(), token_address) + response = await async_client.get_token_account_balance(wallet_ata) + if response.value is None: + return None + response = response.value.ui_amount + logger.debug(f"Balance response: {response}") + + return float(response) + + except Exception as error: + raise Exception(f"Failed to get balance: {str(error)}") from error + + @staticmethod + def fetch_price(token_address: str) -> float: + url = f"https://api.jup.ag/price/v2?ids={token_address}" + + try: + with requests.get(url) as response: + response.raise_for_status() + data = response.json() + price = data.get("data", {}).get(token_address, {}).get("price") + + if not price: + raise Exception("Price data not available for the given token.") + + return str(price) + except Exception as e: + raise Exception(f"Price fetch failed: {str(e)}") + + @staticmethod + def get_token_by_ticker( + ticker: str, + ) -> str: + try: + response = requests.get( + f"https://api.dexscreener.com/latest/dex/search?q={ticker}" + ) + response.raise_for_status() + + data = response.json() + if not data.get("pairs"): + return None + + solana_pairs = [ + pair for pair in data["pairs"] if pair.get("chainId") == "solana" + ] + solana_pairs.sort(key=lambda x: x.get("fdv", 0), reverse=True) + + solana_pairs = [ + pair + for pair in solana_pairs + if pair.get("baseToken", {}).get("symbol", "").lower() == ticker.lower() + ] + + if solana_pairs: + return solana_pairs[0].get("baseToken", {}).get("address") + return None + except Exception as error: + logger.error( + f"Error fetching token address from DexScreener: {str(error)}", + exc_info=True, + ) + return None + + @staticmethod + def get_token_by_address( + address: str, + ) -> str: + try: + response = requests.get( + "https://tokens.jup.ag/tokens?tags=verified", + headers={"Content-Type": "application/json"}, + ) + response.raise_for_status() + + data = response.json() + for token in data: + if token.get("address") == str(address): + return JupiterTokenData( + address=token.get("address"), + symbol=token.get("symbol"), + name=token.get("name"), + ) + return None + except Exception as error: + raise Exception(f"Error fetching token data: {str(error)}") diff --git a/src/helpers/solana/stake.py b/src/helpers/solana/stake.py new file mode 100644 index 00000000..8525dd21 --- /dev/null +++ b/src/helpers/solana/stake.py @@ -0,0 +1,55 @@ +import base64 +import json +import aiohttp +from venv import logger + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Processed +from solana.rpc.types import TxOpts +from solana.rpc.async_api import AsyncClient +from solders import message +from solders.keypair import Keypair # type: ignore +from solders.transaction import VersionedTransaction # type: ignore + +from solders.keypair import Keypair # type: ignore + + +class StakeManager: + @staticmethod + async def stake_with_jup( + async_client: AsyncClient, wallet: Keypair, amount: float + ) -> str: + + try: + + url = f"https://worker.jup.ag/blinks/swap/So11111111111111111111111111111111111111112/jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v/{amount}" + payload = {"account": str(wallet.pubkey())} + + async with aiohttp.ClientSession() as session: + async with session.post(url, json=payload) as res: + if res.status != 200: + raise Exception(f"Failed to fetch transaction: {res.status}") + + data = await res.json() + + raw_transaction = VersionedTransaction.from_bytes( + base64.b64decode(data["transaction"]) + ) + signature = wallet.sign_message( + message.to_bytes_versioned(raw_transaction.message) + ) + signed_txn = VersionedTransaction.populate( + raw_transaction.message, [signature] + ) + opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) + result = await async_client.send_raw_transaction( + txn=bytes(signed_txn), opts=opts + ) + transaction_id = json.loads(result.to_json())["result"] + logger.debug( + f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}" + ) + return str(signature) + + except Exception as e: + raise Exception(f"jupSOL staking failed: {str(e)}") diff --git a/src/helpers/solana/token_deploy.py b/src/helpers/solana/token_deploy.py new file mode 100644 index 00000000..c39a0da5 --- /dev/null +++ b/src/helpers/solana/token_deploy.py @@ -0,0 +1,144 @@ +from venv import logger +from typing import Dict, Any + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed +from solana.rpc.types import TxOpts +from solana.transaction import Transaction + +from solders.keypair import Keypair # type: ignore +from solders.system_program import CreateAccountParams, create_account + +from spl.token._layouts import MINT_LAYOUT +from spl.token.constants import TOKEN_PROGRAM_ID +from spl.token.instructions import ( + InitializeMintParams, + MintToParams, + create_associated_token_account, + get_associated_token_address, + initialize_mint, + mint_to, +) + + +class TokenDeploymentManager: + @staticmethod + async def deploy_token( + async_client: AsyncClient, wallet: Keypair, decimals: int = 9 + ) -> Dict[str, Any]: + """ + Deploy a new SPL token. + + Args: + agent: SolanaAgentKit instance with wallet and connection. + decimals: Number of decimals for the token (default: 9). + + Returns: + A dictionary containing the token mint address. + """ + try: + new_mint = Keypair() + logger.debug(f"Generated mint address: {new_mint.pubkey()}") + + sender = wallet + client = async_client + sender_ata = get_associated_token_address( + sender.pubkey(), new_mint.pubkey() + ) + + transaction = Transaction() + + blockhash = await client.get_latest_blockhash() + transaction.recent_blockhash = blockhash.value.blockhash + + lamports = ( + await client.get_minimum_balance_for_rent_exemption( + MINT_LAYOUT.sizeof() + ) + ).value + + # Add the create account instruction + transaction.add( + create_account( + CreateAccountParams( + from_pubkey=sender.pubkey(), + to_pubkey=new_mint.pubkey(), + owner=TOKEN_PROGRAM_ID, + lamports=lamports, + space=MINT_LAYOUT.sizeof(), + ) + ) + ) + + transaction.fee_payer = sender.pubkey() + + # Add the initialize mint instruction + transaction.add( + initialize_mint( + InitializeMintParams( + decimals=decimals, + freeze_authority=sender.pubkey(), + mint=new_mint.pubkey(), + mint_authority=sender.pubkey(), + program_id=TOKEN_PROGRAM_ID, + ) + ) + ) + + transaction.add( + create_associated_token_account( + sender.pubkey(), sender.pubkey(), new_mint.pubkey() + ) + ) + + amount_to_transfer = 1000000000 * 10**8 + transaction.add( + mint_to( + MintToParams( + amount=amount_to_transfer, + dest=sender_ata, + mint=new_mint.pubkey(), + mint_authority=sender.pubkey(), + program_id=TOKEN_PROGRAM_ID, + signers=[sender.pubkey(), new_mint.pubkey()], + ) + ) + ) + + blockhash_response = await async_client.get_latest_blockhash() + recent_blockhash = blockhash_response.value.blockhash + transaction.recent_blockhash = recent_blockhash + + transaction.sign_partial(new_mint) + transaction.sign(sender) + + tx_resp = await async_client.send_raw_transaction( + transaction.serialize(), opts=TxOpts(preflight_commitment=Confirmed) + ) + + logger.debug(f"resp {tx_resp}") + + tx_id = tx_resp.value + + logger.debug(f"tx_id {tx_id}") + + await async_client.confirm_transaction( + tx_id, + commitment=Confirmed, + last_valid_block_height=blockhash.value.last_valid_block_height, + ) + + logger.debug(f"https://explorer.solana.com/tx/{tx_resp}") + + await client.close() + + logger.debug(f"Transaction Signature: {tx_resp}") + + return { + "mint": str(new_mint.pubkey()), + "signature": tx_resp.value, + } + + except Exception as e: + logger.error(f"Token deployment failed: {str(e)}") + raise Exception(f"Token deployment failed: {str(e)}") diff --git a/src/helpers/solana/trade.py b/src/helpers/solana/trade.py new file mode 100644 index 00000000..29b120c9 --- /dev/null +++ b/src/helpers/solana/trade.py @@ -0,0 +1,90 @@ +import base64 +import json +from venv import logger + +from jupiter_python_sdk.jupiter import Jupiter + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Processed +from solana.rpc.types import TxOpts + +from solders import message +from solders.keypair import Keypair # type: ignore +from solders.pubkey import Pubkey # type: ignore +from solders.transaction import VersionedTransaction # type: ignore + +from spl.token.async_client import AsyncToken +from spl.token.constants import TOKEN_PROGRAM_ID + +from src.constants import DEFAULT_OPTIONS +from src.helpers.solana.transfer import SolanaTransferHelper + + +class TradeManager: + @staticmethod + async def trade( + async_client: AsyncClient, + wallet: Keypair, + jupiter: Jupiter, + output_mint: str, + input_amount: float, + input_mint: str, + slippage_bps: int, + ) -> str: + """ + Swap tokens using Jupiter Exchange. + + Args: + agent (SolanaAgentKit): The Solana agent instance. + output_mint (Pubkey): Target token mint address. + input_amount (float): Amount to swap (in token decimals). + input_mint (Pubkey): Source token mint address (default: USDC). + slippage_bps (int): Slippage tolerance in basis points (default: 300 = 3%). + + Returns: + str: Transaction signature. + + Raises: + Exception: If the swap fails. + """ + # convert wallet.secret() from bytes to string + input_mint = str(input_mint) + output_mint = str(output_mint) + spl_client = AsyncToken( + async_client, Pubkey.from_string(input_mint), TOKEN_PROGRAM_ID, wallet + ) + mint = await spl_client.get_mint_info() + decimals = mint.decimals + input_amount = int(input_amount * 10**decimals) + + try: + transaction_data = await jupiter.swap( + input_mint, + output_mint, + input_amount, + only_direct_routes=False, + slippage_bps=slippage_bps, + ) + raw_transaction = VersionedTransaction.from_bytes( + base64.b64decode(transaction_data) + ) + signature = wallet.sign_message( + message.to_bytes_versioned(raw_transaction.message) + ) + signed_txn = VersionedTransaction.populate( + raw_transaction.message, [signature] + ) + opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) + result = await async_client.send_raw_transaction( + txn=bytes(signed_txn), opts=opts + ) + logger.debug(f"Transaction sent: {json.loads(result.to_json())}") + transaction_id = json.loads(result.to_json())["result"] + logger.debug( + f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}" + ) + await SolanaTransferHelper._confirm_transaction(async_client, signature) + return str(signature) + + except Exception as e: + raise Exception(f"Swap failed: {str(e)}") diff --git a/src/helpers/solana/transfer.py b/src/helpers/solana/transfer.py new file mode 100644 index 00000000..f8c4026c --- /dev/null +++ b/src/helpers/solana/transfer.py @@ -0,0 +1,198 @@ +import math +from venv import logger +from src.constants import LAMPORTS_PER_SOL, SOL_FEES + +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed + +from solders.keypair import Keypair # type: ignore +from solders.pubkey import Pubkey # type: ignore +from solders.system_program import TransferParams, transfer +from solders.transaction import VersionedTransaction # type: ignore +from solders.message import MessageV0 # type: ignore + +from spl.token.async_client import AsyncToken +from spl.token.constants import TOKEN_PROGRAM_ID +from spl.token.instructions import get_associated_token_address, transfer_checked +from spl.token.instructions import TransferCheckedParams +from solana.transaction import Transaction +import asyncio + + +class SolanaTransferHelper: + """Helper class for Solana token and SOL transfers.""" + + @staticmethod + async def transfer( + async_client: AsyncClient, + wallet: Keypair, + to: str, + amount: float, + spl_token: str = None, + ) -> str: + """ + Transfer SOL or SPL tokens. + + Args: + async_client: Async RPC client instance. + wallet: Sender's wallet keypair. + to: Recipient's public key as string. + amount: Amount of tokens to transfer. + spl_token: SPL token mint address as string (default: None). + + Returns: + Transaction signature. + """ + try: + # Convert string address to Pubkey + to_pubkey = Pubkey.from_string(to) + + if spl_token: + signature = await SolanaTransferHelper._transfer_spl_tokens( + async_client, + wallet, + to_pubkey, + spl_token, # Pass as string, convert inside function + amount, + ) + token_identifier = str(spl_token) + else: + signature = await SolanaTransferHelper._transfer_native_sol( + async_client, wallet, to_pubkey, amount + ) + token_identifier = "SOL" + + await SolanaTransferHelper._confirm_transaction(async_client, signature) + + logger.debug( + f"\nSuccess!\n\nSignature: {signature}\nFrom Address: {str(wallet.pubkey())}\nTo Address: {to}\nAmount: {amount}\nToken: {token_identifier}" + ) + + return signature + + except Exception as error: + logger.error(f"Transfer failed: {error}") + raise RuntimeError(f"Transfer operation failed: {error}") from error + + @staticmethod + async def _transfer_native_sol( + async_client: AsyncClient, wallet: Keypair, to: Pubkey, amount: float + ) -> str: + """ + Transfer native SOL. + + Args: + async_client: AsyncClient instance + wallet: Sender's keypair + to: Recipient's Pubkey + amount: Amount of SOL to transfer + + Returns: + Transaction signature. + """ + try: + # Convert amount to lamports + lamports = int(amount * LAMPORTS_PER_SOL) + + ix = transfer( + TransferParams( + from_pubkey=wallet.pubkey(), + to_pubkey=to, + lamports=lamports, + ) + ) + + blockhash = (await async_client.get_latest_blockhash()).value.blockhash + msg = MessageV0.try_compile( + payer=wallet.pubkey(), + instructions=[ix], + address_lookup_table_accounts=[], + recent_blockhash=blockhash, + ) + tx = VersionedTransaction(msg, [wallet]) + + result = await async_client.send_transaction(tx) + return result.value + + except Exception as e: + logger.error(f"Native SOL transfer failed: {str(e)}") + raise + + @staticmethod + async def _transfer_spl_tokens( + async_client: AsyncClient, + wallet: Keypair, + recipient: Pubkey, + spl_token: str, + amount: float, + ) -> str: + """ + Transfer SPL tokens from payer to recipient. + + Args: + async_client: Async RPC client instance. + wallet: Sender's keypair. + recipient: Recipient's Pubkey. + spl_token: SPL token mint address as string. + amount: Amount of tokens to transfer. + + Returns: + Transaction signature. + """ + try: + # Convert string token address to Pubkey + token_mint = Pubkey.from_string(spl_token) + + spl_client = AsyncToken( + async_client, token_mint, TOKEN_PROGRAM_ID, wallet.pubkey() + ) + + # Get token decimals + mint = await spl_client.get_mint_info() + decimals = mint.decimals + + # Convert amount to token units + token_amount = math.floor(amount * 10**decimals) + + # Get token accounts + sender_token_address = get_associated_token_address(wallet.pubkey(), token_mint) + recipient_token_address = get_associated_token_address(recipient, token_mint) + + # Create transfer instruction + transfer_ix = transfer_checked( + TransferCheckedParams( + source=sender_token_address, + dest=recipient_token_address, + owner=wallet.pubkey(), + mint=token_mint, + amount=token_amount, + decimals=decimals, + program_id=TOKEN_PROGRAM_ID, + ) + ) + + # Build and send transaction + blockhash = (await async_client.get_latest_blockhash()).value.blockhash + msg = MessageV0.try_compile( + payer=wallet.pubkey(), + instructions=[transfer_ix], + address_lookup_table_accounts=[], + recent_blockhash=blockhash, + ) + tx = VersionedTransaction(msg, [wallet]) + + result = await async_client.send_transaction(tx) + return result.value + + except Exception as e: + logger.error(f"SPL token transfer failed: {str(e)}") + raise + + @staticmethod + async def _confirm_transaction(async_client: AsyncClient, signature: str) -> None: + """Wait for transaction confirmation.""" + try: + await async_client.confirm_transaction(signature, commitment=Confirmed) + except Exception as e: + logger.error(f"Transaction confirmation failed: {str(e)}") + raise \ No newline at end of file diff --git a/src/types/__init__.py b/src/types/__init__.py new file mode 100644 index 00000000..e49d8ffa --- /dev/null +++ b/src/types/__init__.py @@ -0,0 +1,89 @@ +from typing import List, Optional + +from pydantic import BaseModel +from solders.pubkey import Pubkey # type: ignore + + +class BaseModelWithArbitraryTypes(BaseModel): + class Config: + arbitrary_types_allowed = True + +class Creator(BaseModelWithArbitraryTypes): + address: str + percentage: int + +class CollectionOptions(BaseModelWithArbitraryTypes): + name: str + uri: str + royalty_basis_points: Optional[int] = None + creators: Optional[List[Creator]] = None + +class CollectionDeployment(BaseModelWithArbitraryTypes): + collection_address: Pubkey + signature: bytes + +class MintCollectionNFTResponse(BaseModelWithArbitraryTypes): + mint: Pubkey + metadata: Pubkey + +class PumpfunTokenOptions(BaseModelWithArbitraryTypes): + twitter: Optional[str] = None + telegram: Optional[str] = None + website: Optional[str] = None + initial_liquidity_sol: Optional[float] = None + slippage_bps: Optional[int] = None + priority_fee: Optional[int] = None + +class PumpfunLaunchResponse(BaseModelWithArbitraryTypes): + signature: str + mint: str + metadata_uri: Optional[str] = None + error: Optional[str] = None + +class LuloAccountSettings(BaseModelWithArbitraryTypes): + owner: str + allowed_protocols: Optional[str] = None + homebase: Optional[str] = None + minimum_rate: str + +class LuloAccountDetailsResponse(BaseModelWithArbitraryTypes): + total_value: float + interest_earned: float + realtime_apy: float + settings: LuloAccountSettings + +class NetworkPerformanceMetrics(BaseModelWithArbitraryTypes): + """Data structure for Solana network performance metrics.""" + transactions_per_second: float + total_transactions: int + sampling_period_seconds: int + current_slot: int + +class TokenDeploymentResult(BaseModelWithArbitraryTypes): + """Result of a token deployment operation.""" + mint: Pubkey + transaction_signature: str + +class TokenLaunchResult(BaseModelWithArbitraryTypes): + """Result of a token launch operation.""" + signature: str + mint: str + metadata_uri: str + +class TransferResult(BaseModelWithArbitraryTypes): + """Result of a transfer operation.""" + signature: str + from_address: str + to_address: str + amount: float + token: Optional[str] = None + +class JupiterTokenData(BaseModelWithArbitraryTypes): + address:str + symbol:str + name:str + +class GibworkCreateTaskResponse: + status: str + taskId: Optional[str] = None + signature: Optional[str] = None \ No newline at end of file