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