From ea07d04764226410805852c916dd876380118543 Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 14:27:35 +0200 Subject: [PATCH 01/19] fix: better use of funds --- run_service.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/run_service.py b/run_service.py index ce8bbd41..e729bf93 100755 --- a/run_service.py +++ b/run_service.py @@ -43,14 +43,14 @@ LedgerType, ServiceTemplate, ConfigurationTemplate, - FundRequirementsTemplate, ChainType, + FundRequirementsTemplate, ChainType, OnChainState, ) load_dotenv() SUGGESTED_TOP_UP_DEFAULT = 1_000_000_000_000_000 SUGGESTED_SAFE_TOP_UP_DEFAULT = 5_000_000_000_000_000 -MASTER_WALLET_MIMIMUM_BALANCE = 7_000_000_000_000_000 +MASTER_WALLET_MIMIMUM_BALANCE = 6_001_000_000_000_000 COST_OF_BOND = 1 COST_OF_BOND_STAKING = 2 * 10 ** 19 STAKED_BONDING_TOKEN = "OLAS" @@ -467,8 +467,10 @@ def main() -> None: print( f"[{chain_name}] Main wallet balance: {balance_str}", ) + safe_exists = wallet.safes.get(chain_type) is not None + required_balance = MASTER_WALLET_MIMIMUM_BALANCE if safe_exists else SUGGESTED_TOP_UP_DEFAULT print( - f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}", + f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(required_balance, token)}", ) spinner = Halo( text=f"[{chain_name}] Waiting for funds...", @@ -476,15 +478,13 @@ def main() -> None: ) spinner.start() - while ledger_api.get_balance(wallet.crypto.address) < MASTER_WALLET_MIMIMUM_BALANCE: + while ledger_api.get_balance(wallet.crypto.address) < required_balance: time.sleep(1) spinner.succeed(f"[{chain_name}] Main wallet updated balance: {wei_to_token(ledger_api.get_balance(wallet.crypto.address), token)}.") print() - if wallet.safes.get(chain_type) is not None: - print(f"[{chain_name}] Safe already exists") - else: + if not safe_exists: print(f"[{chain_name}] Creating Safe") ledger_type = LedgerType.ETHEREUM wallet_manager = operate.wallet_manager @@ -506,21 +506,24 @@ def main() -> None: print_section(f"[{chain_name}] Set up the service in the Olas Protocol") address = wallet.safes[chain_type] - print( - f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}." - ) - spinner = Halo( - text=f"[{chain_name}] Waiting for funds...", - spinner="dots", - ) - spinner.start() + service_exists = manager._get_on_chain_state(chain_config) != OnChainState.NON_EXISTENT - while ledger_api.get_balance(address) < MASTER_WALLET_MIMIMUM_BALANCE: - time.sleep(1) + if not service_exists: + print( + f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}." + ) + spinner = Halo( + text=f"[{chain_name}] Waiting for funds...", + spinner="dots", + ) + spinner.start() + + while ledger_api.get_balance(address) < MASTER_WALLET_MIMIMUM_BALANCE: + time.sleep(1) - spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") + spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") - if chain_config.chain_data.user_params.use_staking: + if chain_config.chain_data.user_params.use_staking and not service_exists: print(f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(2 * COST_OF_BOND_STAKING, STAKED_BONDING_TOKEN)}") spinner = Halo( @@ -536,7 +539,7 @@ def main() -> None: spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} {STAKED_BONDING_TOKEN}") - if chain_metadata.get("usdcRequired", False): + if chain_metadata.get("usdcRequired", False) and not service_exists: print(f"[{chain_name}] Please make sure address {address} has at least 10 USDC") spinner = Halo( From 9bc804dfe16ae0fd0b34080620d03e3ffeb895d8 Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 15:00:37 +0200 Subject: [PATCH 02/19] fix: better use of funds --- run_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_service.py b/run_service.py index e729bf93..5f0e8cd4 100755 --- a/run_service.py +++ b/run_service.py @@ -147,7 +147,7 @@ def wei_to_unit(wei: int) -> float: def wei_to_token(wei: int, token: str = "xDAI") -> str: """Convert Wei to token.""" - return f"{wei_to_unit(wei):.2f} {token}" + return f"{wei_to_unit(wei):.6f} {token}" def ask_confirm_password() -> str: From 367c78c9732fa65ebf068afc34fcc47b86dae511 Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 15:42:13 +0200 Subject: [PATCH 03/19] fix: better use of funds --- run_service.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/run_service.py b/run_service.py index 5f0e8cd4..9c1529f7 100755 --- a/run_service.py +++ b/run_service.py @@ -26,6 +26,7 @@ import typing as t from dataclasses import dataclass from pathlib import Path +from token import MINUS import requests import yaml @@ -65,15 +66,21 @@ "token": "ETH", "native_token_balance": MASTER_WALLET_MIMIMUM_BALANCE, "usdcRequired": True, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10 * 2, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 10, }, 10: { "name": "Optimism", "token": "ETH", "usdcRequired": False, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, }, 8453: { "name": "Base", "token": "ETH", + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, "usdcRequired": False, }, } @@ -461,14 +468,16 @@ def main() -> None: chain_type=chain_type, rpc=chain_config.ledger_config.rpc, ) + os.environ["CUSTOM_CHAIN_RPC"] = chain_config.ledger_config.rpc + os.environ["OPEN_AUTONOMY_SUBGRAPH_URL"] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" + service_exists = manager._get_on_chain_state(chain_config) != OnChainState.NON_EXISTENT chain_name, token = chain_metadata['name'], chain_metadata["token"] balance_str = wei_to_token(ledger_api.get_balance(wallet.crypto.address), token) print( f"[{chain_name}] Main wallet balance: {balance_str}", ) - safe_exists = wallet.safes.get(chain_type) is not None - required_balance = MASTER_WALLET_MIMIMUM_BALANCE if safe_exists else SUGGESTED_TOP_UP_DEFAULT + required_balance = chain_metadata["firstTimeTopUp"] if not service_exists else chain_metadata["operationalFundReq"] print( f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(required_balance, token)}", ) @@ -484,7 +493,7 @@ def main() -> None: spinner.succeed(f"[{chain_name}] Main wallet updated balance: {wei_to_token(ledger_api.get_balance(wallet.crypto.address), token)}.") print() - if not safe_exists: + if not service_exists: print(f"[{chain_name}] Creating Safe") ledger_type = LedgerType.ETHEREUM wallet_manager = operate.wallet_manager @@ -497,7 +506,7 @@ def main() -> None: print(f"[{chain_name}] Funding Safe") wallet.transfer( to=t.cast(str, wallet.safes[chain_type]), - amount=int(MASTER_WALLET_MIMIMUM_BALANCE), + amount=int(chain_metadata["firstTimeTopUp"]), chain_type=chain_type, from_safe=False, rpc=chain_config.ledger_config.rpc, @@ -506,11 +515,10 @@ def main() -> None: print_section(f"[{chain_name}] Set up the service in the Olas Protocol") address = wallet.safes[chain_type] - service_exists = manager._get_on_chain_state(chain_config) != OnChainState.NON_EXISTENT - if not service_exists: + first_time_top_up = chain_metadata["firstTimeTopUp"] print( - f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}." + f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(first_time_top_up, token)}." ) spinner = Halo( text=f"[{chain_name}] Waiting for funds...", @@ -518,7 +526,7 @@ def main() -> None: ) spinner.start() - while ledger_api.get_balance(address) < MASTER_WALLET_MIMIMUM_BALANCE: + while ledger_api.get_balance(address) < first_time_top_up: time.sleep(1) spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") From c695687d2295247045e3a3bf8590ff5a262c17f0 Mon Sep 17 00:00:00 2001 From: gauravlochab Date: Thu, 12 Sep 2024 20:02:17 +0530 Subject: [PATCH 04/19] chore: cleaning code --- staking_report.py | 1 - wallet_info.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/staking_report.py b/staking_report.py index 35d61d86..bdee722b 100644 --- a/staking_report.py +++ b/staking_report.py @@ -29,7 +29,6 @@ from web3 import HTTPProvider, Web3 SCRIPT_PATH = Path(__file__).resolve().parent -print("SCRIPT_PATH",SCRIPT_PATH) STAKING_TOKEN_JSON_PATH = Path( SCRIPT_PATH, "contracts", diff --git a/wallet_info.py b/wallet_info.py index 6bad5642..1a2afabe 100644 --- a/wallet_info.py +++ b/wallet_info.py @@ -104,8 +104,6 @@ def save_wallet_info(): file_path = OPERATE_HOME / "wallets" / "wallet_info.json" with open(file_path, "w") as f: json.dump(wallet_info, f, indent=2, cls=DecimalEncoder) - - print(f"Wallet information saved to {file_path}") if __name__ == "__main__": save_wallet_info() \ No newline at end of file From e40536e5d76582beabdf35168d5ee8e6267263e8 Mon Sep 17 00:00:00 2001 From: gauravlochab Date: Thu, 12 Sep 2024 20:07:15 +0530 Subject: [PATCH 05/19] chore: edit readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 270fa4cf..5e26d460 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,11 @@ Once the command has completed, i.e. the service is running, you can see the liv ```bash docker logs optimus_abci_0 --follow ``` +Execute the report command to view a summary of the service status: +```bash +python report.py +``` To inspect the tree state transition of the current run of the agent run: ```bash poetry run autonomy analyse logs --from-dir .optimus/services/[service-hash]/deployment/persistent_data/logs/ --agent aea_0 --fsm --reset-db From fd8663f22742e0a36cc32b2ac140870478caa767 Mon Sep 17 00:00:00 2001 From: gauravlochab Date: Thu, 12 Sep 2024 20:09:30 +0530 Subject: [PATCH 06/19] chore: edit readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e26d460..9782a9c0 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ docker logs optimus_abci_0 --follow Execute the report command to view a summary of the service status: ```bash -python report.py +poetry run python report.py ``` To inspect the tree state transition of the current run of the agent run: ```bash From f67242ac58d716b907646f957df9e03e024d97da Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 18:15:50 +0200 Subject: [PATCH 07/19] hotfix: set max fess --- operate/utils/gnosis.py | 9 +++++++++ run_service.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/operate/utils/gnosis.py b/operate/utils/gnosis.py index 0b90a57e..37f22ba7 100644 --- a/operate/utils/gnosis.py +++ b/operate/utils/gnosis.py @@ -20,6 +20,7 @@ """Safe helpers.""" import binascii +import os import secrets import typing as t from enum import Enum @@ -237,6 +238,8 @@ def send_safe_txs( is_deprecated_mode=True, )[2:] } + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( ledger_api=ledger_api, contract_address=safe, @@ -249,6 +252,8 @@ def send_safe_txs( signatures_by_owner=signatures, operation=SafeOperation.CALL.value, nonce=ledger_api.api.eth.get_transaction_count(owner), + max_fee_per_gas=max_fee_per_gas, + max_priority_fee_per_gas=max_priority_fee_per_gas, ) ledger_api.get_transaction_receipt( ledger_api.send_signed_transaction( @@ -325,6 +330,8 @@ def transfer( is_deprecated_mode=True, )[2:] } + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( ledger_api=ledger_api, contract_address=safe, @@ -337,6 +344,8 @@ def transfer( signatures_by_owner=signatures, operation=SafeOperation.CALL.value, nonce=ledger_api.api.eth.get_transaction_count(owner), + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, ) ledger_api.get_transaction_receipt( ledger_api.send_signed_transaction( diff --git a/run_service.py b/run_service.py index 9c1529f7..0f445ad9 100755 --- a/run_service.py +++ b/run_service.py @@ -68,6 +68,11 @@ "usdcRequired": True, "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10 * 2, "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 10, + "gasParams": { + # this means default values will be used + "MAX_PRIORITY_FEE_PER_GAS": "", + "MAX_FEE_PER_GAS": "", + } }, 10: { "name": "Optimism", @@ -75,6 +80,10 @@ "usdcRequired": False, "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, + "gasParams": { + "MAX_PRIORITY_FEE_PER_GAS": str(150_000), + "MAX_FEE_PER_GAS": str(5_000_000_000), + } }, 8453: { "name": "Base", @@ -82,6 +91,10 @@ "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, "usdcRequired": False, + "gasParams": { + "MAX_PRIORITY_FEE_PER_GAS": str(150_000), + "MAX_FEE_PER_GAS": str(5_000_000_000), + } }, } @@ -470,6 +483,8 @@ def main() -> None: ) os.environ["CUSTOM_CHAIN_RPC"] = chain_config.ledger_config.rpc os.environ["OPEN_AUTONOMY_SUBGRAPH_URL"] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" + os.environ["MAX_PRIORITY_FEE_PER_GAS"] = chain_metadata["gasParams"]["MAX_PRIORITY_FEE_PER_GAS"] + os.environ["MAX_FEE_PER_GAS"] = chain_metadata["gasParams"]["MAX_FEE_PER_GAS"] service_exists = manager._get_on_chain_state(chain_config) != OnChainState.NON_EXISTENT chain_name, token = chain_metadata['name'], chain_metadata["token"] From 1924d91b278e2c649217018f4f2235d957384c10 Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 18:39:02 +0200 Subject: [PATCH 08/19] hotfix: lower operational fund req mainnet --- run_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_service.py b/run_service.py index 0f445ad9..1dcd3887 100755 --- a/run_service.py +++ b/run_service.py @@ -67,7 +67,7 @@ "native_token_balance": MASTER_WALLET_MIMIMUM_BALANCE, "usdcRequired": True, "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10 * 2, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 10, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 5, "gasParams": { # this means default values will be used "MAX_PRIORITY_FEE_PER_GAS": "", From a9500ebe91deca276bd50122a2d77ce7159a2bdb Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 18:42:32 +0200 Subject: [PATCH 09/19] hotfix: lower operational fund req optimism --- run_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_service.py b/run_service.py index 1dcd3887..a2440ef1 100755 --- a/run_service.py +++ b/run_service.py @@ -78,7 +78,7 @@ "name": "Optimism", "token": "ETH", "usdcRequired": False, - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 9, "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, "gasParams": { "MAX_PRIORITY_FEE_PER_GAS": str(150_000), From 5dd74ceab569cd6123f616ed475c264748be513e Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 18:46:21 +0200 Subject: [PATCH 10/19] hotfix: lower operational fund req optimism --- run_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_service.py b/run_service.py index a2440ef1..3947507a 100755 --- a/run_service.py +++ b/run_service.py @@ -67,7 +67,7 @@ "native_token_balance": MASTER_WALLET_MIMIMUM_BALANCE, "usdcRequired": True, "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10 * 2, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 5, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 3, "gasParams": { # this means default values will be used "MAX_PRIORITY_FEE_PER_GAS": "", From a6daa697d52d9399b70cb7bf18b59779766240ee Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 12 Sep 2024 19:02:55 +0200 Subject: [PATCH 11/19] fix: use `safe` exists --- run_service.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run_service.py b/run_service.py index 3947507a..defc306c 100755 --- a/run_service.py +++ b/run_service.py @@ -492,7 +492,8 @@ def main() -> None: print( f"[{chain_name}] Main wallet balance: {balance_str}", ) - required_balance = chain_metadata["firstTimeTopUp"] if not service_exists else chain_metadata["operationalFundReq"] + safe_exists = wallet.safes[chain_type] is not None + required_balance = chain_metadata["firstTimeTopUp"] if not safe_exists else chain_metadata["operationalFundReq"] print( f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(required_balance, token)}", ) @@ -508,7 +509,7 @@ def main() -> None: spinner.succeed(f"[{chain_name}] Main wallet updated balance: {wei_to_token(ledger_api.get_balance(wallet.crypto.address), token)}.") print() - if not service_exists: + if not safe_exists: print(f"[{chain_name}] Creating Safe") ledger_type = LedgerType.ETHEREUM wallet_manager = operate.wallet_manager From cd6f6e982fbee42a2bfcde55184a0fff9cd70174 Mon Sep 17 00:00:00 2001 From: gauravlochab Date: Fri, 13 Sep 2024 12:54:49 +0530 Subject: [PATCH 12/19] chore: Modularize wallet_info, staking_report, and report scripts; added utility functions --- report.py | 322 +++++++++++++++---------------------- staking_report.py | 402 ++++++++++++++++++++-------------------------- utils.py | 115 +++++++++++++ wallet_info.py | 201 +++++++++++++++-------- 4 files changed, 554 insertions(+), 486 deletions(-) create mode 100644 utils.py diff --git a/report.py b/report.py index f2f4ccc7..c3d9be22 100644 --- a/report.py +++ b/report.py @@ -1,215 +1,149 @@ +# report.py import json from datetime import datetime -from pathlib import Path -from web3 import Web3 -import docker -# Import necessary functions and constants from run_service.py +import logging +from decimal import Decimal, getcontext + from run_service import ( get_local_config, get_service_template, - wei_to_token, - get_erc20_balance, CHAIN_ID_TO_METADATA, - USDC_ADDRESS, OPERATE_HOME, - OptimusConfig, ) -# Import necessary functions from wallet_info.py -from wallet_info import save_wallet_info -from staking_report import staking_report -# Existing imports and functions... -OUTPUT_WIDTH = 80 - - -class ColorCode: - """Terminal color codes""" - - GREEN = "\033[92m" - RED = "\033[91m" - YELLOW = "\033[93m" - RESET = "\033[0m" - - -def _color_string(text: str, color_code: str) -> str: - return f"{color_code}{text}{ColorCode.RESET}" - - -def _color_bool( - is_true: bool, true_string: str = "True", false_string: str = "False" -) -> str: - if is_true: - return _color_string(true_string, ColorCode.GREEN) - return _color_string(false_string, ColorCode.RED) - - -def _color_percent(p: float, multiplier: float = 100, symbol: str = "%") -> str: - if p >= 0: - return f"{p*multiplier:.2f} {symbol}" - return _color_string(f"{p*multiplier:.2f} {symbol}", ColorCode.RED) - -def _print_section_header(header: str) -> None: - print("\n\n" + header) - print("=" * OUTPUT_WIDTH) - - -def _print_subsection_header(header: str) -> None: - print("\n" + header) - print("-" * OUTPUT_WIDTH) - - -def _print_status(key: str, value: str, message: str = "") -> None: - print(f"{key:<30}{value:<10} {message or ''}") - -def _get_agent_status() -> str: - client = docker.from_env() - optmius_abci_container = ( - client.containers.get("optimus_abci_0") - if "optimus_abci_0" in [c.name for c in client.containers.list()] - else None - ) - is_running = optmius_abci_container - return _color_bool(is_running, "Running", "Stopped") - -def wei_to_unit(wei: int) -> float: - """Convert Wei to unit.""" - return wei / 1e18 - +from utils import ( + _print_section_header, + _print_subsection_header, + _print_status, + wei_to_eth, + _warning_message, + get_chain_name, + load_operator_address, + validate_config, + _get_agent_status, +) -def wei_to_token(wei: int, token: str = "xDAI") -> str: - """Convert Wei to token.""" - return f"{wei_to_unit(wei):.2f} {token}" +from wallet_info import save_wallet_info, load_config as load_wallet_config +from staking_report import staking_report -def wei_to_olas(wei: int) -> str: - """Converts and formats wei to WxDAI.""" - return "{:.2f} OLAS".format(wei_to_unit(wei)) +# Set decimal precision +getcontext().prec = 18 -def _warning_message(current_value: int, threshold: int = 0, message: str = "") -> str: - default_message = _color_string( - f"- Balance too low. Threshold is {wei_to_unit(threshold):.2f}.", - ColorCode.YELLOW, - ) - if current_value < threshold: - return ( - _color_string(f"{message}", ColorCode.YELLOW) - if message - else default_message - ) - return "" +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(message)s') def load_wallet_info(): save_wallet_info() file_path = OPERATE_HOME / "wallets" / "wallet_info.json" - with open(file_path, "r") as f: - return json.load(f) - -def load_config(): - optimus_config = get_local_config() - service_template = get_service_template(optimus_config) - service_hash = service_template["hash"] - - config_path = OPERATE_HOME / f"services/{service_hash}/config.json" - with open(config_path, "r") as f: - return json.load(f) - -def load_operator_address(): - ethereum_json_path = OPERATE_HOME / "wallets" / "ethereum.json" - with open(ethereum_json_path, "r") as f: - ethereum_data = json.load(f) - # Pick the address from safes where safe chain is 4 - return ethereum_data["safes"]["4"] - -def check_service_status(): - # Placeholder for actual status checking logic - return "Stopped" - -def wei_to_eth(wei_value): - return Web3.from_wei(wei_value, 'ether') + try: + with open(file_path, "r") as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: Wallet info file not found at {file_path}") + return {} + except json.JSONDecodeError: + print("Error: Wallet info file contains invalid JSON.") + return {} def generate_report(): - # First, update the wallet info - wallet_info = load_wallet_info() - - operator_address = load_operator_address() - config = load_config() - # Service Report Header - print("") - print("==============") - print("Optimus Service report") - print("Generated on: ", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - print("==============") - # Now load the updated wallet info - - - staking_report(config) - home_chain_id = config.get("home_chain_id") - service_id = config["chain_configs"][home_chain_id]["chain_data"]["token"] - - - - # Service Section - _print_section_header("Service") - _print_status("ID", str(service_id)) - - # Agent Section - agent_status = _get_agent_status() - _print_subsection_header("Agent") - _print_status("Status (on this machine)", agent_status) - _print_status("Address", wallet_info['main_wallet_address']) - # _print_status( - # " Balance", - # f"{wei_to_xdai(agent_xdai)} {_warning_message(agent_xdai, AGENT_XDAI_BALANCE_THRESHOLD)}", - # ) #to-do warning message and threshold - - - # Agent balances across chains - for chain_id, chain_config in config["chain_configs"].items(): - chain_name = CHAIN_ID_TO_METADATA[int(chain_id)]["name"] - balance_info = wallet_info['main_wallet_balances'].get(chain_name, {}) - _print_status(f"{chain_name} Balance",f"{balance_info.get('balance_formatted', 'N/A')}") - # Get the agent threshold from the config - ######TO-DO get the agent threshold from the config - # agent_threshold_wei = chain_config["chain_data"]["user_params"]["fund_requirements"]["agent"] - # agent_threshold = f"{wei_to_eth(agent_threshold_wei):.3f} ETH" - - # # Check for low balance - # current_balance = float(balance_info.get('balance_formatted', '0').split()[0]) - # if current_balance < float(agent_threshold.split()[0]): - # report.append(f"Agent - Balance too low. Threshold is {agent_threshold}.") - - # report.append("") - - # # Safe Section - _print_subsection_header("Safe") - for chain_id, chain_config in config["chain_configs"].items(): - chain_name = CHAIN_ID_TO_METADATA[int(chain_id)]["name"] - safe_info = wallet_info['safe_balances'].get(chain_name, {}) - _print_status(f"Address ({chain_name})",f"{safe_info.get('address', 'N/A')}") - _print_status(f"{safe_info.get('token', 'ETH')} Balance",f"{safe_info.get('balance_formatted', 'N/A')}") - - if 'usdc_balance' in safe_info: - _print_status("USDC Balance",safe_info['usdc_balance_formatted']) - print(" ") - ###### TO-DO Low balance check - # # Get the safe threshold from the config - # safe_threshold_wei = chain_config["chain_data"]["user_params"]["fund_requirements"]["safe"] - # safe_threshold = f"{wei_to_eth(safe_threshold_wei):.3f} ETH" - - # # Check for low balance - # current_balance = float(safe_info.get('balance_formatted', '0').split()[0]) - # if current_balance < float(safe_threshold.split()[0]): - # report.append(f"Safe - Balance too low. Threshold is {safe_threshold}.") - - # report.append("") - - # # Owner/Operator Section - _print_subsection_header("Owner/Operator") - _print_status("Address", operator_address) - for chain_name, balance_info in wallet_info['main_wallet_balances'].items(): - _print_status(f"{chain_name} Balance",balance_info['balance_formatted']) - # report.append(f"{chain_name} Balance {balance_info['balance_formatted']}") - - + try: + # First, update the wallet info + wallet_info = load_wallet_info() + if not wallet_info: + print("Error: Wallet info is empty.") + return + + operator_address = load_operator_address(OPERATE_HOME) + if not operator_address: + print("Error: Operator address could not be loaded.") + return + + config = load_wallet_config() + if not config: + print("Error: Config is empty.") + return + + if not validate_config(config): + return + + # Service Report Header + print("") + print("==============") + print("Optimus Service Report") + print("Generated on: ", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + print("==============") + + staking_report(config) + home_chain_id = config.get("home_chain_id") + service_id = config.get("chain_configs", {}).get(str(home_chain_id), {}).get("chain_data", {}).get("token") + if not service_id: + print(f"Error: 'token' not found in chain data for chain ID {home_chain_id}.") + return + + # Service Section + _print_section_header("Service") + _print_status("ID", str(service_id)) + + # Agent Section + agent_status = _get_agent_status() + _print_subsection_header("Agent") + _print_status("Status (on this machine)", agent_status) + _print_status("Address", wallet_info.get('main_wallet_address', 'N/A')) + + for chain_id, chain_config in config.get("chain_configs", {}).items(): + chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) + balance_info = wallet_info.get('main_wallet_balances', {}).get(chain_name, {}) + balance_formatted = balance_info.get('balance_formatted', 'N/A') + _print_status(f"{chain_name} Balance", balance_formatted) + + # Low balance check + agent_threshold_wei = chain_config.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}).get("agent") + if agent_threshold_wei: + agent_threshold_eth = wei_to_eth(agent_threshold_wei) + current_balance_str = balance_formatted.split()[0] + try: + current_balance = Decimal(current_balance_str) + if current_balance < agent_threshold_eth: + warning_msg = _warning_message(current_balance, agent_threshold_eth, f"Balance below threshold of {agent_threshold_eth:.2f} ETH") + _print_status("Warning", warning_msg) + except (ValueError, Exception): + print(f"Warning: Could not parse balance '{balance_formatted}' for chain '{chain_name}'.") + + # Safe Section + _print_subsection_header("Safe") + safe_balances = wallet_info.get('safe_balances', {}) + for chain_id, chain_config in config.get("chain_configs", {}).items(): + chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) + safe_info = safe_balances.get(chain_name, {}) + _print_status(f"Address ({chain_name})", safe_info.get('address', 'N/A')) + _print_status(f"{safe_info.get('token', 'ETH')} Balance", safe_info.get('balance_formatted', 'N/A')) + + # Check for USDC balance on Ethereum Mainnet + if chain_id == "1": + usdc_balance_formatted = safe_info.get('usdc_balance_formatted', 'N/A') + _print_status("USDC Balance", usdc_balance_formatted) + + # Low balance check + safe_threshold_wei = chain_config.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}).get("safe") + if safe_threshold_wei: + safe_threshold_eth = wei_to_eth(safe_threshold_wei) + balance_str = safe_info.get('balance_formatted', '0').split()[0] + try: + current_balance = Decimal(balance_str) + if current_balance < safe_threshold_eth: + warning_msg = _warning_message(current_balance, safe_threshold_eth, f"Balance below threshold of {safe_threshold_eth:.2f} ETH") + _print_status("Warning", warning_msg) + except (ValueError, Exception): + print(f"Warning: Could not parse balance '{balance_str}' for chain '{chain_name}'.") + print() + # Owner/Operator Section + _print_subsection_header("Owner/Operator") + _print_status("Address", operator_address) + for chain_name, balance_info in wallet_info.get('main_wallet_balances', {}).items(): + _print_status(f"{chain_name} Balance", balance_info.get('balance_formatted', 'N/A')) + + except Exception as e: + print(f"An unexpected error occurred in generate_report: {e}") if __name__ == "__main__": generate_report() diff --git a/staking_report.py b/staking_report.py index bdee722b..4e55c669 100644 --- a/staking_report.py +++ b/staking_report.py @@ -1,260 +1,210 @@ +# staking_report.py import json import math -from datetime import datetime from pathlib import Path -from web3 import Web3 -from argparse import ArgumentParser -from dotenv import dotenv_values -from enum import Enum -from pathlib import Path -import requests -import sys -from typing import Any, List -from operate.ledger.profiles import STAKING -from operate.types import ChainType -# Import necessary functions and constants from run_service.py +from web3 import Web3, HTTPProvider +from decimal import Decimal, getcontext +import logging + from run_service import ( get_local_config, get_service_template, - wei_to_token, - get_erc20_balance, + FALLBACK_STAKING_PARAMS, CHAIN_ID_TO_METADATA, - USDC_ADDRESS, OPERATE_HOME, - FALLBACK_STAKING_PARAMS, - OptimusConfig ) +from operate.ledger.profiles import STAKING +from operate.types import ChainType -from web3 import HTTPProvider, Web3 - -SCRIPT_PATH = Path(__file__).resolve().parent -STAKING_TOKEN_JSON_PATH = Path( - SCRIPT_PATH, - "contracts", - "StakingToken.json", -) -ACTIVITY_CHECKER_JSON_PATH = Path( - SCRIPT_PATH, - "contracts", - "StakingActivityChecker.json", -) -SERVICE_REGISTRY_L2_JSON_PATH = Path( - SCRIPT_PATH, - "contracts", - "ServiceRegistryL2.json", +from utils import ( + _print_section_header, + _print_subsection_header, + _print_status, + wei_to_olas, + wei_to_eth, + _warning_message, + StakingState, + get_chain_name, + load_operator_address, + validate_config, + _color_bool ) -SERVICE_REGISTRY_TOKEN_UTILITY_JSON_PATH = Path( - SCRIPT_PATH, - "contracts", - "ServiceRegistryTokenUtility.json", -) - -OUTPUT_WIDTH = 80 - -class ColorCode: - """Terminal color codes""" - - GREEN = "\033[92m" - RED = "\033[91m" - YELLOW = "\033[93m" - RESET = "\033[0m" - - -class StakingState(Enum): - """Staking state enumeration for the staking.""" - - UNSTAKED = 0 - STAKED = 1 - EVICTED = 2 - -def _color_string(text: str, color_code: str) -> str: - return f"{color_code}{text}{ColorCode.RESET}" - - -def _color_bool( - is_true: bool, true_string: str = "True", false_string: str = "False" -) -> str: - if is_true: - return _color_string(true_string, ColorCode.GREEN) - return _color_string(false_string, ColorCode.RED) - - -def _color_percent(p: float, multiplier: float = 100, symbol: str = "%") -> str: - if p >= 0: - return f"{p*multiplier:.2f} {symbol}" - return _color_string(f"{p*multiplier:.2f} {symbol}", ColorCode.RED) - -def _print_section_header(header: str) -> None: - print("\n\n" + header) - print("=" * OUTPUT_WIDTH) - - -def _print_subsection_header(header: str) -> None: - print("\n" + header) - print("-" * OUTPUT_WIDTH) - - -def _print_status(key: str, value: str, message: str = "") -> None: - print(f"{key:<30}{value:<10} {message or ''}") - -def wei_to_unit(wei: int) -> float: - """Convert Wei to unit.""" - return wei / 1e18 +# Set decimal precision +getcontext().prec = 18 -def wei_to_token(wei: int, token: str = "xDAI") -> str: - """Convert Wei to token.""" - return f"{wei_to_unit(wei):.2f} {token}" +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(message)s') -def wei_to_olas(wei: int) -> str: - """Converts and formats wei to WxDAI.""" - return "{:.2f} OLAS".format(wei_to_unit(wei)) - -def _warning_message(current_value: int, threshold: int = 0, message: str = "") -> str: - default_message = _color_string( - f"- Balance too low. Threshold is {wei_to_unit(threshold):.2f}.", - ColorCode.YELLOW, - ) - if current_value < threshold: - return ( - _color_string(f"{message}", ColorCode.YELLOW) - if message - else default_message - ) - return "" - -def load_operator_address(): - ethereum_json_path = OPERATE_HOME / "wallets" / "ethereum.json" - with open(ethereum_json_path, "r") as f: - ethereum_data = json.load(f) - # Pick the address from safes where safe chain is 4 - return ethereum_data["safes"]["4"] +SCRIPT_PATH = Path(__file__).resolve().parent +STAKING_TOKEN_JSON_PATH = SCRIPT_PATH / "contracts" / "StakingToken.json" +ACTIVITY_CHECKER_JSON_PATH = SCRIPT_PATH / "contracts" / "StakingActivityChecker.json" +SERVICE_REGISTRY_TOKEN_UTILITY_JSON_PATH = SCRIPT_PATH / "contracts" / "ServiceRegistryTokenUtility.json" def staking_report(config: dict) -> None: - - _print_section_header("Performance") - _print_subsection_header("Staking") - operator_address = load_operator_address() - chain_data = next( - (data for data in config["chain_configs"].values() if data["chain_data"]["user_params"]["use_staking"]), - None - ) - if not chain_data: - print("No staking configuration found where use_staking is true.") - return - - rpc = chain_data["ledger_config"]["rpc"] - staking_program_id = chain_data["chain_data"]["user_params"]["staking_program_id"] - home_chain_id = config.get("home_chain_id") - service_id = config["chain_configs"][home_chain_id]["chain_data"]["token"] - multisig_address = config["chain_configs"][home_chain_id]["chain_data"]["multisig"] - w3 = Web3(HTTPProvider(rpc)) - #staking_token_address = "0x88996bbdE7f982D93214881756840cE2c77C4992" - staking_token_address = STAKING[ChainType.OPTIMISM]["optimus_alpha"] - with open(STAKING_TOKEN_JSON_PATH, "r", encoding="utf-8") as file: - staking_token_data = json.load(file) - - staking_token_abi = staking_token_data.get("abi", []) - staking_token_contract = w3.eth.contract( - address=staking_token_address, abi=staking_token_abi # type: ignore - ) - staking_state = StakingState( - staking_token_contract.functions.getStakingState( - service_id # how to get service id and how it knows that on that service id the service is stacked - ).call() - ) - is_staked = ( - staking_state == StakingState.STAKED - or staking_state == StakingState.EVICTED - ) - _print_status("Is service staked?", _color_bool(is_staked, "Yes", "No")) - if is_staked: - _print_status("Staking program", staking_program_id) # type: ignore # how to get the staking program? from config file under stacking program id? - if staking_state == StakingState.STAKED: - _print_status("Staking state", staking_state.name) - elif staking_state == StakingState.EVICTED: - _print_status("Staking state", _color_string(staking_state.name, ColorCode.RED)) - - if is_staked: - - activity_checker_address = staking_token_contract.functions.activityChecker().call() - with open(ACTIVITY_CHECKER_JSON_PATH, "r", encoding="utf-8") as file: - activity_checker_data = json.load(file) - - activity_checker_abi = activity_checker_data.get("abi", []) - activity_checker_contract = w3.eth.contract( - address=activity_checker_address, abi=activity_checker_abi # type: ignore + try: + _print_section_header("Performance") + _print_subsection_header("Staking") + operator_address = load_operator_address(OPERATE_HOME) + if not operator_address: + print("Error: Operator address could not be loaded.") + return + + # Find the chain configuration where use_staking is True + chain_data = next( + ( + data for data in config.get("chain_configs", {}).values() + if data.get("chain_data", {}).get("user_params", {}).get("use_staking") + ), + None + ) + if not chain_data: + print("No staking configuration found where 'use_staking' is true.") + return + + rpc = chain_data.get("ledger_config", {}).get("rpc") + if not rpc: + print("Error: RPC endpoint not found in ledger configuration.") + return + + staking_program_id = chain_data.get("chain_data", {}).get("user_params", {}).get("staking_program_id") + if not staking_program_id: + print("Error: 'staking_program_id' not found in user parameters.") + return + + home_chain_id = config.get("home_chain_id") + if not home_chain_id: + print("Error: 'home_chain_id' not found in config.") + return + + service_id = config.get("chain_configs", {}).get(str(home_chain_id), {}).get("chain_data", {}).get("token") + if not service_id: + print(f"Error: 'token' not found in chain data for chain ID {home_chain_id}.") + return + + multisig_address = config.get("chain_configs", {}).get(str(home_chain_id), {}).get("chain_data", {}).get("multisig") + if not multisig_address: + print(f"Error: 'multisig' address not found in chain data for chain ID {home_chain_id}.") + return + + w3 = Web3(HTTPProvider(rpc)) + + staking_token_address = STAKING.get(ChainType.OPTIMISM, {}).get("optimus_alpha") + if not staking_token_address: + print("Error: Staking token address not found for OPTIMISM ChainType.") + return + + # Load ABI files + with open(STAKING_TOKEN_JSON_PATH, "r", encoding="utf-8") as file: + staking_token_data = json.load(file) + staking_token_abi = staking_token_data.get("abi", []) + staking_token_contract = w3.eth.contract( + address=staking_token_address, abi=staking_token_abi # type: ignore ) - with open( - SERVICE_REGISTRY_TOKEN_UTILITY_JSON_PATH, "r", encoding="utf-8" - ) as file: - service_registry_token_utility_data = json.load(file) + # Get staking state + staking_state_value = staking_token_contract.functions.getStakingState(service_id).call() + staking_state = StakingState(staking_state_value) + is_staked = staking_state in (StakingState.STAKED, StakingState.EVICTED) + _print_status("Is service staked?", _color_bool(is_staked, "Yes", "No")) + if is_staked: + _print_status("Staking program", str(staking_program_id)) + _print_status("Staking state", staking_state.name if staking_state == StakingState.STAKED else _color_string(staking_state.name, ColorCode.RED)) + + # Activity Checker + activity_checker_address = staking_token_contract.functions.activityChecker().call() + with open(ACTIVITY_CHECKER_JSON_PATH, "r", encoding="utf-8") as file: + activity_checker_data = json.load(file) + activity_checker_abi = activity_checker_data.get("abi", []) + activity_checker_contract = w3.eth.contract( + address=activity_checker_address, abi=activity_checker_abi # type: ignore + ) - service_registry_token_utility_contract_address = ( - staking_token_contract.functions.serviceRegistryTokenUtility().call() - ) - service_registry_token_utility_abi = ( - service_registry_token_utility_data.get("abi", []) - ) - service_registry_token_utility_contract = w3.eth.contract( - address=service_registry_token_utility_contract_address, - abi=service_registry_token_utility_abi, - ) + # Service Registry Token Utility + with open(SERVICE_REGISTRY_TOKEN_UTILITY_JSON_PATH, "r", encoding="utf-8") as file: + service_registry_token_utility_data = json.load(file) + service_registry_token_utility_contract_address = staking_token_contract.functions.serviceRegistryTokenUtility().call() + service_registry_token_utility_abi = service_registry_token_utility_data.get("abi", []) + service_registry_token_utility_contract = w3.eth.contract( + address=service_registry_token_utility_contract_address, + abi=service_registry_token_utility_abi, + ) - security_deposit = ( - service_registry_token_utility_contract.functions.getOperatorBalance( + # Get security deposit + security_deposit = service_registry_token_utility_contract.functions.getOperatorBalance( operator_address, service_id ).call() - ) - agent_bond = service_registry_token_utility_contract.functions.getAgentBond( - service_id, FALLBACK_STAKING_PARAMS["agent_ids"][0] - ).call() - min_staking_deposit = ( - staking_token_contract.functions.minStakingDeposit().call() - ) + # Get agent bond + agent_ids = FALLBACK_STAKING_PARAMS.get("agent_ids", []) + if not agent_ids: + print("Error: 'agent_ids' not found in FALLBACK_STAKING_PARAMS.") + return + agent_bond = service_registry_token_utility_contract.functions.getAgentBond( + service_id, agent_ids[0] + ).call() + min_staking_deposit = staking_token_contract.functions.minStakingDeposit().call() + min_security_deposit = min_staking_deposit - min_security_deposit = min_staking_deposit - _print_status( - "Staked (security deposit)", - f"{wei_to_olas(security_deposit)} {_warning_message(security_deposit, min_security_deposit)}", - ) - _print_status( - "Staked (agent bond)", - f"{wei_to_olas(agent_bond)} {_warning_message(agent_bond, min_staking_deposit)}", - ) + security_deposit_formatted = wei_to_olas(security_deposit) + agent_bond_formatted = wei_to_olas(agent_bond) + min_staking_deposit_formatted = wei_to_olas(min_staking_deposit) + + security_deposit_decimal = Decimal(security_deposit_formatted.split()[0]) + min_security_deposit_decimal = Decimal(min_staking_deposit_formatted.split()[0]) - service_info = staking_token_contract.functions.mapServiceInfo( - service_id - ).call() - rewards = service_info[3] - _print_status("Accrued rewards", f"{wei_to_olas(rewards)}") + agent_bond_decimal = Decimal(agent_bond_formatted.split()[0]) - liveness_ratio = ( - activity_checker_contract.functions.livenessRatio().call() + _print_status( + "Staked (security deposit)", + security_deposit_formatted, + _warning_message(security_deposit_decimal, min_security_deposit_decimal) + ) + _print_status( + "Staked (agent bond)", + agent_bond_formatted, + _warning_message(agent_bond_decimal, min_security_deposit_decimal) ) - multisig_nonces_24h_threshold = math.ceil( - (liveness_ratio * 60 * 60 * 24) / 10**18 - ) - multisig_nonces = activity_checker_contract.functions.getMultisigNonces(multisig_address).call() - multisig_nonces = multisig_nonces[0] - service_info = staking_token_contract.functions.getServiceInfo(service_id).call() - multisig_nonces_on_last_checkpoint = service_info[2][0] - multisig_nonces_since_last_cp = ( - multisig_nonces - multisig_nonces_on_last_checkpoint - ) - multisig_nonces_current_epoch = multisig_nonces_since_last_cp - _print_status( + # Accrued rewards + service_info = staking_token_contract.functions.mapServiceInfo(service_id).call() + rewards = service_info[3] + _print_status("Accrued rewards", wei_to_olas(rewards)) + + # Liveness ratio and transactions + liveness_ratio = activity_checker_contract.functions.livenessRatio().call() + multisig_nonces_24h_threshold = math.ceil( + (liveness_ratio * 60 * 60 * 24) / Decimal(1e18) + ) + + multisig_nonces = activity_checker_contract.functions.getMultisigNonces(multisig_address).call() + multisig_nonces = multisig_nonces[0] + service_info = staking_token_contract.functions.getServiceInfo(service_id).call() + multisig_nonces_on_last_checkpoint = service_info[2][0] + multisig_nonces_since_last_cp = multisig_nonces - multisig_nonces_on_last_checkpoint + multisig_nonces_current_epoch = multisig_nonces_since_last_cp + _print_status( "Num. txs current epoch", - f"{multisig_nonces_current_epoch} {_warning_message(multisig_nonces_current_epoch, multisig_nonces_24h_threshold, f'- Too low. Threshold is {multisig_nonces_24h_threshold}.')}", + str(multisig_nonces_current_epoch), + _warning_message( + Decimal(multisig_nonces_current_epoch), + Decimal(multisig_nonces_24h_threshold), + f"- Too low. Threshold is {multisig_nonces_24h_threshold}." + ) ) -if __name__ == "__main__": - with open("./.optimus/services/bafybeie4mwft76qkajsn3wypza5vcpvx5vdaosywqzzsnohr4my6o2xu3y/config.json", "r") as f: - config = json.load(f) - staking_report(config) + except Exception as e: + print(f"An unexpected error occurred in staking_report: {e}") +if __name__ == "__main__": + try: + # Load configuration + config = load_config() + if not config: + print("Error: Config is empty.") + else: + staking_report(config) + except Exception as e: + print(f"An unexpected error occurred: {e}") diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..5b3abe49 --- /dev/null +++ b/utils.py @@ -0,0 +1,115 @@ +# utils.py +import json +from pathlib import Path +from datetime import datetime +from decimal import Decimal, getcontext +import logging +import docker +from web3 import Web3 +from web3.middleware import geth_poa_middleware +from enum import Enum + +# Set decimal precision +getcontext().prec = 18 + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(message)s') + +# Terminal color codes +class ColorCode: + GREEN = "\033[92m" + RED = "\033[91m" + YELLOW = "\033[93m" + RESET = "\033[0m" + +class StakingState(Enum): + """Staking state enumeration for the staking.""" + UNSTAKED = 0 + STAKED = 1 + EVICTED = 2 + +def _color_string(text: str, color_code: str) -> str: + return f"{color_code}{text}{ColorCode.RESET}" + +def _color_bool(is_true: bool, true_string: str = "True", false_string: str = "False") -> str: + if is_true: + return _color_string(true_string, ColorCode.GREEN) + return _color_string(false_string, ColorCode.RED) + +def _warning_message(current_value: Decimal, threshold: Decimal, message: str = "") -> str: + default_message = _color_string( + f"- Value too low. Threshold is {threshold:.2f}.", + ColorCode.YELLOW, + ) + if current_value < threshold: + return _color_string(message or default_message, ColorCode.YELLOW) + return "" + +def _print_section_header(header: str, output_width: int = 80) -> None: + print("\n\n" + header) + print("=" * output_width) + +def _print_subsection_header(header: str, output_width: int = 80) -> None: + print("\n" + header) + print("-" * output_width) + +def _print_status(key: str, value: str, message: str = "") -> None: + line = f"{key:<30}{value:<20}" + if message: + line += f"{message}" + print(line) + +def wei_to_unit(wei: int) -> Decimal: + """Convert Wei to unit.""" + return Decimal(wei) / Decimal(1e18) + +def wei_to_token(wei: int, token: str = "xDAI") -> str: + """Convert Wei to token.""" + return f"{wei_to_unit(wei):.2f} {token}" + +def wei_to_olas(wei: int) -> str: + """Converts and formats wei to OLAS.""" + return "{:.2f} OLAS".format(wei_to_unit(wei)) + +def wei_to_eth(wei_value): + return Decimal(wei_value) / Decimal(1e18) + +def get_chain_name(chain_id, chain_id_to_metadata): + return chain_id_to_metadata.get(int(chain_id), {}).get("name", f"Chain {chain_id}") + +def load_operator_address(operate_home): + ethereum_json_path = operate_home / "wallets" / "ethereum.json" + try: + with open(ethereum_json_path, "r") as f: + ethereum_data = json.load(f) + operator_address = ethereum_data.get("safes", {}).get("4") + if not operator_address: + print("Error: Operator address not found for chain ID 4 in the wallet file.") + return None + return operator_address + except FileNotFoundError: + print(f"Error: Ethereum wallet file not found at {ethereum_json_path}") + return None + except json.JSONDecodeError: + print("Error: Ethereum wallet file contains invalid JSON.") + return None + +def validate_config(config): + required_keys = ['home_chain_id', 'chain_configs'] + for key in required_keys: + if key not in config: + print(f"Error: '{key}' is missing in the configuration.") + return False + return True + +def _get_agent_status() -> str: + try: + client = docker.from_env() + container = client.containers.get("optimus_abci_0") + is_running = container.status == "running" + return _color_bool(is_running, "Running", "Stopped") + except docker.errors.NotFound: + return _color_string("Not Found", ColorCode.RED) + except docker.errors.DockerException as e: + print(f"Error: Docker exception occurred - {str(e)}") + return _color_string("Error", ColorCode.RED) diff --git a/wallet_info.py b/wallet_info.py index 1a2afabe..5cf08544 100644 --- a/wallet_info.py +++ b/wallet_info.py @@ -1,41 +1,83 @@ +# wallet_info.py import json from pathlib import Path from web3 import Web3 from web3.middleware import geth_poa_middleware -from decimal import Decimal +from decimal import Decimal, getcontext +import logging + from run_service import ( get_local_config, get_service_template, - wei_to_token, - get_erc20_balance, CHAIN_ID_TO_METADATA, USDC_ADDRESS, OPERATE_HOME, - OptimusConfig, ) +from utils import ( + get_chain_name, + load_operator_address, + validate_config, + wei_to_unit, + wei_to_eth, + ColorCode, +) +# Set decimal precision +getcontext().prec = 18 -USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" -USDC_ABI = [{"constant":True,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"}] +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(message)s') -def load_config(): - optimus_config = get_local_config() - service_template = get_service_template(optimus_config) - service_hash = service_template["hash"] +# Ensure USDC_ADDRESS is set (if not, define it here) +USDC_ADDRESS = USDC_ADDRESS or "0xA0b86991c6218b36c1d19d4a2e9Eb0cE3606eB48" # Ethereum Mainnet USDC address - config_path = OPERATE_HOME / f"services/{service_hash}/config.json" - with open(config_path, "r") as f: - return json.load(f) +USDC_ABI = [{ + "constant": True, + "inputs": [{"name": "_owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "balance", "type": "uint256"}], + "type": "function" +}] + +def load_config(): + try: + optimus_config = get_local_config() + service_template = get_service_template(optimus_config) + service_hash = service_template.get("hash") + if not service_hash: + print("Error: Service hash not found in service template.") + return {} + config_path = OPERATE_HOME / f"services/{service_hash}/config.json" + try: + with open(config_path, "r") as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: Config file not found at {config_path}") + return {} + except json.JSONDecodeError: + print("Error: Config file contains invalid JSON.") + return {} + except Exception as e: + print(f"An error occurred while loading the config: {e}") + return {} def get_balance(web3, address): - balance = web3.eth.get_balance(address) - return float(Web3.from_wei(balance, 'ether')) + try: + balance = web3.eth.get_balance(address) + return Decimal(Web3.from_wei(balance, 'ether')) + except Exception as e: + print(f"Error getting balance for address {address}: {e}") + return Decimal(0) def get_usdc_balance(web3, address): - usdc_contract = web3.eth.contract(address=USDC_ADDRESS, abi=USDC_ABI) - balance = usdc_contract.functions.balanceOf(address).call() - return float(balance) / 1e6 # USDC has 6 decimal places + try: + usdc_contract = web3.eth.contract(address=USDC_ADDRESS, abi=USDC_ABI) + balance = usdc_contract.functions.balanceOf(address).call() + return Decimal(balance) / Decimal(1e6) # USDC has 6 decimal places + except Exception as e: + print(f"Error getting USDC balance for address {address}: {e}") + return Decimal(0) class DecimalEncoder(json.JSONEncoder): def default(self, o): @@ -45,65 +87,92 @@ def default(self, o): def save_wallet_info(): config = load_config() - main_wallet_address = config['keys'][0]['address'] - + if not config: + print("Error: Configuration could not be loaded.") + return + + main_wallet_address = config.get('keys', [{}])[0].get('address') + if not main_wallet_address: + print("Error: Main wallet address not found in configuration.") + return + main_balances = {} safe_balances = {} - - for chain_id, chain_config in config['chain_configs'].items(): - rpc_url = chain_config['ledger_config']['rpc'] - web3 = Web3(Web3.HTTPProvider(rpc_url)) - web3.middleware_onion.inject(geth_poa_middleware, layer=0) - - chain_name = { - "1": "Ethereum Mainnet", - "10": "Optimism", - "8453": "Base" - }.get(chain_id, f"Chain {chain_id}") - - # Get main wallet balance - main_balance = get_balance(web3, main_wallet_address) - main_balances[chain_name] = { - "token": "ETH", - "balance": main_balance, - "balance_formatted": f"{main_balance:.6f} ETH" - } - - # Get Safe balance - safe_address = chain_config['chain_data']['multisig'] - safe_balance = get_balance(web3, safe_address) - safe_balances[chain_name] = { - "address": safe_address, - "token": "ETH", - "balance": safe_balance, - "balance_formatted": f"{safe_balance:.6f} ETH" - } - - # Get USDC balance for Ethereum Mainnet - if chain_id == "1": - usdc_balance = get_usdc_balance(web3, safe_address) - safe_balances[chain_name]["usdc_balance"] = usdc_balance - safe_balances[chain_name]["usdc_balance_formatted"] = f"{usdc_balance:.2f} USDC" - + + for chain_id, chain_config in config.get('chain_configs', {}).items(): + rpc_url = chain_config.get('ledger_config', {}).get('rpc') + if not rpc_url: + print(f"Error: RPC URL not found for chain ID {chain_id}.") + continue + + try: + web3 = Web3(Web3.HTTPProvider(rpc_url)) + if chain_id != "1": # Ethereum Mainnet + web3.middleware_onion.inject(geth_poa_middleware, layer=0) + + chain_name = get_chain_name(chain_id, CHAIN_ID_TO_METADATA) + + # Get main wallet balance + main_balance = get_balance(web3, main_wallet_address) + main_balances[chain_name] = { + "token": "ETH", + "balance": main_balance, + "balance_formatted": f"{main_balance:.6f} ETH" + } + + # Get Safe balance + safe_address = chain_config.get('chain_data', {}).get('multisig') + if not safe_address: + print(f"Error: Safe address not found for chain ID {chain_id}.") + continue + + safe_balance = get_balance(web3, safe_address) + safe_balances[chain_name] = { + "address": safe_address, + "token": "ETH", + "balance": safe_balance, + "balance_formatted": f"{safe_balance:.6f} ETH" + } + + # Get USDC balance for Ethereum Mainnet (chain ID 1) + if chain_id == "1": + usdc_balance = get_usdc_balance(web3, safe_address) + safe_balances[chain_name]["usdc_balance"] = usdc_balance + safe_balances[chain_name]["usdc_balance_formatted"] = f"{usdc_balance:.2f} USDC" + + except Exception as e: + print(f"An error occurred while processing chain ID {chain_id}: {e}") + continue + wallet_info = { "main_wallet_address": main_wallet_address, "main_wallet_balances": main_balances, - "safe_addresses": {chain_id: config['chain_configs'][chain_id]['chain_data']['multisig'] for chain_id in config['chain_configs']}, + "safe_addresses": { + chain_id: chain_config.get('chain_data', {}).get('multisig', 'N/A') + for chain_id, chain_config in config.get('chain_configs', {}).items() + }, "safe_balances": safe_balances, "chain_configs": { chain_id: { - "rpc": chain_config['ledger_config']['rpc'], - "multisig": chain_config['chain_data']['multisig'], - "token": chain_config['chain_data']['token'], - "fund_requirements": chain_config['chain_data']['user_params']['fund_requirements'] + "rpc": chain_config.get('ledger_config', {}).get('rpc'), + "multisig": chain_config.get('chain_data', {}).get('multisig'), + "token": chain_config.get('chain_data', {}).get('token'), + "fund_requirements": chain_config.get('chain_data', {}).get('user_params', {}).get('fund_requirements') } - for chain_id, chain_config in config['chain_configs'].items() + for chain_id, chain_config in config.get('chain_configs', {}).items() } } - + file_path = OPERATE_HOME / "wallets" / "wallet_info.json" - with open(file_path, "w") as f: - json.dump(wallet_info, f, indent=2, cls=DecimalEncoder) + try: + with open(file_path, "w") as f: + json.dump(wallet_info, f, indent=2, cls=DecimalEncoder) + print(f"Wallet info saved to {file_path}") + except Exception as e: + print(f"Error saving wallet info to {file_path}: {e}") if __name__ == "__main__": - save_wallet_info() \ No newline at end of file + try: + save_wallet_info() + except Exception as e: + print(f"An unexpected error occurred: {e}") From 358bd02583b32045683e55a18b15d7f5837c360a Mon Sep 17 00:00:00 2001 From: gauravlochab Date: Fri, 13 Sep 2024 13:07:04 +0530 Subject: [PATCH 13/19] Revert "Merge commit 'a6daa697d52d9399b70cb7bf18b59779766240ee' into chore/optimize-report" This reverts commit 9df79a47541542047f042d3fb6bd803915918023, reversing changes made to cd6f6e982fbee42a2bfcde55184a0fff9cd70174. --- operate/utils/gnosis.py | 9 ------ run_service.py | 71 +++++++++++++---------------------------- 2 files changed, 22 insertions(+), 58 deletions(-) diff --git a/operate/utils/gnosis.py b/operate/utils/gnosis.py index 37f22ba7..0b90a57e 100644 --- a/operate/utils/gnosis.py +++ b/operate/utils/gnosis.py @@ -20,7 +20,6 @@ """Safe helpers.""" import binascii -import os import secrets import typing as t from enum import Enum @@ -238,8 +237,6 @@ def send_safe_txs( is_deprecated_mode=True, )[2:] } - max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) - max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( ledger_api=ledger_api, contract_address=safe, @@ -252,8 +249,6 @@ def send_safe_txs( signatures_by_owner=signatures, operation=SafeOperation.CALL.value, nonce=ledger_api.api.eth.get_transaction_count(owner), - max_fee_per_gas=max_fee_per_gas, - max_priority_fee_per_gas=max_priority_fee_per_gas, ) ledger_api.get_transaction_receipt( ledger_api.send_signed_transaction( @@ -330,8 +325,6 @@ def transfer( is_deprecated_mode=True, )[2:] } - max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) - max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( ledger_api=ledger_api, contract_address=safe, @@ -344,8 +337,6 @@ def transfer( signatures_by_owner=signatures, operation=SafeOperation.CALL.value, nonce=ledger_api.api.eth.get_transaction_count(owner), - max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, - max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, ) ledger_api.get_transaction_receipt( ledger_api.send_signed_transaction( diff --git a/run_service.py b/run_service.py index defc306c..ce8bbd41 100755 --- a/run_service.py +++ b/run_service.py @@ -26,7 +26,6 @@ import typing as t from dataclasses import dataclass from pathlib import Path -from token import MINUS import requests import yaml @@ -44,14 +43,14 @@ LedgerType, ServiceTemplate, ConfigurationTemplate, - FundRequirementsTemplate, ChainType, OnChainState, + FundRequirementsTemplate, ChainType, ) load_dotenv() SUGGESTED_TOP_UP_DEFAULT = 1_000_000_000_000_000 SUGGESTED_SAFE_TOP_UP_DEFAULT = 5_000_000_000_000_000 -MASTER_WALLET_MIMIMUM_BALANCE = 6_001_000_000_000_000 +MASTER_WALLET_MIMIMUM_BALANCE = 7_000_000_000_000_000 COST_OF_BOND = 1 COST_OF_BOND_STAKING = 2 * 10 ** 19 STAKED_BONDING_TOKEN = "OLAS" @@ -66,35 +65,16 @@ "token": "ETH", "native_token_balance": MASTER_WALLET_MIMIMUM_BALANCE, "usdcRequired": True, - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10 * 2, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 3, - "gasParams": { - # this means default values will be used - "MAX_PRIORITY_FEE_PER_GAS": "", - "MAX_FEE_PER_GAS": "", - } }, 10: { "name": "Optimism", "token": "ETH", "usdcRequired": False, - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 9, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, - "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(5_000_000_000), - } }, 8453: { "name": "Base", "token": "ETH", - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, "usdcRequired": False, - "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(5_000_000_000), - } }, } @@ -167,7 +147,7 @@ def wei_to_unit(wei: int) -> float: def wei_to_token(wei: int, token: str = "xDAI") -> str: """Convert Wei to token.""" - return f"{wei_to_unit(wei):.6f} {token}" + return f"{wei_to_unit(wei):.2f} {token}" def ask_confirm_password() -> str: @@ -481,21 +461,14 @@ def main() -> None: chain_type=chain_type, rpc=chain_config.ledger_config.rpc, ) - os.environ["CUSTOM_CHAIN_RPC"] = chain_config.ledger_config.rpc - os.environ["OPEN_AUTONOMY_SUBGRAPH_URL"] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" - os.environ["MAX_PRIORITY_FEE_PER_GAS"] = chain_metadata["gasParams"]["MAX_PRIORITY_FEE_PER_GAS"] - os.environ["MAX_FEE_PER_GAS"] = chain_metadata["gasParams"]["MAX_FEE_PER_GAS"] - service_exists = manager._get_on_chain_state(chain_config) != OnChainState.NON_EXISTENT chain_name, token = chain_metadata['name'], chain_metadata["token"] balance_str = wei_to_token(ledger_api.get_balance(wallet.crypto.address), token) print( f"[{chain_name}] Main wallet balance: {balance_str}", ) - safe_exists = wallet.safes[chain_type] is not None - required_balance = chain_metadata["firstTimeTopUp"] if not safe_exists else chain_metadata["operationalFundReq"] print( - f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(required_balance, token)}", + f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}", ) spinner = Halo( text=f"[{chain_name}] Waiting for funds...", @@ -503,13 +476,15 @@ def main() -> None: ) spinner.start() - while ledger_api.get_balance(wallet.crypto.address) < required_balance: + while ledger_api.get_balance(wallet.crypto.address) < MASTER_WALLET_MIMIMUM_BALANCE: time.sleep(1) spinner.succeed(f"[{chain_name}] Main wallet updated balance: {wei_to_token(ledger_api.get_balance(wallet.crypto.address), token)}.") print() - if not safe_exists: + if wallet.safes.get(chain_type) is not None: + print(f"[{chain_name}] Safe already exists") + else: print(f"[{chain_name}] Creating Safe") ledger_type = LedgerType.ETHEREUM wallet_manager = operate.wallet_manager @@ -522,7 +497,7 @@ def main() -> None: print(f"[{chain_name}] Funding Safe") wallet.transfer( to=t.cast(str, wallet.safes[chain_type]), - amount=int(chain_metadata["firstTimeTopUp"]), + amount=int(MASTER_WALLET_MIMIMUM_BALANCE), chain_type=chain_type, from_safe=False, rpc=chain_config.ledger_config.rpc, @@ -531,23 +506,21 @@ def main() -> None: print_section(f"[{chain_name}] Set up the service in the Olas Protocol") address = wallet.safes[chain_type] - if not service_exists: - first_time_top_up = chain_metadata["firstTimeTopUp"] - print( - f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(first_time_top_up, token)}." - ) - spinner = Halo( - text=f"[{chain_name}] Waiting for funds...", - spinner="dots", - ) - spinner.start() + print( + f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}." + ) + spinner = Halo( + text=f"[{chain_name}] Waiting for funds...", + spinner="dots", + ) + spinner.start() - while ledger_api.get_balance(address) < first_time_top_up: - time.sleep(1) + while ledger_api.get_balance(address) < MASTER_WALLET_MIMIMUM_BALANCE: + time.sleep(1) - spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") + spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") - if chain_config.chain_data.user_params.use_staking and not service_exists: + if chain_config.chain_data.user_params.use_staking: print(f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(2 * COST_OF_BOND_STAKING, STAKED_BONDING_TOKEN)}") spinner = Halo( @@ -563,7 +536,7 @@ def main() -> None: spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} {STAKED_BONDING_TOKEN}") - if chain_metadata.get("usdcRequired", False) and not service_exists: + if chain_metadata.get("usdcRequired", False): print(f"[{chain_name}] Please make sure address {address} has at least 10 USDC") spinner = Halo( From 631b8c7eab6677c90ffccf3ae775d7a282b3cfb8 Mon Sep 17 00:00:00 2001 From: Ardian Date: Fri, 13 Sep 2024 18:58:31 +0200 Subject: [PATCH 14/19] fix: misc --- operate/services/protocol.py | 5 +++++ operate/utils/gnosis.py | 4 ++-- operate/wallet/master.py | 5 +++++ run_service.py | 35 +++++++++++++++++------------------ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/operate/services/protocol.py b/operate/services/protocol.py index b9fd314b..23d00fbc 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -24,6 +24,7 @@ import io import json import logging +import os import tempfile import time import typing as t @@ -147,6 +148,8 @@ def build(self) -> t.Dict: is_deprecated_mode=True, )[2:] } + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) tx = registry_contracts.gnosis_safe.get_raw_safe_transaction( ledger_api=self.ledger_api, contract_address=self.safe, @@ -159,6 +162,8 @@ def build(self) -> t.Dict: signatures_by_owner=signatures, operation=SafeOperation.DELEGATE_CALL.value, nonce=self.ledger_api.api.eth.get_transaction_count(owner), + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, ) self.tx = self.crypto.sign_transaction(tx) return t.cast(t.Dict, self.tx) diff --git a/operate/utils/gnosis.py b/operate/utils/gnosis.py index 37f22ba7..67628cad 100644 --- a/operate/utils/gnosis.py +++ b/operate/utils/gnosis.py @@ -252,8 +252,8 @@ def send_safe_txs( signatures_by_owner=signatures, operation=SafeOperation.CALL.value, nonce=ledger_api.api.eth.get_transaction_count(owner), - max_fee_per_gas=max_fee_per_gas, - max_priority_fee_per_gas=max_priority_fee_per_gas, + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None ) ledger_api.get_transaction_receipt( ledger_api.send_signed_transaction( diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 3b8be2af..23313ade 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -20,6 +20,7 @@ """Master key implementation""" import json +import os import typing as t from dataclasses import dataclass, field from pathlib import Path @@ -179,6 +180,8 @@ def _build_tx( # pylint: disable=unused-argument *args: t.Any, **kwargs: t.Any ) -> t.Dict: """Build transaction""" + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) tx = ledger_api.get_transfer_transaction( sender_address=self.crypto.address, destination_address=to, @@ -187,6 +190,8 @@ def _build_tx( # pylint: disable=unused-argument tx_nonce="0x", chain_id=chain_type.id, raise_on_try=True, + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, ) return ledger_api.update_with_gas_estimate( transaction=tx, diff --git a/run_service.py b/run_service.py index defc306c..ea074115 100755 --- a/run_service.py +++ b/run_service.py @@ -26,7 +26,6 @@ import typing as t from dataclasses import dataclass from pathlib import Path -from token import MINUS import requests import yaml @@ -78,22 +77,22 @@ "name": "Optimism", "token": "ETH", "usdcRequired": False, - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 9, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 5, "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(5_000_000_000), + "MAX_PRIORITY_FEE_PER_GAS": str(15_000), + "MAX_FEE_PER_GAS": str(1_000_000_000), } }, 8453: { "name": "Base", "token": "ETH", - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 5, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT / 10, "usdcRequired": False, "gasParams": { "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(5_000_000_000), + "MAX_FEE_PER_GAS": str(500_000_000), } }, } @@ -338,7 +337,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate: "fund_requirements": FundRequirementsTemplate( { "agent": SUGGESTED_TOP_UP_DEFAULT, - "safe": SUGGESTED_SAFE_TOP_UP_DEFAULT, + "safe": 0, } ), } @@ -354,7 +353,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate: "fund_requirements": FundRequirementsTemplate( { "agent": SUGGESTED_TOP_UP_DEFAULT, - "safe": SUGGESTED_SAFE_TOP_UP_DEFAULT, + "safe": 0, } ), } @@ -492,7 +491,7 @@ def main() -> None: print( f"[{chain_name}] Main wallet balance: {balance_str}", ) - safe_exists = wallet.safes[chain_type] is not None + safe_exists = wallet.safes.get(chain_type) is not None required_balance = chain_metadata["firstTimeTopUp"] if not safe_exists else chain_metadata["operationalFundReq"] print( f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(required_balance, token)}", @@ -519,14 +518,6 @@ def main() -> None: chain_type=chain_type, rpc=chain_config.ledger_config.rpc, ) - print(f"[{chain_name}] Funding Safe") - wallet.transfer( - to=t.cast(str, wallet.safes[chain_type]), - amount=int(chain_metadata["firstTimeTopUp"]), - chain_type=chain_type, - from_safe=False, - rpc=chain_config.ledger_config.rpc, - ) print_section(f"[{chain_name}] Set up the service in the Olas Protocol") @@ -543,6 +534,14 @@ def main() -> None: spinner.start() while ledger_api.get_balance(address) < first_time_top_up: + print(f"[{chain_name}] Funding Safe") + wallet.transfer( + to=t.cast(str, wallet.safes[chain_type]), + amount=int(chain_metadata["firstTimeTopUp"]), + chain_type=chain_type, + from_safe=False, + rpc=chain_config.ledger_config.rpc, + ) time.sleep(1) spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") From 64128d4c3415f669a9c656ad9421be2f839f5159 Mon Sep 17 00:00:00 2001 From: gauravlochab Date: Mon, 16 Sep 2024 22:32:44 +0530 Subject: [PATCH 15/19] "Reverted changes to commit 631b8c7eab6677c90ffccf3ae775d7a282b3cfb8" --- operate/services/protocol.py | 3045 +++++++++++++++++----------------- operate/utils/gnosis.py | 708 ++++---- operate/wallet/master.py | 877 +++++----- run_service.py | 1214 +++++++------- 4 files changed, 2935 insertions(+), 2909 deletions(-) mode change 100755 => 100644 run_service.py diff --git a/operate/services/protocol.py b/operate/services/protocol.py index b9fd314b..6988a7dd 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -1,1520 +1,1525 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2021-2024 Valory AG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ -"""This module implements the onchain manager.""" - -import binascii -import contextlib -import io -import json -import logging -import tempfile -import time -import typing as t -from datetime import datetime -from enum import Enum -from pathlib import Path -from typing import Optional, Union - -from aea.configurations.data_types import PackageType -from aea.crypto.base import Crypto, LedgerApi -from aea.helpers.base import IPFSHash, cd -from aea_ledger_ethereum.ethereum import EthereumCrypto -from autonomy.chain.base import registry_contracts -from autonomy.chain.config import ChainConfigs, ChainType, ContractConfigs -from autonomy.chain.constants import ( - GNOSIS_SAFE_PROXY_FACTORY_CONTRACT, - GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT, - MULTISEND_CONTRACT, -) -from autonomy.chain.service import ( - get_agent_instances, - get_delployment_payload, - get_reuse_multisig_payload, - get_service_info, -) -from autonomy.chain.tx import TxSettler -from autonomy.cli.helpers.chain import MintHelper as MintManager -from autonomy.cli.helpers.chain import OnChainHelper -from autonomy.cli.helpers.chain import ServiceHelper as ServiceManager -from eth_utils import to_bytes -from hexbytes import HexBytes -from web3.contract import Contract - -from operate.constants import ( - ON_CHAIN_INTERACT_RETRIES, - ON_CHAIN_INTERACT_SLEEP, - ON_CHAIN_INTERACT_TIMEOUT, -) -from operate.data import DATA_DIR -from operate.data.contracts.service_staking_token.contract import ( - ServiceStakingTokenContract, -) -from operate.types import ContractAddresses -from operate.utils.gnosis import ( - MultiSendOperation, - NULL_ADDRESS, - SafeOperation, - hash_payload_to_hex, - skill_input_hex_to_payload, -) -from operate.wallet.master import MasterWallet -from operate.types import ChainType as OperateChainType - - -ETHEREUM_ERC20 = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - - -class StakingState(Enum): - """Staking state enumeration for the staking.""" - - UNSTAKED = 0 - STAKED = 1 - EVICTED = 2 - - -class GnosisSafeTransaction: - """Safe transaction""" - - def __init__( - self, - ledger_api: LedgerApi, - crypto: Crypto, - chain_type: ChainType, - safe: str, - ) -> None: - """Initiliaze a Gnosis safe tx""" - self.ledger_api = ledger_api - self.crypto = crypto - self.chain_type = chain_type - self.safe = safe - self._txs: t.List[t.Dict] = [] - self.tx: t.Optional[t.Dict] = None - - def add(self, tx: t.Dict) -> "GnosisSafeTransaction": - """Add a transaction""" - self._txs.append(tx) - return self - - def build(self) -> t.Dict: - """Build the transaction.""" - multisend_data = bytes.fromhex( - registry_contracts.multisend.get_tx_data( - ledger_api=self.ledger_api, - contract_address=ContractConfigs.multisend.contracts[self.chain_type], - multi_send_txs=self._txs, - ).get("data")[2:] - ) - safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( - ledger_api=self.ledger_api, - contract_address=self.safe, - value=0, - safe_tx_gas=0, - to_address=ContractConfigs.multisend.contracts[self.chain_type], - data=multisend_data, - operation=SafeOperation.DELEGATE_CALL.value, - ).get("tx_hash")[2:] - payload_data = hash_payload_to_hex( - safe_tx_hash=safe_tx_hash, - ether_value=0, - safe_tx_gas=0, - to_address=ContractConfigs.multisend.contracts[self.chain_type], - operation=SafeOperation.DELEGATE_CALL.value, - data=multisend_data, - ) - owner = self.ledger_api.api.to_checksum_address(self.crypto.address) - tx_params = skill_input_hex_to_payload(payload=payload_data) - safe_tx_bytes = binascii.unhexlify(tx_params["safe_tx_hash"]) - signatures = { - owner: self.crypto.sign_message( - message=safe_tx_bytes, - is_deprecated_mode=True, - )[2:] - } - tx = registry_contracts.gnosis_safe.get_raw_safe_transaction( - ledger_api=self.ledger_api, - contract_address=self.safe, - sender_address=owner, - owners=(owner,), # type: ignore - to_address=tx_params["to_address"], - value=tx_params["ether_value"], - data=tx_params["data"], - safe_tx_gas=tx_params["safe_tx_gas"], - signatures_by_owner=signatures, - operation=SafeOperation.DELEGATE_CALL.value, - nonce=self.ledger_api.api.eth.get_transaction_count(owner), - ) - self.tx = self.crypto.sign_transaction(tx) - return t.cast(t.Dict, self.tx) - - def settle(self) -> t.Dict: - """Settle the transaction.""" - retries = 0 - deadline = datetime.now().timestamp() + ON_CHAIN_INTERACT_TIMEOUT - while ( - retries < ON_CHAIN_INTERACT_RETRIES - and datetime.now().timestamp() < deadline - ): - try: - self.build() - tx_digest = self.ledger_api.send_signed_transaction(self.tx) - except Exception as e: # pylint: disable=broad-except - print(f"Error sending the safe tx: {e}") - tx_digest = None - - if tx_digest is not None: - receipt = self.ledger_api.api.eth.wait_for_transaction_receipt( - tx_digest - ) - if receipt["status"] != 0: - return receipt - time.sleep(ON_CHAIN_INTERACT_SLEEP) - raise RuntimeError("Timeout while waiting for safe transaction to go through") - - -class StakingManager(OnChainHelper): - """Helper class for staking a service.""" - - def __init__( - self, - key: Path, - chain_type: ChainType = ChainType.CUSTOM, - password: Optional[str] = None, - ) -> None: - """Initialize object.""" - super().__init__(key=key, chain_type=chain_type, password=password) - self.staking_ctr = t.cast( - ServiceStakingTokenContract, - ServiceStakingTokenContract.from_dir( - directory=str(DATA_DIR / "contracts" / "service_staking_token") - ), - ) - - def status(self, service_id: int, staking_contract: str) -> StakingState: - """Is the service staked?""" - return StakingState( - self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - .functions.getStakingState(service_id) - .call() - ) - - def slots_available(self, staking_contract: str) -> bool: - """Check if there are available slots on the staking contract""" - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - available = instance.functions.maxNumServices().call() - len( - instance.functions.getServiceIds().call() - ) - return available > 0 - - def available_rewards(self, staking_contract: str) -> int: - """Get the available staking rewards on the staking contract""" - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - available_rewards = instance.functions.availableRewards().call() - return available_rewards - - def service_info(self, staking_contract: str, service_id: int) -> dict: - """Get the service onchain info""" - return self.staking_ctr.get_service_info( - self.ledger_api, - staking_contract, - service_id, - ).get("data") - - def agent_ids(self, staking_contract: str) -> t.List[int]: - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - return instance.functions.getAgentIds().call() - - def service_registry(self, staking_contract: str) -> str: - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - return instance.functions.serviceRegistry().call() - - def staking_token(self, staking_contract: str) -> str: - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - return instance.functions.stakingToken().call() - - def service_registry_token_utility(self, staking_contract: str) -> str: - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - return instance.functions.serviceRegistryTokenUtility().call() - - def min_staking_deposit(self, staking_contract: str) -> str: - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - return instance.functions.minStakingDeposit().call() - - def activity_checker(self, staking_contract: str) -> str: - instance = self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ) - return instance.functions.activityChecker().call() - - def check_staking_compatibility( - self, - service_id: int, - staking_contract: str, - ) -> None: - """Check if service can be staked.""" - status = self.status(service_id, staking_contract) - if status == StakingState.STAKED: - raise ValueError("Service already staked") - - if status == StakingState.EVICTED: - raise ValueError("Service is evicted") - - if not self.slots_available(staking_contract): - raise ValueError("No sataking slots available.") - - def stake( - self, - service_id: int, - service_registry: str, - staking_contract: str, - ) -> None: - """Stake the service""" - self.check_staking_compatibility( - service_id=service_id, staking_contract=staking_contract - ) - - tx_settler = TxSettler( - ledger_api=self.ledger_api, - crypto=self.crypto, - chain_type=self.chain_type, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - - # we make use of the ERC20 contract to build the approval transaction - # since it has the same interface as ERC721 we might want to create - # a ERC721 contract package - def _build_approval_tx( # pylint: disable=unused-argument - *args: t.Any, **kargs: t.Any - ) -> t.Dict: - return registry_contracts.erc20.get_approve_tx( - ledger_api=self.ledger_api, - contract_address=service_registry, - spender=staking_contract, - sender=self.crypto.address, - amount=service_id, - ) - - setattr(tx_settler, "build", _build_approval_tx) # noqa: B010 - tx_settler.transact( - method=lambda: {}, - contract="", - kwargs={}, - dry_run=False, - ) - - def _build_staking_tx( # pylint: disable=unused-argument - *args: t.Any, **kargs: t.Any - ) -> t.Dict: - return self.ledger_api.build_transaction( - contract_instance=self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ), - method_name="stake", - method_args={"serviceId": service_id}, - tx_args={ - "sender_address": self.crypto.address, - }, - raise_on_try=True, - ) - - setattr(tx_settler, "build", _build_staking_tx) # noqa: B010 - tx_settler.transact( - method=lambda: {}, - contract="", - kwargs={}, - dry_run=False, - ) - - def check_if_unstaking_possible( - self, - service_id: int, - staking_contract: str, - ) -> None: - """Check unstaking availability""" - if self.status( - service_id=service_id, staking_contract=staking_contract - ) not in {StakingState.STAKED, StakingState.EVICTED}: - raise ValueError("Service not staked.") - - ts_start = t.cast(int, self.service_info(staking_contract, service_id)[3]) - available_rewards = t.cast( - int, - self.staking_ctr.available_rewards(self.ledger_api, staking_contract).get( - "data" - ), - ) - minimum_staking_duration = t.cast( - int, - self.staking_ctr.get_min_staking_duration( - self.ledger_api, staking_contract - ).get("data"), - ) - staked_duration = time.time() - ts_start - if staked_duration < minimum_staking_duration and available_rewards > 0: - raise ValueError("Service cannot be unstaked yet.") - - def unstake(self, service_id: int, staking_contract: str) -> None: - """Unstake the service""" - - tx_settler = TxSettler( - ledger_api=self.ledger_api, - crypto=self.crypto, - chain_type=self.chain_type, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - - def _build_unstaking_tx( # pylint: disable=unused-argument - *args: t.Any, **kargs: t.Any - ) -> t.Dict: - return self.ledger_api.build_transaction( - contract_instance=self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ), - method_name="unstake", - method_args={"serviceId": service_id}, - tx_args={ - "sender_address": self.crypto.address, - }, - raise_on_try=True, - ) - - setattr(tx_settler, "build", _build_unstaking_tx) # noqa: B010 - tx_settler.transact( - method=lambda: {}, - contract="", - kwargs={}, - dry_run=False, - ) - - def get_stake_approval_tx_data( - self, - service_id: int, - service_registry: str, - staking_contract: str, - ) -> bytes: - """Get stake approval tx data.""" - self.check_staking_compatibility( - service_id=service_id, - staking_contract=staking_contract, - ) - return registry_contracts.erc20.get_instance( - ledger_api=self.ledger_api, - contract_address=service_registry, - ).encodeABI( - fn_name="approve", - args=[ - staking_contract, - service_id, - ], - ) - - def get_stake_tx_data(self, service_id: int, staking_contract: str) -> bytes: - """Get stake approval tx data.""" - self.check_staking_compatibility( - service_id=service_id, - staking_contract=staking_contract, - ) - return self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ).encodeABI( - fn_name="stake", - args=[service_id], - ) - - def get_unstake_tx_data(self, service_id: int, staking_contract: str) -> bytes: - """Unstake the service""" - self.check_if_unstaking_possible( - service_id=service_id, - staking_contract=staking_contract, - ) - return self.staking_ctr.get_instance( - ledger_api=self.ledger_api, - contract_address=staking_contract, - ).encodeABI( - fn_name="unstake", - args=[service_id], - ) - - -class _ChainUtil: - """On chain service management.""" - - def __init__( - self, - rpc: str, - wallet: MasterWallet, - contracts: ContractAddresses, - chain_type: t.Optional[ChainType] = None, - ) -> None: - """On chain manager.""" - self.rpc = rpc - self.wallet = wallet - self.contracts = contracts - self.chain_type = chain_type or ChainType.CUSTOM - - def _patch(self) -> None: - """Patch contract and chain config.""" - ChainConfigs.get(self.chain_type).rpc = self.rpc - if self.chain_type != ChainType.CUSTOM: - return - - for name, address in self.contracts.items(): - ContractConfigs.get(name=name).contracts[self.chain_type] = address - - @property - def safe(self) -> str: - """Get safe address.""" - chain_id = self.ledger_api.api.eth.chain_id - chain_type = OperateChainType.from_id(chain_id) - return self.wallet.safes[chain_type] - - @property - def crypto(self) -> Crypto: - """Load crypto object.""" - self._patch() - _, crypto = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - ) - return crypto - - @property - def ledger_api(self) -> LedgerApi: - """Load ledger api object.""" - self._patch() - ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - ) - return ledger_api - - @property - def service_manager_instance(self) -> Contract: - """Load service manager contract instance.""" - contract_interface = registry_contracts.service_manager.contract_interface.get(self.ledger_api.identifier, {}) - instance = self.ledger_api.get_contract_instance( - contract_interface, - self.contracts["service_manager"], - ) - return instance - - - def owner_of(self, token_id: int) -> str: - """Get owner of a service.""" - self._patch() - ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type - ) - - - def info(self, token_id: int) -> t.Dict: - """Get service info.""" - self._patch() - ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type - ) - ( - security_deposit, - multisig_address, - config_hash, - threshold, - max_agents, - number_of_agent_instances, - service_state, - canonical_agents, - ) = get_service_info( - ledger_api=ledger_api, - chain_type=self.chain_type, - token_id=token_id, - ) - instances = get_agent_instances( - ledger_api=ledger_api, - chain_type=self.chain_type, - token_id=token_id, - ).get("agentInstances", []) - return dict( - security_deposit=security_deposit, - multisig=multisig_address, - config_hash=config_hash.hex(), - threshold=threshold, - max_agents=max_agents, - number_of_agent_instances=number_of_agent_instances, - service_state=service_state, - canonical_agents=canonical_agents, - instances=instances, - ) - - - def get_service_safe_owners(self, service_id: int) -> t.List[str]: - """Get list of owners.""" - ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type - ) - ( - _, - multisig_address, - _, - _, - _, - _, - _, - _, - ) = get_service_info( - ledger_api=ledger_api, - chain_type=self.chain_type, - token_id=service_id, - ) - return registry_contracts.gnosis_safe.get_owners( - ledger_api=ledger_api, - contract_address=multisig_address, - ).get("owners", []) - - - def swap( # pylint: disable=too-many-arguments,too-many-locals - self, - service_id: int, - multisig: str, - owner_key: str, - new_owner_address: str - ) -> None: - """Swap safe owner.""" - logging.info(f"Swapping safe for service {service_id} [{multisig}]...") - self._patch() - manager = ServiceManager( - service_id=service_id, - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - with tempfile.TemporaryDirectory() as temp_dir: - key_file = Path(temp_dir, "key.txt") - key_file.write_text(owner_key, encoding="utf-8") - owner_crypto = EthereumCrypto(private_key_path=str(key_file)) - owner_cryptos: t.List[EthereumCrypto] = [owner_crypto] - owners = [ - manager.ledger_api.api.to_checksum_address(owner_crypto.address) - for owner_crypto in owner_cryptos - ] - owner_to_swap = owners[0] - multisend_txs = [] - txd = registry_contracts.gnosis_safe.get_swap_owner_data( - ledger_api=manager.ledger_api, - contract_address=multisig, - old_owner=manager.ledger_api.api.to_checksum_address(owner_to_swap), - new_owner=manager.ledger_api.api.to_checksum_address( - new_owner_address - ), - ).get("data") - multisend_txs.append( - { - "operation": MultiSendOperation.CALL, - "to": multisig, - "value": 0, - "data": HexBytes(txd[2:]), - } - ) - multisend_txd = registry_contracts.multisend.get_tx_data( # type: ignore - ledger_api=manager.ledger_api, - contract_address=ContractConfigs.multisend.contracts[self.chain_type], - multi_send_txs=multisend_txs, - ).get("data") - multisend_data = bytes.fromhex(multisend_txd[2:]) - safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( - ledger_api=manager.ledger_api, - contract_address=multisig, - to_address=ContractConfigs.multisend.contracts[self.chain_type], - value=0, - data=multisend_data, - safe_tx_gas=0, - operation=SafeOperation.DELEGATE_CALL.value, - ).get("tx_hash")[2:] - payload_data = hash_payload_to_hex( - safe_tx_hash=safe_tx_hash, - ether_value=0, - safe_tx_gas=0, - to_address=ContractConfigs.multisend.contracts[self.chain_type], - data=multisend_data, - ) - tx_params = skill_input_hex_to_payload(payload=payload_data) - safe_tx_bytes = binascii.unhexlify(tx_params["safe_tx_hash"]) - owner_to_signature = {} - for owner_crypto in owner_cryptos: - signature = owner_crypto.sign_message( - message=safe_tx_bytes, - is_deprecated_mode=True, - ) - owner_to_signature[ - manager.ledger_api.api.to_checksum_address(owner_crypto.address) - ] = signature[2:] - tx = registry_contracts.gnosis_safe.get_raw_safe_transaction( - ledger_api=manager.ledger_api, - contract_address=multisig, - sender_address=owner_crypto.address, - owners=tuple(owners), # type: ignore - to_address=tx_params["to_address"], - value=tx_params["ether_value"], - data=tx_params["data"], - safe_tx_gas=tx_params["safe_tx_gas"], - signatures_by_owner=owner_to_signature, - operation=SafeOperation.DELEGATE_CALL.value, - ) - stx = owner_crypto.sign_transaction(tx) - tx_digest = manager.ledger_api.send_signed_transaction(stx) - receipt = manager.ledger_api.api.eth.wait_for_transaction_receipt(tx_digest) - if receipt["status"] != 1: - raise RuntimeError("Error swapping owners") - - def staking_slots_available(self, staking_contract: str) -> bool: - """Check if there are available slots on the staking contract""" - self._patch() - return StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).slots_available( - staking_contract=staking_contract, - ) - - def staking_rewards_available(self, staking_contract: str) -> bool: - """Check if there are available staking rewards on the staking contract""" - self._patch() - available_rewards = StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).available_rewards( - staking_contract=staking_contract, - ) - return available_rewards > 0 - -class OnChainManager(_ChainUtil): - """On chain service management.""" - - def mint( # pylint: disable=too-many-arguments,too-many-locals - self, - package_path: Path, - agent_id: int, - number_of_slots: int, - cost_of_bond: int, - threshold: int, - nft: Optional[Union[Path, IPFSHash]], - update_token: t.Optional[int] = None, - token: t.Optional[str] = None, - ) -> t.Dict: - """Mint service.""" - # TODO: Support for update - self._patch() - manager = MintManager( - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - update_token=update_token, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - - # Prepare for minting - ( - manager.load_package_configuration( - package_path=package_path, package_type=PackageType.SERVICE - ) - .load_metadata() - .verify_nft(nft=nft) - .verify_service_dependencies(agent_id=agent_id) - .publish_metadata() - ) - - with tempfile.TemporaryDirectory() as temp, contextlib.redirect_stdout( - io.StringIO() - ): - with cd(temp): - kwargs = dict( - number_of_slots=number_of_slots, - cost_of_bond=cost_of_bond, - threshold=threshold, - token=token, - ) - # TODO: Enable after consulting smart contracts team re a safe - # being a service owner - # if update_token is None: - # kwargs["owner"] = self.wallet.safe # noqa: F401 - method = ( - manager.mint_service - if update_token is None - else manager.update_service - ) - method(**kwargs) - (metadata,) = Path(temp).glob("*.json") - published = { - "token": int(Path(metadata).name.replace(".json", "")), - "metadata": json.loads(Path(metadata).read_text(encoding="utf-8")), - } - return published - - def activate( - self, - service_id: int, - token: t.Optional[str] = None, - ) -> None: - """Activate service.""" - logging.info(f"Activating service {service_id}...") - self._patch() - with contextlib.redirect_stdout(io.StringIO()): - ServiceManager( - service_id=service_id, - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ).check_is_service_token_secured( - token=token, - ).activate_service() - - def register( - self, - service_id: int, - instances: t.List[str], - agents: t.List[int], - token: t.Optional[str] = None, - ) -> None: - """Register instance.""" - logging.info(f"Registering service {service_id}...") - with contextlib.redirect_stdout(io.StringIO()): - ServiceManager( - service_id=service_id, - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ).check_is_service_token_secured( - token=token, - ).register_instance( - instances=instances, - agent_ids=agents, - ) - - def deploy( - self, - service_id: int, - reuse_multisig: bool = False, - token: t.Optional[str] = None, - ) -> None: - """Deploy service.""" - logging.info(f"Deploying service {service_id}...") - self._patch() - with contextlib.redirect_stdout(io.StringIO()): - ServiceManager( - service_id=service_id, - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ).check_is_service_token_secured( - token=token, - ).deploy_service( - reuse_multisig=reuse_multisig, - ) - - def terminate(self, service_id: int, token: t.Optional[str] = None) -> None: - """Terminate service.""" - logging.info(f"Terminating service {service_id}...") - self._patch() - with contextlib.redirect_stdout(io.StringIO()): - ServiceManager( - service_id=service_id, - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ).check_is_service_token_secured( - token=token, - ).terminate_service() - - def unbond(self, service_id: int, token: t.Optional[str] = None) -> None: - """Unbond service.""" - logging.info(f"Unbonding service {service_id}...") - self._patch() - with contextlib.redirect_stdout(io.StringIO()): - ServiceManager( - service_id=service_id, - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ).check_is_service_token_secured( - token=token, - ).unbond_service() - - def stake( - self, - service_id: int, - service_registry: str, - staking_contract: str, - ) -> None: - """Stake service.""" - self._patch() - StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).stake( - service_id=service_id, - service_registry=service_registry, - staking_contract=staking_contract, - ) - - def unstake(self, service_id: int, staking_contract: str) -> None: - """Unstake service.""" - self._patch() - StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).unstake( - service_id=service_id, - staking_contract=staking_contract, - ) - - def staking_status(self, service_id: int, staking_contract: str) -> StakingState: - """Stake the service""" - self._patch() - return StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).status( - service_id=service_id, - staking_contract=staking_contract, - ) - - -class EthSafeTxBuilder(_ChainUtil): - """Safe Transaction builder.""" - - def new_tx(self) -> GnosisSafeTransaction: - """Create a new GnosisSafeTransaction instance.""" - return GnosisSafeTransaction( - ledger_api=self.ledger_api, - crypto=self.crypto, - chain_type=self.chain_type, - safe=self.safe, - ) - - def get_mint_tx_data( # pylint: disable=too-many-arguments - self, - package_path: Path, - agent_id: int, - number_of_slots: int, - cost_of_bond: int, - threshold: int, - nft: Optional[Union[Path, IPFSHash]], - update_token: t.Optional[int] = None, - token: t.Optional[str] = None, - ) -> t.Dict: - """Build mint transaction.""" - # TODO: Support for update - self._patch() - manager = MintManager( - chain_type=self.chain_type, - key=self.wallet.key_path, - password=self.wallet.password, - update_token=update_token, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - # Prepare for minting - ( - manager.load_package_configuration( - package_path=package_path, package_type=PackageType.SERVICE - ) - .load_metadata() - .verify_nft(nft=nft) - #.verify_service_dependencies(agent_id=agent_id) # TODO add this check once subgraph production indexes agent 25 - .publish_metadata() - ) - - instance = self.service_manager_instance - if update_token is None: - safe = self.safe - txd = instance.encodeABI( - fn_name="create", - args=[ - safe, - token or ETHEREUM_ERC20, - manager.metadata_hash, - [agent_id], - [[number_of_slots, cost_of_bond]], - threshold, - ], - ) - else: - txd = instance.encodeABI( - fn_name="update", - args=[ - token or ETHEREUM_ERC20, - manager.metadata_hash, - [agent_id], - [[number_of_slots, cost_of_bond]], - threshold, - update_token - ], - ) - - return { - "to": self.contracts["service_manager"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_olas_approval_data( - self, - spender: str, - amount: int, - olas_contract: str, - ) -> t.Dict: - """Get activate tx data.""" - instance = registry_contracts.erc20.get_instance( - ledger_api=self.ledger_api, - contract_address=olas_contract, - ) - txd = instance.encodeABI( - fn_name="approve", - args=[spender, amount], - ) - return { - "to": olas_contract, - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_activate_data(self, service_id: int, cost_of_bond: int) -> t.Dict: - """Get activate tx data.""" - instance = self.service_manager_instance - txd = instance.encodeABI( - fn_name="activateRegistration", - args=[service_id], - ) - return { - "from": self.safe, - "to": self.contracts["service_manager"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": cost_of_bond, - } - - def get_register_instances_data( - self, - service_id: int, - instances: t.List[str], - agents: t.List[int], - cost_of_bond: int, - ) -> t.Dict: - """Get register instances tx data.""" - instance = self.service_manager_instance - txd = instance.encodeABI( - fn_name="registerAgents", - args=[ - service_id, - instances, - agents, - ], - ) - return { - "from": self.safe, - "to": self.contracts["service_manager"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": cost_of_bond, - } - - def get_deploy_data( - self, - service_id: int, - reuse_multisig: bool = False, - ) -> t.Dict: - """Get deploy tx data.""" - instance = self.service_manager_instance - if reuse_multisig: - _deployment_payload, error = get_reuse_multisig_payload( - ledger_api=self.ledger_api, - crypto=self.crypto, - chain_type=self.chain_type, - service_id=service_id, - ) - if _deployment_payload is None: - raise ValueError(error) - deployment_payload = _deployment_payload - gnosis_safe_multisig = ContractConfigs.get( - GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name - ).contracts[self.chain_type] - else: - deployment_payload = get_delployment_payload() - gnosis_safe_multisig = ContractConfigs.get( - GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name - ).contracts[self.chain_type] - - txd = instance.encodeABI( - fn_name="deploy", - args=[ - service_id, - gnosis_safe_multisig, - deployment_payload, - ], - ) - return { - "to": self.contracts["service_manager"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_deploy_data_from_safe( - self, - service_id: int, - master_safe: str, - reuse_multisig: bool = False, - ) -> t.List[t.Dict[str, t.Any]]: - """Get the deploy data instructions for a safe""" - registry_instance = self.service_manager_instance - approve_hash_message = None - if reuse_multisig: - _deployment_payload, approve_hash_message, error = get_reuse_multisig_from_safe_payload( - ledger_api=self.ledger_api, - chain_type=self.chain_type, - service_id=service_id, - master_safe=master_safe, - ) - if _deployment_payload is None: - raise ValueError(error) - deployment_payload = _deployment_payload - gnosis_safe_multisig = ContractConfigs.get( - GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name - ).contracts[self.chain_type] - else: - deployment_payload = get_delployment_payload() - gnosis_safe_multisig = ContractConfigs.get( - GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name - ).contracts[self.chain_type] - - deploy_data = registry_instance.encodeABI( - fn_name="deploy", - args=[ - service_id, - gnosis_safe_multisig, - deployment_payload, - ], - ) - deploy_message = { - "to": self.contracts["service_manager"], - "data": deploy_data[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - if approve_hash_message is None: - return [deploy_message] - return [approve_hash_message, deploy_message] - - def get_terminate_data(self, service_id: int) -> t.Dict: - """Get terminate tx data.""" - instance = self.service_manager_instance - txd = instance.encodeABI( - fn_name="terminate", - args=[service_id], - ) - return { - "to": self.contracts["service_manager"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_unbond_data(self, service_id: int) -> t.Dict: - """Get unbond tx data.""" - instance = self.service_manager_instance - txd = instance.encodeABI( - fn_name="unbond", - args=[service_id], - ) - return { - "to": self.contracts["service_manager"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_staking_approval_data( - self, - service_id: int, - service_registry: str, - staking_contract: str, - ) -> t.Dict: - """Get staking approval data""" - self._patch() - txd = StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).get_stake_approval_tx_data( - service_id=service_id, - service_registry=service_registry, - staking_contract=staking_contract, - ) - return { - "from": self.safe, - "to": self.contracts["service_registry"], - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_staking_data( - self, - service_id: int, - staking_contract: str, - ) -> t.Dict: - """Get staking tx data""" - self._patch() - txd = StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).get_stake_tx_data( - service_id=service_id, - staking_contract=staking_contract, - ) - return { - "to": staking_contract, - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def get_unstaking_data( - self, - service_id: int, - staking_contract: str, - ) -> t.Dict: - """Get unstaking tx data""" - self._patch() - txd = StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).get_unstake_tx_data( - service_id=service_id, - staking_contract=staking_contract, - ) - return { - "to": staking_contract, - "data": txd[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - def staking_slots_available(self, staking_contract: str) -> bool: - """Stake service.""" - self._patch() - return StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).slots_available( - staking_contract=staking_contract, - ) - - def staking_status(self, service_id: int, staking_contract: str) -> StakingState: - """Stake the service""" - self._patch() - return StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).status( - service_id=service_id, - staking_contract=staking_contract, - ) - - def get_staking_params(self, staking_contract: str) -> t.Dict: - """Get agent IDs for the staking contract""" - self._patch() - staking_manager = StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ) - agent_ids = staking_manager.agent_ids( - staking_contract=staking_contract, - ) - service_registry = staking_manager.service_registry( - staking_contract=staking_contract, - ) - staking_token = staking_manager.staking_token( - staking_contract=staking_contract, - ) - service_registry_token_utility = staking_manager.service_registry_token_utility( - staking_contract=staking_contract, - ) - min_staking_deposit = staking_manager.min_staking_deposit( - staking_contract=staking_contract, - ) - activity_checker = staking_manager.activity_checker( - staking_contract=staking_contract, - ) - - return dict( - agent_ids=agent_ids, - service_registry=service_registry, - staking_token=staking_token, - service_registry_token_utility=service_registry_token_utility, - min_staking_deposit=min_staking_deposit, - activity_checker=activity_checker - ) - - def can_unstake(self, service_id: int, staking_contract: str) -> bool: - """Can unstake the service?""" - try: - StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).check_if_unstaking_possible( - service_id=service_id, - staking_contract=staking_contract, - ) - return True - except ValueError: - return False - - def get_swap_data(self, service_id: int, multisig: str, owner_key: str) -> t.Dict: - """Swap safe owner.""" - # TODO: Discuss implementation - raise NotImplementedError() - - - -def get_packed_signature_for_approved_hash(owners: t.Tuple[str]) -> bytes: - """Get the packed signatures.""" - sorted_owners = sorted(owners, key=str.lower) - signatures = b'' - for owner in sorted_owners: - # Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros) - r_bytes = to_bytes(hexstr=owner[2:].rjust(64, '0')) - - # `s` as 32 zero bytes - s_bytes = b'\x00' * 32 - - # `v` as a single byte - v_bytes = to_bytes(1) - - # Concatenate r, s, and v to form the packed signature - packed_signature = r_bytes + s_bytes + v_bytes - signatures += packed_signature - - return signatures - - -def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals - ledger_api: LedgerApi, - chain_type: ChainType, - service_id: int, - master_safe: str, -) -> t.Tuple[Optional[str], Optional[t.Dict[str, t.Any]], Optional[str]]: - """Reuse multisig.""" - _, multisig_address, _, threshold, *_ = get_service_info( - ledger_api=ledger_api, - chain_type=chain_type, - token_id=service_id, - ) - if multisig_address == NULL_ADDRESS: - return None, None, "Cannot reuse multisig, No previous deployment exist!" - - multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[ - chain_type - ] - multisig_instance = registry_contracts.gnosis_safe.get_instance( - ledger_api=ledger_api, - contract_address=multisig_address, - ) - - # Verify if the service was terminated properly or not - old_owners = multisig_instance.functions.getOwners().call() - if len(old_owners) != 1 or master_safe not in old_owners: - return ( - None, - None, - "Service was not terminated properly, the service owner should be the only owner of the safe", - ) - - # Build multisend tx to add new instances as owners - txs = [] - new_owners = t.cast( - t.List[str], - get_agent_instances( - ledger_api=ledger_api, - chain_type=chain_type, - token_id=service_id, - ).get("agentInstances"), - ) - - for _owner in new_owners: - txs.append( - { - "to": multisig_address, - "data": HexBytes( - bytes.fromhex( - multisig_instance.encodeABI( - fn_name="addOwnerWithThreshold", - args=[_owner, 1], - )[2:] - ) - ), - "operation": MultiSendOperation.CALL, - "value": 0, - } - ) - - txs.append( - { - "to": multisig_address, - "data": HexBytes( - bytes.fromhex( - multisig_instance.encodeABI( - fn_name="removeOwner", - args=[new_owners[0], master_safe, 1], - )[2:] - ) - ), - "operation": MultiSendOperation.CALL, - "value": 0, - } - ) - - txs.append( - { - "to": multisig_address, - "data": HexBytes( - bytes.fromhex( - multisig_instance.encodeABI( - fn_name="changeThreshold", - args=[threshold], - )[2:] - ) - ), - "operation": MultiSendOperation.CALL, - "value": 0, - } - ) - - multisend_tx = registry_contracts.multisend.get_multisend_tx( - ledger_api=ledger_api, - contract_address=multisend_address, - txs=txs, - ) - signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe, )) - - safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( - ledger_api=ledger_api, - contract_address=multisig_address, - to_address=multisend_address, - value=multisend_tx["value"], - data=multisend_tx["data"], - operation=1, - ).get("tx_hash") - approve_hash_data = multisig_instance.encodeABI( - fn_name="approveHash", - args=[ - safe_tx_hash, - ], - ) - approve_hash_message = { - "to": multisig_address, - "data": approve_hash_data[2:], - "operation": MultiSendOperation.CALL, - "value": 0, - } - - safe_exec_data = multisig_instance.encodeABI( - fn_name="execTransaction", - args=[ - multisend_address, # to address - multisend_tx["value"], # value - multisend_tx["data"], # data - 1, # operation - 0, # safe tx gas - 0, # bas gas - 0, # safe gas price - NULL_ADDRESS, # gas token - NULL_ADDRESS, # refund receiver - signature_bytes, # signatures - ], - ) - payload = multisig_address + safe_exec_data[2:] - return payload, approve_hash_message, None - +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module implements the onchain manager.""" + +import binascii +import contextlib +import io +import json +import logging +import os +import tempfile +import time +import typing as t +from datetime import datetime +from enum import Enum +from pathlib import Path +from typing import Optional, Union + +from aea.configurations.data_types import PackageType +from aea.crypto.base import Crypto, LedgerApi +from aea.helpers.base import IPFSHash, cd +from aea_ledger_ethereum.ethereum import EthereumCrypto +from autonomy.chain.base import registry_contracts +from autonomy.chain.config import ChainConfigs, ChainType, ContractConfigs +from autonomy.chain.constants import ( + GNOSIS_SAFE_PROXY_FACTORY_CONTRACT, + GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT, + MULTISEND_CONTRACT, +) +from autonomy.chain.service import ( + get_agent_instances, + get_delployment_payload, + get_reuse_multisig_payload, + get_service_info, +) +from autonomy.chain.tx import TxSettler +from autonomy.cli.helpers.chain import MintHelper as MintManager +from autonomy.cli.helpers.chain import OnChainHelper +from autonomy.cli.helpers.chain import ServiceHelper as ServiceManager +from eth_utils import to_bytes +from hexbytes import HexBytes +from web3.contract import Contract + +from operate.constants import ( + ON_CHAIN_INTERACT_RETRIES, + ON_CHAIN_INTERACT_SLEEP, + ON_CHAIN_INTERACT_TIMEOUT, +) +from operate.data import DATA_DIR +from operate.data.contracts.service_staking_token.contract import ( + ServiceStakingTokenContract, +) +from operate.types import ContractAddresses +from operate.utils.gnosis import ( + MultiSendOperation, + NULL_ADDRESS, + SafeOperation, + hash_payload_to_hex, + skill_input_hex_to_payload, +) +from operate.wallet.master import MasterWallet +from operate.types import ChainType as OperateChainType + + +ETHEREUM_ERC20 = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + + +class StakingState(Enum): + """Staking state enumeration for the staking.""" + + UNSTAKED = 0 + STAKED = 1 + EVICTED = 2 + + +class GnosisSafeTransaction: + """Safe transaction""" + + def __init__( + self, + ledger_api: LedgerApi, + crypto: Crypto, + chain_type: ChainType, + safe: str, + ) -> None: + """Initiliaze a Gnosis safe tx""" + self.ledger_api = ledger_api + self.crypto = crypto + self.chain_type = chain_type + self.safe = safe + self._txs: t.List[t.Dict] = [] + self.tx: t.Optional[t.Dict] = None + + def add(self, tx: t.Dict) -> "GnosisSafeTransaction": + """Add a transaction""" + self._txs.append(tx) + return self + + def build(self) -> t.Dict: + """Build the transaction.""" + multisend_data = bytes.fromhex( + registry_contracts.multisend.get_tx_data( + ledger_api=self.ledger_api, + contract_address=ContractConfigs.multisend.contracts[self.chain_type], + multi_send_txs=self._txs, + ).get("data")[2:] + ) + safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( + ledger_api=self.ledger_api, + contract_address=self.safe, + value=0, + safe_tx_gas=0, + to_address=ContractConfigs.multisend.contracts[self.chain_type], + data=multisend_data, + operation=SafeOperation.DELEGATE_CALL.value, + ).get("tx_hash")[2:] + payload_data = hash_payload_to_hex( + safe_tx_hash=safe_tx_hash, + ether_value=0, + safe_tx_gas=0, + to_address=ContractConfigs.multisend.contracts[self.chain_type], + operation=SafeOperation.DELEGATE_CALL.value, + data=multisend_data, + ) + owner = self.ledger_api.api.to_checksum_address(self.crypto.address) + tx_params = skill_input_hex_to_payload(payload=payload_data) + safe_tx_bytes = binascii.unhexlify(tx_params["safe_tx_hash"]) + signatures = { + owner: self.crypto.sign_message( + message=safe_tx_bytes, + is_deprecated_mode=True, + )[2:] + } + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) + tx = registry_contracts.gnosis_safe.get_raw_safe_transaction( + ledger_api=self.ledger_api, + contract_address=self.safe, + sender_address=owner, + owners=(owner,), # type: ignore + to_address=tx_params["to_address"], + value=tx_params["ether_value"], + data=tx_params["data"], + safe_tx_gas=tx_params["safe_tx_gas"], + signatures_by_owner=signatures, + operation=SafeOperation.DELEGATE_CALL.value, + nonce=self.ledger_api.api.eth.get_transaction_count(owner), + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, + ) + self.tx = self.crypto.sign_transaction(tx) + return t.cast(t.Dict, self.tx) + + def settle(self) -> t.Dict: + """Settle the transaction.""" + retries = 0 + deadline = datetime.now().timestamp() + ON_CHAIN_INTERACT_TIMEOUT + while ( + retries < ON_CHAIN_INTERACT_RETRIES + and datetime.now().timestamp() < deadline + ): + try: + self.build() + tx_digest = self.ledger_api.send_signed_transaction(self.tx) + except Exception as e: # pylint: disable=broad-except + print(f"Error sending the safe tx: {e}") + tx_digest = None + + if tx_digest is not None: + receipt = self.ledger_api.api.eth.wait_for_transaction_receipt( + tx_digest + ) + if receipt["status"] != 0: + return receipt + time.sleep(ON_CHAIN_INTERACT_SLEEP) + raise RuntimeError("Timeout while waiting for safe transaction to go through") + + +class StakingManager(OnChainHelper): + """Helper class for staking a service.""" + + def __init__( + self, + key: Path, + chain_type: ChainType = ChainType.CUSTOM, + password: Optional[str] = None, + ) -> None: + """Initialize object.""" + super().__init__(key=key, chain_type=chain_type, password=password) + self.staking_ctr = t.cast( + ServiceStakingTokenContract, + ServiceStakingTokenContract.from_dir( + directory=str(DATA_DIR / "contracts" / "service_staking_token") + ), + ) + + def status(self, service_id: int, staking_contract: str) -> StakingState: + """Is the service staked?""" + return StakingState( + self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + .functions.getStakingState(service_id) + .call() + ) + + def slots_available(self, staking_contract: str) -> bool: + """Check if there are available slots on the staking contract""" + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + available = instance.functions.maxNumServices().call() - len( + instance.functions.getServiceIds().call() + ) + return available > 0 + + def available_rewards(self, staking_contract: str) -> int: + """Get the available staking rewards on the staking contract""" + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + available_rewards = instance.functions.availableRewards().call() + return available_rewards + + def service_info(self, staking_contract: str, service_id: int) -> dict: + """Get the service onchain info""" + return self.staking_ctr.get_service_info( + self.ledger_api, + staking_contract, + service_id, + ).get("data") + + def agent_ids(self, staking_contract: str) -> t.List[int]: + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + return instance.functions.getAgentIds().call() + + def service_registry(self, staking_contract: str) -> str: + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + return instance.functions.serviceRegistry().call() + + def staking_token(self, staking_contract: str) -> str: + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + return instance.functions.stakingToken().call() + + def service_registry_token_utility(self, staking_contract: str) -> str: + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + return instance.functions.serviceRegistryTokenUtility().call() + + def min_staking_deposit(self, staking_contract: str) -> str: + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + return instance.functions.minStakingDeposit().call() + + def activity_checker(self, staking_contract: str) -> str: + instance = self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ) + return instance.functions.activityChecker().call() + + def check_staking_compatibility( + self, + service_id: int, + staking_contract: str, + ) -> None: + """Check if service can be staked.""" + status = self.status(service_id, staking_contract) + if status == StakingState.STAKED: + raise ValueError("Service already staked") + + if status == StakingState.EVICTED: + raise ValueError("Service is evicted") + + if not self.slots_available(staking_contract): + raise ValueError("No sataking slots available.") + + def stake( + self, + service_id: int, + service_registry: str, + staking_contract: str, + ) -> None: + """Stake the service""" + self.check_staking_compatibility( + service_id=service_id, staking_contract=staking_contract + ) + + tx_settler = TxSettler( + ledger_api=self.ledger_api, + crypto=self.crypto, + chain_type=self.chain_type, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + + # we make use of the ERC20 contract to build the approval transaction + # since it has the same interface as ERC721 we might want to create + # a ERC721 contract package + def _build_approval_tx( # pylint: disable=unused-argument + *args: t.Any, **kargs: t.Any + ) -> t.Dict: + return registry_contracts.erc20.get_approve_tx( + ledger_api=self.ledger_api, + contract_address=service_registry, + spender=staking_contract, + sender=self.crypto.address, + amount=service_id, + ) + + setattr(tx_settler, "build", _build_approval_tx) # noqa: B010 + tx_settler.transact( + method=lambda: {}, + contract="", + kwargs={}, + dry_run=False, + ) + + def _build_staking_tx( # pylint: disable=unused-argument + *args: t.Any, **kargs: t.Any + ) -> t.Dict: + return self.ledger_api.build_transaction( + contract_instance=self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ), + method_name="stake", + method_args={"serviceId": service_id}, + tx_args={ + "sender_address": self.crypto.address, + }, + raise_on_try=True, + ) + + setattr(tx_settler, "build", _build_staking_tx) # noqa: B010 + tx_settler.transact( + method=lambda: {}, + contract="", + kwargs={}, + dry_run=False, + ) + + def check_if_unstaking_possible( + self, + service_id: int, + staking_contract: str, + ) -> None: + """Check unstaking availability""" + if self.status( + service_id=service_id, staking_contract=staking_contract + ) not in {StakingState.STAKED, StakingState.EVICTED}: + raise ValueError("Service not staked.") + + ts_start = t.cast(int, self.service_info(staking_contract, service_id)[3]) + available_rewards = t.cast( + int, + self.staking_ctr.available_rewards(self.ledger_api, staking_contract).get( + "data" + ), + ) + minimum_staking_duration = t.cast( + int, + self.staking_ctr.get_min_staking_duration( + self.ledger_api, staking_contract + ).get("data"), + ) + staked_duration = time.time() - ts_start + if staked_duration < minimum_staking_duration and available_rewards > 0: + raise ValueError("Service cannot be unstaked yet.") + + def unstake(self, service_id: int, staking_contract: str) -> None: + """Unstake the service""" + + tx_settler = TxSettler( + ledger_api=self.ledger_api, + crypto=self.crypto, + chain_type=self.chain_type, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + + def _build_unstaking_tx( # pylint: disable=unused-argument + *args: t.Any, **kargs: t.Any + ) -> t.Dict: + return self.ledger_api.build_transaction( + contract_instance=self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ), + method_name="unstake", + method_args={"serviceId": service_id}, + tx_args={ + "sender_address": self.crypto.address, + }, + raise_on_try=True, + ) + + setattr(tx_settler, "build", _build_unstaking_tx) # noqa: B010 + tx_settler.transact( + method=lambda: {}, + contract="", + kwargs={}, + dry_run=False, + ) + + def get_stake_approval_tx_data( + self, + service_id: int, + service_registry: str, + staking_contract: str, + ) -> bytes: + """Get stake approval tx data.""" + self.check_staking_compatibility( + service_id=service_id, + staking_contract=staking_contract, + ) + return registry_contracts.erc20.get_instance( + ledger_api=self.ledger_api, + contract_address=service_registry, + ).encodeABI( + fn_name="approve", + args=[ + staking_contract, + service_id, + ], + ) + + def get_stake_tx_data(self, service_id: int, staking_contract: str) -> bytes: + """Get stake approval tx data.""" + self.check_staking_compatibility( + service_id=service_id, + staking_contract=staking_contract, + ) + return self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ).encodeABI( + fn_name="stake", + args=[service_id], + ) + + def get_unstake_tx_data(self, service_id: int, staking_contract: str) -> bytes: + """Unstake the service""" + self.check_if_unstaking_possible( + service_id=service_id, + staking_contract=staking_contract, + ) + return self.staking_ctr.get_instance( + ledger_api=self.ledger_api, + contract_address=staking_contract, + ).encodeABI( + fn_name="unstake", + args=[service_id], + ) + + +class _ChainUtil: + """On chain service management.""" + + def __init__( + self, + rpc: str, + wallet: MasterWallet, + contracts: ContractAddresses, + chain_type: t.Optional[ChainType] = None, + ) -> None: + """On chain manager.""" + self.rpc = rpc + self.wallet = wallet + self.contracts = contracts + self.chain_type = chain_type or ChainType.CUSTOM + + def _patch(self) -> None: + """Patch contract and chain config.""" + ChainConfigs.get(self.chain_type).rpc = self.rpc + if self.chain_type != ChainType.CUSTOM: + return + + for name, address in self.contracts.items(): + ContractConfigs.get(name=name).contracts[self.chain_type] = address + + @property + def safe(self) -> str: + """Get safe address.""" + chain_id = self.ledger_api.api.eth.chain_id + chain_type = OperateChainType.from_id(chain_id) + return self.wallet.safes[chain_type] + + @property + def crypto(self) -> Crypto: + """Load crypto object.""" + self._patch() + _, crypto = OnChainHelper.get_ledger_and_crypto_objects( + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + ) + return crypto + + @property + def ledger_api(self) -> LedgerApi: + """Load ledger api object.""" + self._patch() + ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + ) + return ledger_api + + @property + def service_manager_instance(self) -> Contract: + """Load service manager contract instance.""" + contract_interface = registry_contracts.service_manager.contract_interface.get(self.ledger_api.identifier, {}) + instance = self.ledger_api.get_contract_instance( + contract_interface, + self.contracts["service_manager"], + ) + return instance + + + def owner_of(self, token_id: int) -> str: + """Get owner of a service.""" + self._patch() + ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( + chain_type=self.chain_type + ) + + + def info(self, token_id: int) -> t.Dict: + """Get service info.""" + self._patch() + ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( + chain_type=self.chain_type + ) + ( + security_deposit, + multisig_address, + config_hash, + threshold, + max_agents, + number_of_agent_instances, + service_state, + canonical_agents, + ) = get_service_info( + ledger_api=ledger_api, + chain_type=self.chain_type, + token_id=token_id, + ) + instances = get_agent_instances( + ledger_api=ledger_api, + chain_type=self.chain_type, + token_id=token_id, + ).get("agentInstances", []) + return dict( + security_deposit=security_deposit, + multisig=multisig_address, + config_hash=config_hash.hex(), + threshold=threshold, + max_agents=max_agents, + number_of_agent_instances=number_of_agent_instances, + service_state=service_state, + canonical_agents=canonical_agents, + instances=instances, + ) + + + def get_service_safe_owners(self, service_id: int) -> t.List[str]: + """Get list of owners.""" + ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( + chain_type=self.chain_type + ) + ( + _, + multisig_address, + _, + _, + _, + _, + _, + _, + ) = get_service_info( + ledger_api=ledger_api, + chain_type=self.chain_type, + token_id=service_id, + ) + return registry_contracts.gnosis_safe.get_owners( + ledger_api=ledger_api, + contract_address=multisig_address, + ).get("owners", []) + + + def swap( # pylint: disable=too-many-arguments,too-many-locals + self, + service_id: int, + multisig: str, + owner_key: str, + new_owner_address: str + ) -> None: + """Swap safe owner.""" + logging.info(f"Swapping safe for service {service_id} [{multisig}]...") + self._patch() + manager = ServiceManager( + service_id=service_id, + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + with tempfile.TemporaryDirectory() as temp_dir: + key_file = Path(temp_dir, "key.txt") + key_file.write_text(owner_key, encoding="utf-8") + owner_crypto = EthereumCrypto(private_key_path=str(key_file)) + owner_cryptos: t.List[EthereumCrypto] = [owner_crypto] + owners = [ + manager.ledger_api.api.to_checksum_address(owner_crypto.address) + for owner_crypto in owner_cryptos + ] + owner_to_swap = owners[0] + multisend_txs = [] + txd = registry_contracts.gnosis_safe.get_swap_owner_data( + ledger_api=manager.ledger_api, + contract_address=multisig, + old_owner=manager.ledger_api.api.to_checksum_address(owner_to_swap), + new_owner=manager.ledger_api.api.to_checksum_address( + new_owner_address + ), + ).get("data") + multisend_txs.append( + { + "operation": MultiSendOperation.CALL, + "to": multisig, + "value": 0, + "data": HexBytes(txd[2:]), + } + ) + multisend_txd = registry_contracts.multisend.get_tx_data( # type: ignore + ledger_api=manager.ledger_api, + contract_address=ContractConfigs.multisend.contracts[self.chain_type], + multi_send_txs=multisend_txs, + ).get("data") + multisend_data = bytes.fromhex(multisend_txd[2:]) + safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( + ledger_api=manager.ledger_api, + contract_address=multisig, + to_address=ContractConfigs.multisend.contracts[self.chain_type], + value=0, + data=multisend_data, + safe_tx_gas=0, + operation=SafeOperation.DELEGATE_CALL.value, + ).get("tx_hash")[2:] + payload_data = hash_payload_to_hex( + safe_tx_hash=safe_tx_hash, + ether_value=0, + safe_tx_gas=0, + to_address=ContractConfigs.multisend.contracts[self.chain_type], + data=multisend_data, + ) + tx_params = skill_input_hex_to_payload(payload=payload_data) + safe_tx_bytes = binascii.unhexlify(tx_params["safe_tx_hash"]) + owner_to_signature = {} + for owner_crypto in owner_cryptos: + signature = owner_crypto.sign_message( + message=safe_tx_bytes, + is_deprecated_mode=True, + ) + owner_to_signature[ + manager.ledger_api.api.to_checksum_address(owner_crypto.address) + ] = signature[2:] + tx = registry_contracts.gnosis_safe.get_raw_safe_transaction( + ledger_api=manager.ledger_api, + contract_address=multisig, + sender_address=owner_crypto.address, + owners=tuple(owners), # type: ignore + to_address=tx_params["to_address"], + value=tx_params["ether_value"], + data=tx_params["data"], + safe_tx_gas=tx_params["safe_tx_gas"], + signatures_by_owner=owner_to_signature, + operation=SafeOperation.DELEGATE_CALL.value, + ) + stx = owner_crypto.sign_transaction(tx) + tx_digest = manager.ledger_api.send_signed_transaction(stx) + receipt = manager.ledger_api.api.eth.wait_for_transaction_receipt(tx_digest) + if receipt["status"] != 1: + raise RuntimeError("Error swapping owners") + + def staking_slots_available(self, staking_contract: str) -> bool: + """Check if there are available slots on the staking contract""" + self._patch() + return StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).slots_available( + staking_contract=staking_contract, + ) + + def staking_rewards_available(self, staking_contract: str) -> bool: + """Check if there are available staking rewards on the staking contract""" + self._patch() + available_rewards = StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).available_rewards( + staking_contract=staking_contract, + ) + return available_rewards > 0 + +class OnChainManager(_ChainUtil): + """On chain service management.""" + + def mint( # pylint: disable=too-many-arguments,too-many-locals + self, + package_path: Path, + agent_id: int, + number_of_slots: int, + cost_of_bond: int, + threshold: int, + nft: Optional[Union[Path, IPFSHash]], + update_token: t.Optional[int] = None, + token: t.Optional[str] = None, + ) -> t.Dict: + """Mint service.""" + # TODO: Support for update + self._patch() + manager = MintManager( + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + update_token=update_token, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + + # Prepare for minting + ( + manager.load_package_configuration( + package_path=package_path, package_type=PackageType.SERVICE + ) + .load_metadata() + .verify_nft(nft=nft) + .verify_service_dependencies(agent_id=agent_id) + .publish_metadata() + ) + + with tempfile.TemporaryDirectory() as temp, contextlib.redirect_stdout( + io.StringIO() + ): + with cd(temp): + kwargs = dict( + number_of_slots=number_of_slots, + cost_of_bond=cost_of_bond, + threshold=threshold, + token=token, + ) + # TODO: Enable after consulting smart contracts team re a safe + # being a service owner + # if update_token is None: + # kwargs["owner"] = self.wallet.safe # noqa: F401 + method = ( + manager.mint_service + if update_token is None + else manager.update_service + ) + method(**kwargs) + (metadata,) = Path(temp).glob("*.json") + published = { + "token": int(Path(metadata).name.replace(".json", "")), + "metadata": json.loads(Path(metadata).read_text(encoding="utf-8")), + } + return published + + def activate( + self, + service_id: int, + token: t.Optional[str] = None, + ) -> None: + """Activate service.""" + logging.info(f"Activating service {service_id}...") + self._patch() + with contextlib.redirect_stdout(io.StringIO()): + ServiceManager( + service_id=service_id, + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ).check_is_service_token_secured( + token=token, + ).activate_service() + + def register( + self, + service_id: int, + instances: t.List[str], + agents: t.List[int], + token: t.Optional[str] = None, + ) -> None: + """Register instance.""" + logging.info(f"Registering service {service_id}...") + with contextlib.redirect_stdout(io.StringIO()): + ServiceManager( + service_id=service_id, + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ).check_is_service_token_secured( + token=token, + ).register_instance( + instances=instances, + agent_ids=agents, + ) + + def deploy( + self, + service_id: int, + reuse_multisig: bool = False, + token: t.Optional[str] = None, + ) -> None: + """Deploy service.""" + logging.info(f"Deploying service {service_id}...") + self._patch() + with contextlib.redirect_stdout(io.StringIO()): + ServiceManager( + service_id=service_id, + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ).check_is_service_token_secured( + token=token, + ).deploy_service( + reuse_multisig=reuse_multisig, + ) + + def terminate(self, service_id: int, token: t.Optional[str] = None) -> None: + """Terminate service.""" + logging.info(f"Terminating service {service_id}...") + self._patch() + with contextlib.redirect_stdout(io.StringIO()): + ServiceManager( + service_id=service_id, + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ).check_is_service_token_secured( + token=token, + ).terminate_service() + + def unbond(self, service_id: int, token: t.Optional[str] = None) -> None: + """Unbond service.""" + logging.info(f"Unbonding service {service_id}...") + self._patch() + with contextlib.redirect_stdout(io.StringIO()): + ServiceManager( + service_id=service_id, + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ).check_is_service_token_secured( + token=token, + ).unbond_service() + + def stake( + self, + service_id: int, + service_registry: str, + staking_contract: str, + ) -> None: + """Stake service.""" + self._patch() + StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).stake( + service_id=service_id, + service_registry=service_registry, + staking_contract=staking_contract, + ) + + def unstake(self, service_id: int, staking_contract: str) -> None: + """Unstake service.""" + self._patch() + StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).unstake( + service_id=service_id, + staking_contract=staking_contract, + ) + + def staking_status(self, service_id: int, staking_contract: str) -> StakingState: + """Stake the service""" + self._patch() + return StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).status( + service_id=service_id, + staking_contract=staking_contract, + ) + + +class EthSafeTxBuilder(_ChainUtil): + """Safe Transaction builder.""" + + def new_tx(self) -> GnosisSafeTransaction: + """Create a new GnosisSafeTransaction instance.""" + return GnosisSafeTransaction( + ledger_api=self.ledger_api, + crypto=self.crypto, + chain_type=self.chain_type, + safe=self.safe, + ) + + def get_mint_tx_data( # pylint: disable=too-many-arguments + self, + package_path: Path, + agent_id: int, + number_of_slots: int, + cost_of_bond: int, + threshold: int, + nft: Optional[Union[Path, IPFSHash]], + update_token: t.Optional[int] = None, + token: t.Optional[str] = None, + ) -> t.Dict: + """Build mint transaction.""" + # TODO: Support for update + self._patch() + manager = MintManager( + chain_type=self.chain_type, + key=self.wallet.key_path, + password=self.wallet.password, + update_token=update_token, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + # Prepare for minting + ( + manager.load_package_configuration( + package_path=package_path, package_type=PackageType.SERVICE + ) + .load_metadata() + .verify_nft(nft=nft) + #.verify_service_dependencies(agent_id=agent_id) # TODO add this check once subgraph production indexes agent 25 + .publish_metadata() + ) + + instance = self.service_manager_instance + if update_token is None: + safe = self.safe + txd = instance.encodeABI( + fn_name="create", + args=[ + safe, + token or ETHEREUM_ERC20, + manager.metadata_hash, + [agent_id], + [[number_of_slots, cost_of_bond]], + threshold, + ], + ) + else: + txd = instance.encodeABI( + fn_name="update", + args=[ + token or ETHEREUM_ERC20, + manager.metadata_hash, + [agent_id], + [[number_of_slots, cost_of_bond]], + threshold, + update_token + ], + ) + + return { + "to": self.contracts["service_manager"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_olas_approval_data( + self, + spender: str, + amount: int, + olas_contract: str, + ) -> t.Dict: + """Get activate tx data.""" + instance = registry_contracts.erc20.get_instance( + ledger_api=self.ledger_api, + contract_address=olas_contract, + ) + txd = instance.encodeABI( + fn_name="approve", + args=[spender, amount], + ) + return { + "to": olas_contract, + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_activate_data(self, service_id: int, cost_of_bond: int) -> t.Dict: + """Get activate tx data.""" + instance = self.service_manager_instance + txd = instance.encodeABI( + fn_name="activateRegistration", + args=[service_id], + ) + return { + "from": self.safe, + "to": self.contracts["service_manager"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": cost_of_bond, + } + + def get_register_instances_data( + self, + service_id: int, + instances: t.List[str], + agents: t.List[int], + cost_of_bond: int, + ) -> t.Dict: + """Get register instances tx data.""" + instance = self.service_manager_instance + txd = instance.encodeABI( + fn_name="registerAgents", + args=[ + service_id, + instances, + agents, + ], + ) + return { + "from": self.safe, + "to": self.contracts["service_manager"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": cost_of_bond, + } + + def get_deploy_data( + self, + service_id: int, + reuse_multisig: bool = False, + ) -> t.Dict: + """Get deploy tx data.""" + instance = self.service_manager_instance + if reuse_multisig: + _deployment_payload, error = get_reuse_multisig_payload( + ledger_api=self.ledger_api, + crypto=self.crypto, + chain_type=self.chain_type, + service_id=service_id, + ) + if _deployment_payload is None: + raise ValueError(error) + deployment_payload = _deployment_payload + gnosis_safe_multisig = ContractConfigs.get( + GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name + ).contracts[self.chain_type] + else: + deployment_payload = get_delployment_payload() + gnosis_safe_multisig = ContractConfigs.get( + GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name + ).contracts[self.chain_type] + + txd = instance.encodeABI( + fn_name="deploy", + args=[ + service_id, + gnosis_safe_multisig, + deployment_payload, + ], + ) + return { + "to": self.contracts["service_manager"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_deploy_data_from_safe( + self, + service_id: int, + master_safe: str, + reuse_multisig: bool = False, + ) -> t.List[t.Dict[str, t.Any]]: + """Get the deploy data instructions for a safe""" + registry_instance = self.service_manager_instance + approve_hash_message = None + if reuse_multisig: + _deployment_payload, approve_hash_message, error = get_reuse_multisig_from_safe_payload( + ledger_api=self.ledger_api, + chain_type=self.chain_type, + service_id=service_id, + master_safe=master_safe, + ) + if _deployment_payload is None: + raise ValueError(error) + deployment_payload = _deployment_payload + gnosis_safe_multisig = ContractConfigs.get( + GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name + ).contracts[self.chain_type] + else: + deployment_payload = get_delployment_payload() + gnosis_safe_multisig = ContractConfigs.get( + GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name + ).contracts[self.chain_type] + + deploy_data = registry_instance.encodeABI( + fn_name="deploy", + args=[ + service_id, + gnosis_safe_multisig, + deployment_payload, + ], + ) + deploy_message = { + "to": self.contracts["service_manager"], + "data": deploy_data[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + if approve_hash_message is None: + return [deploy_message] + return [approve_hash_message, deploy_message] + + def get_terminate_data(self, service_id: int) -> t.Dict: + """Get terminate tx data.""" + instance = self.service_manager_instance + txd = instance.encodeABI( + fn_name="terminate", + args=[service_id], + ) + return { + "to": self.contracts["service_manager"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_unbond_data(self, service_id: int) -> t.Dict: + """Get unbond tx data.""" + instance = self.service_manager_instance + txd = instance.encodeABI( + fn_name="unbond", + args=[service_id], + ) + return { + "to": self.contracts["service_manager"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_staking_approval_data( + self, + service_id: int, + service_registry: str, + staking_contract: str, + ) -> t.Dict: + """Get staking approval data""" + self._patch() + txd = StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).get_stake_approval_tx_data( + service_id=service_id, + service_registry=service_registry, + staking_contract=staking_contract, + ) + return { + "from": self.safe, + "to": self.contracts["service_registry"], + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_staking_data( + self, + service_id: int, + staking_contract: str, + ) -> t.Dict: + """Get staking tx data""" + self._patch() + txd = StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).get_stake_tx_data( + service_id=service_id, + staking_contract=staking_contract, + ) + return { + "to": staking_contract, + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def get_unstaking_data( + self, + service_id: int, + staking_contract: str, + ) -> t.Dict: + """Get unstaking tx data""" + self._patch() + txd = StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).get_unstake_tx_data( + service_id=service_id, + staking_contract=staking_contract, + ) + return { + "to": staking_contract, + "data": txd[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + def staking_slots_available(self, staking_contract: str) -> bool: + """Stake service.""" + self._patch() + return StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).slots_available( + staking_contract=staking_contract, + ) + + def staking_status(self, service_id: int, staking_contract: str) -> StakingState: + """Stake the service""" + self._patch() + return StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).status( + service_id=service_id, + staking_contract=staking_contract, + ) + + def get_staking_params(self, staking_contract: str) -> t.Dict: + """Get agent IDs for the staking contract""" + self._patch() + staking_manager = StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ) + agent_ids = staking_manager.agent_ids( + staking_contract=staking_contract, + ) + service_registry = staking_manager.service_registry( + staking_contract=staking_contract, + ) + staking_token = staking_manager.staking_token( + staking_contract=staking_contract, + ) + service_registry_token_utility = staking_manager.service_registry_token_utility( + staking_contract=staking_contract, + ) + min_staking_deposit = staking_manager.min_staking_deposit( + staking_contract=staking_contract, + ) + activity_checker = staking_manager.activity_checker( + staking_contract=staking_contract, + ) + + return dict( + agent_ids=agent_ids, + service_registry=service_registry, + staking_token=staking_token, + service_registry_token_utility=service_registry_token_utility, + min_staking_deposit=min_staking_deposit, + activity_checker=activity_checker + ) + + def can_unstake(self, service_id: int, staking_contract: str) -> bool: + """Can unstake the service?""" + try: + StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).check_if_unstaking_possible( + service_id=service_id, + staking_contract=staking_contract, + ) + return True + except ValueError: + return False + + def get_swap_data(self, service_id: int, multisig: str, owner_key: str) -> t.Dict: + """Swap safe owner.""" + # TODO: Discuss implementation + raise NotImplementedError() + + + +def get_packed_signature_for_approved_hash(owners: t.Tuple[str]) -> bytes: + """Get the packed signatures.""" + sorted_owners = sorted(owners, key=str.lower) + signatures = b'' + for owner in sorted_owners: + # Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros) + r_bytes = to_bytes(hexstr=owner[2:].rjust(64, '0')) + + # `s` as 32 zero bytes + s_bytes = b'\x00' * 32 + + # `v` as a single byte + v_bytes = to_bytes(1) + + # Concatenate r, s, and v to form the packed signature + packed_signature = r_bytes + s_bytes + v_bytes + signatures += packed_signature + + return signatures + + +def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals + ledger_api: LedgerApi, + chain_type: ChainType, + service_id: int, + master_safe: str, +) -> t.Tuple[Optional[str], Optional[t.Dict[str, t.Any]], Optional[str]]: + """Reuse multisig.""" + _, multisig_address, _, threshold, *_ = get_service_info( + ledger_api=ledger_api, + chain_type=chain_type, + token_id=service_id, + ) + if multisig_address == NULL_ADDRESS: + return None, None, "Cannot reuse multisig, No previous deployment exist!" + + multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[ + chain_type + ] + multisig_instance = registry_contracts.gnosis_safe.get_instance( + ledger_api=ledger_api, + contract_address=multisig_address, + ) + + # Verify if the service was terminated properly or not + old_owners = multisig_instance.functions.getOwners().call() + if len(old_owners) != 1 or master_safe not in old_owners: + return ( + None, + None, + "Service was not terminated properly, the service owner should be the only owner of the safe", + ) + + # Build multisend tx to add new instances as owners + txs = [] + new_owners = t.cast( + t.List[str], + get_agent_instances( + ledger_api=ledger_api, + chain_type=chain_type, + token_id=service_id, + ).get("agentInstances"), + ) + + for _owner in new_owners: + txs.append( + { + "to": multisig_address, + "data": HexBytes( + bytes.fromhex( + multisig_instance.encodeABI( + fn_name="addOwnerWithThreshold", + args=[_owner, 1], + )[2:] + ) + ), + "operation": MultiSendOperation.CALL, + "value": 0, + } + ) + + txs.append( + { + "to": multisig_address, + "data": HexBytes( + bytes.fromhex( + multisig_instance.encodeABI( + fn_name="removeOwner", + args=[new_owners[0], master_safe, 1], + )[2:] + ) + ), + "operation": MultiSendOperation.CALL, + "value": 0, + } + ) + + txs.append( + { + "to": multisig_address, + "data": HexBytes( + bytes.fromhex( + multisig_instance.encodeABI( + fn_name="changeThreshold", + args=[threshold], + )[2:] + ) + ), + "operation": MultiSendOperation.CALL, + "value": 0, + } + ) + + multisend_tx = registry_contracts.multisend.get_multisend_tx( + ledger_api=ledger_api, + contract_address=multisend_address, + txs=txs, + ) + signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe, )) + + safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( + ledger_api=ledger_api, + contract_address=multisig_address, + to_address=multisend_address, + value=multisend_tx["value"], + data=multisend_tx["data"], + operation=1, + ).get("tx_hash") + approve_hash_data = multisig_instance.encodeABI( + fn_name="approveHash", + args=[ + safe_tx_hash, + ], + ) + approve_hash_message = { + "to": multisig_address, + "data": approve_hash_data[2:], + "operation": MultiSendOperation.CALL, + "value": 0, + } + + safe_exec_data = multisig_instance.encodeABI( + fn_name="execTransaction", + args=[ + multisend_address, # to address + multisend_tx["value"], # value + multisend_tx["data"], # data + 1, # operation + 0, # safe tx gas + 0, # bas gas + 0, # safe gas price + NULL_ADDRESS, # gas token + NULL_ADDRESS, # refund receiver + signature_bytes, # signatures + ], + ) + payload = multisig_address + safe_exec_data[2:] + return payload, approve_hash_message, None + diff --git a/operate/utils/gnosis.py b/operate/utils/gnosis.py index 4619ff05..9285f291 100644 --- a/operate/utils/gnosis.py +++ b/operate/utils/gnosis.py @@ -1,352 +1,356 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2023 Valory AG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Safe helpers.""" - -import binascii -import secrets -import typing as t -from enum import Enum - -from aea.crypto.base import Crypto, LedgerApi -from autonomy.chain.base import registry_contracts -from autonomy.chain.config import ChainType as ChainProfile -from autonomy.chain.tx import TxSettler - -from operate.constants import ( - ON_CHAIN_INTERACT_RETRIES, - ON_CHAIN_INTERACT_SLEEP, - ON_CHAIN_INTERACT_TIMEOUT, -) - - -NULL_ADDRESS: str = "0x" + "0" * 40 -MAX_UINT256 = 2**256 - 1 -ZERO_ETH = 0 - - -class SafeOperation(Enum): - """Operation types.""" - - CALL = 0 - DELEGATE_CALL = 1 - CREATE = 2 - - -class MultiSendOperation(Enum): - """Operation types.""" - - CALL = 0 - DELEGATE_CALL = 1 - - -def hash_payload_to_hex( # pylint: disable=too-many-arguments,too-many-locals - safe_tx_hash: str, - ether_value: int, - safe_tx_gas: int, - to_address: str, - data: bytes, - operation: int = SafeOperation.CALL.value, - base_gas: int = 0, - safe_gas_price: int = 0, - gas_token: str = NULL_ADDRESS, - refund_receiver: str = NULL_ADDRESS, - use_flashbots: bool = False, - gas_limit: int = 0, - raise_on_failed_simulation: bool = False, -) -> str: - """Serialise to a hex string.""" - if len(safe_tx_hash) != 64: # should be exactly 32 bytes! - raise ValueError( - "cannot encode safe_tx_hash of non-32 bytes" - ) # pragma: nocover - - if len(to_address) != 42 or len(gas_token) != 42 or len(refund_receiver) != 42: - raise ValueError("cannot encode address of non 42 length") # pragma: nocover - - if ( - ether_value > MAX_UINT256 - or safe_tx_gas > MAX_UINT256 - or base_gas > MAX_UINT256 - or safe_gas_price > MAX_UINT256 - or gas_limit > MAX_UINT256 - ): - raise ValueError( - "Value is bigger than the max 256 bit value" - ) # pragma: nocover - - if operation not in [v.value for v in SafeOperation]: - raise ValueError("SafeOperation value is not valid") # pragma: nocover - - if not isinstance(use_flashbots, bool): - raise ValueError( - f"`use_flashbots` value ({use_flashbots}) is not valid. A boolean value was expected instead" - ) - - ether_value_ = ether_value.to_bytes(32, "big").hex() - safe_tx_gas_ = safe_tx_gas.to_bytes(32, "big").hex() - operation_ = operation.to_bytes(1, "big").hex() - base_gas_ = base_gas.to_bytes(32, "big").hex() - safe_gas_price_ = safe_gas_price.to_bytes(32, "big").hex() - use_flashbots_ = use_flashbots.to_bytes(32, "big").hex() - gas_limit_ = gas_limit.to_bytes(32, "big").hex() - raise_on_failed_simulation_ = raise_on_failed_simulation.to_bytes(32, "big").hex() - - concatenated = ( - safe_tx_hash - + ether_value_ - + safe_tx_gas_ - + to_address - + operation_ - + base_gas_ - + safe_gas_price_ - + gas_token - + refund_receiver - + use_flashbots_ - + gas_limit_ - + raise_on_failed_simulation_ - + data.hex() - ) - return concatenated - - -def skill_input_hex_to_payload(payload: str) -> dict: - """Decode payload.""" - tx_params = dict( - safe_tx_hash=payload[:64], - ether_value=int.from_bytes(bytes.fromhex(payload[64:128]), "big"), - safe_tx_gas=int.from_bytes(bytes.fromhex(payload[128:192]), "big"), - to_address=payload[192:234], - operation=int.from_bytes(bytes.fromhex(payload[234:236]), "big"), - base_gas=int.from_bytes(bytes.fromhex(payload[236:300]), "big"), - safe_gas_price=int.from_bytes(bytes.fromhex(payload[300:364]), "big"), - gas_token=payload[364:406], - refund_receiver=payload[406:448], - use_flashbots=bool.from_bytes(bytes.fromhex(payload[448:512]), "big"), - gas_limit=int.from_bytes(bytes.fromhex(payload[512:576]), "big"), - raise_on_failed_simulation=bool.from_bytes( - bytes.fromhex(payload[576:640]), "big" - ), - data=bytes.fromhex(payload[640:]), - ) - return tx_params - - -def _get_nonce() -> int: - """Generate a nonce for the Safe deployment.""" - return secrets.SystemRandom().randint(0, 2**256 - 1) - - -def create_safe( - ledger_api: LedgerApi, - crypto: Crypto, - owner: t.Optional[str] = None, - salt_nonce: t.Optional[int] = None, -) -> t.Tuple[str, int]: - """Create gnosis safe.""" - salt_nonce = salt_nonce or _get_nonce() - - def _build( # pylint: disable=unused-argument - *args: t.Any, **kwargs: t.Any - ) -> t.Dict: - tx = registry_contracts.gnosis_safe.get_deploy_transaction( - ledger_api=ledger_api, - deployer_address=crypto.address, - owners=[crypto.address] if owner is None else [crypto.address, owner], - threshold=1, - salt_nonce=salt_nonce, - ) - del tx["contract_address"] - return tx - - tx_settler = TxSettler( - ledger_api=ledger_api, - crypto=crypto, - chain_type=ChainProfile.CUSTOM, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - setattr( # noqa: B010 - tx_settler, - "build", - _build, - ) - receipt = tx_settler.transact( - method=lambda: {}, - contract="", - kwargs={}, - ) - instance = registry_contracts.gnosis_safe_proxy_factory.get_instance( - ledger_api=ledger_api, - contract_address="0xa6b71e26c5e0845f74c812102ca7114b6a896ab2", - ) - (event,) = instance.events.ProxyCreation().process_receipt(receipt) - return event["args"]["proxy"], salt_nonce - - -def get_owners(ledger_api: LedgerApi, safe: str) -> t.List[str]: - """Get list of owners.""" - return registry_contracts.gnosis_safe.get_owners( - ledger_api=ledger_api, - contract_address=safe, - ).get("owners", []) - - -def send_safe_txs( - txd: bytes, - safe: str, - ledger_api: LedgerApi, - crypto: Crypto, -) -> None: - """Send internal safe transaction.""" - owner = ledger_api.api.to_checksum_address( - crypto.address, - ) - safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( - ledger_api=ledger_api, - contract_address=safe, - value=0, - safe_tx_gas=0, - to_address=safe, - data=txd, - operation=SafeOperation.CALL.value, - ).get("tx_hash") - safe_tx_bytes = binascii.unhexlify( - safe_tx_hash[2:], - ) - signatures = { - owner: crypto.sign_message( - message=safe_tx_bytes, - is_deprecated_mode=True, - )[2:] - } - transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( - ledger_api=ledger_api, - contract_address=safe, - sender_address=owner, - owners=(owner,), # type: ignore - to_address=safe, - value=0, - data=txd, - safe_tx_gas=0, - signatures_by_owner=signatures, - operation=SafeOperation.CALL.value, - nonce=ledger_api.api.eth.get_transaction_count(owner), -<<<<<<< HEAD -======= - max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, - max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None ->>>>>>> 631b8c7eab6677c90ffccf3ae775d7a282b3cfb8 - ) - ledger_api.get_transaction_receipt( - ledger_api.send_signed_transaction( - crypto.sign_transaction( - transaction, - ), - ) - ) - - -def add_owner( - ledger_api: LedgerApi, - crypto: Crypto, - safe: str, - owner: str, -) -> None: - """Add owner to a safe.""" - instance = registry_contracts.gnosis_safe.get_instance( - ledger_api=ledger_api, - contract_address=safe, - ) - txd = instance.encodeABI( - fn_name="addOwnerWithThreshold", - args=[ - owner, - 1, - ], - ) - send_safe_txs( - txd=bytes.fromhex(txd[2:]), - safe=safe, - ledger_api=ledger_api, - crypto=crypto, - ) - - -def swap_owner( # pylint: disable=unused-argument - ledger_api: LedgerApi, - crypto: Crypto, - safe: str, - old_owner: str, - new_owner: str, -) -> None: - """Swap owner on a safe.""" - - -def transfer( - ledger_api: LedgerApi, - crypto: Crypto, - safe: str, - to: str, - amount: t.Union[float, int], -) -> None: - """Transfer assets from safe to given address.""" - amount = int(amount) - owner = ledger_api.api.to_checksum_address( - crypto.address, - ) - safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( - ledger_api=ledger_api, - contract_address=safe, - value=amount, - safe_tx_gas=0, - to_address=to, - data=b"", - operation=SafeOperation.CALL.value, - ).get("tx_hash") - safe_tx_bytes = binascii.unhexlify( - safe_tx_hash[2:], - ) - signatures = { - owner: crypto.sign_message( - message=safe_tx_bytes, - is_deprecated_mode=True, - )[2:] - } - transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( - ledger_api=ledger_api, - contract_address=safe, - sender_address=owner, - owners=(owner,), # type: ignore - to_address=to, - value=amount, - data=b"", - safe_tx_gas=0, - signatures_by_owner=signatures, - operation=SafeOperation.CALL.value, - nonce=ledger_api.api.eth.get_transaction_count(owner), - ) - ledger_api.get_transaction_receipt( - ledger_api.send_signed_transaction( - crypto.sign_transaction( - transaction, - ), - ) - ) +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Safe helpers.""" + +import binascii +import os +import secrets +import typing as t +from enum import Enum + +from aea.crypto.base import Crypto, LedgerApi +from autonomy.chain.base import registry_contracts +from autonomy.chain.config import ChainType as ChainProfile +from autonomy.chain.tx import TxSettler + +from operate.constants import ( + ON_CHAIN_INTERACT_RETRIES, + ON_CHAIN_INTERACT_SLEEP, + ON_CHAIN_INTERACT_TIMEOUT, +) + + +NULL_ADDRESS: str = "0x" + "0" * 40 +MAX_UINT256 = 2**256 - 1 +ZERO_ETH = 0 + + +class SafeOperation(Enum): + """Operation types.""" + + CALL = 0 + DELEGATE_CALL = 1 + CREATE = 2 + + +class MultiSendOperation(Enum): + """Operation types.""" + + CALL = 0 + DELEGATE_CALL = 1 + + +def hash_payload_to_hex( # pylint: disable=too-many-arguments,too-many-locals + safe_tx_hash: str, + ether_value: int, + safe_tx_gas: int, + to_address: str, + data: bytes, + operation: int = SafeOperation.CALL.value, + base_gas: int = 0, + safe_gas_price: int = 0, + gas_token: str = NULL_ADDRESS, + refund_receiver: str = NULL_ADDRESS, + use_flashbots: bool = False, + gas_limit: int = 0, + raise_on_failed_simulation: bool = False, +) -> str: + """Serialise to a hex string.""" + if len(safe_tx_hash) != 64: # should be exactly 32 bytes! + raise ValueError( + "cannot encode safe_tx_hash of non-32 bytes" + ) # pragma: nocover + + if len(to_address) != 42 or len(gas_token) != 42 or len(refund_receiver) != 42: + raise ValueError("cannot encode address of non 42 length") # pragma: nocover + + if ( + ether_value > MAX_UINT256 + or safe_tx_gas > MAX_UINT256 + or base_gas > MAX_UINT256 + or safe_gas_price > MAX_UINT256 + or gas_limit > MAX_UINT256 + ): + raise ValueError( + "Value is bigger than the max 256 bit value" + ) # pragma: nocover + + if operation not in [v.value for v in SafeOperation]: + raise ValueError("SafeOperation value is not valid") # pragma: nocover + + if not isinstance(use_flashbots, bool): + raise ValueError( + f"`use_flashbots` value ({use_flashbots}) is not valid. A boolean value was expected instead" + ) + + ether_value_ = ether_value.to_bytes(32, "big").hex() + safe_tx_gas_ = safe_tx_gas.to_bytes(32, "big").hex() + operation_ = operation.to_bytes(1, "big").hex() + base_gas_ = base_gas.to_bytes(32, "big").hex() + safe_gas_price_ = safe_gas_price.to_bytes(32, "big").hex() + use_flashbots_ = use_flashbots.to_bytes(32, "big").hex() + gas_limit_ = gas_limit.to_bytes(32, "big").hex() + raise_on_failed_simulation_ = raise_on_failed_simulation.to_bytes(32, "big").hex() + + concatenated = ( + safe_tx_hash + + ether_value_ + + safe_tx_gas_ + + to_address + + operation_ + + base_gas_ + + safe_gas_price_ + + gas_token + + refund_receiver + + use_flashbots_ + + gas_limit_ + + raise_on_failed_simulation_ + + data.hex() + ) + return concatenated + + +def skill_input_hex_to_payload(payload: str) -> dict: + """Decode payload.""" + tx_params = dict( + safe_tx_hash=payload[:64], + ether_value=int.from_bytes(bytes.fromhex(payload[64:128]), "big"), + safe_tx_gas=int.from_bytes(bytes.fromhex(payload[128:192]), "big"), + to_address=payload[192:234], + operation=int.from_bytes(bytes.fromhex(payload[234:236]), "big"), + base_gas=int.from_bytes(bytes.fromhex(payload[236:300]), "big"), + safe_gas_price=int.from_bytes(bytes.fromhex(payload[300:364]), "big"), + gas_token=payload[364:406], + refund_receiver=payload[406:448], + use_flashbots=bool.from_bytes(bytes.fromhex(payload[448:512]), "big"), + gas_limit=int.from_bytes(bytes.fromhex(payload[512:576]), "big"), + raise_on_failed_simulation=bool.from_bytes( + bytes.fromhex(payload[576:640]), "big" + ), + data=bytes.fromhex(payload[640:]), + ) + return tx_params + + +def _get_nonce() -> int: + """Generate a nonce for the Safe deployment.""" + return secrets.SystemRandom().randint(0, 2**256 - 1) + + +def create_safe( + ledger_api: LedgerApi, + crypto: Crypto, + owner: t.Optional[str] = None, + salt_nonce: t.Optional[int] = None, +) -> t.Tuple[str, int]: + """Create gnosis safe.""" + salt_nonce = salt_nonce or _get_nonce() + + def _build( # pylint: disable=unused-argument + *args: t.Any, **kwargs: t.Any + ) -> t.Dict: + tx = registry_contracts.gnosis_safe.get_deploy_transaction( + ledger_api=ledger_api, + deployer_address=crypto.address, + owners=[crypto.address] if owner is None else [crypto.address, owner], + threshold=1, + salt_nonce=salt_nonce, + ) + del tx["contract_address"] + return tx + + tx_settler = TxSettler( + ledger_api=ledger_api, + crypto=crypto, + chain_type=ChainProfile.CUSTOM, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + setattr( # noqa: B010 + tx_settler, + "build", + _build, + ) + receipt = tx_settler.transact( + method=lambda: {}, + contract="", + kwargs={}, + ) + instance = registry_contracts.gnosis_safe_proxy_factory.get_instance( + ledger_api=ledger_api, + contract_address="0xa6b71e26c5e0845f74c812102ca7114b6a896ab2", + ) + (event,) = instance.events.ProxyCreation().process_receipt(receipt) + return event["args"]["proxy"], salt_nonce + + +def get_owners(ledger_api: LedgerApi, safe: str) -> t.List[str]: + """Get list of owners.""" + return registry_contracts.gnosis_safe.get_owners( + ledger_api=ledger_api, + contract_address=safe, + ).get("owners", []) + + +def send_safe_txs( + txd: bytes, + safe: str, + ledger_api: LedgerApi, + crypto: Crypto, +) -> None: + """Send internal safe transaction.""" + owner = ledger_api.api.to_checksum_address( + crypto.address, + ) + safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( + ledger_api=ledger_api, + contract_address=safe, + value=0, + safe_tx_gas=0, + to_address=safe, + data=txd, + operation=SafeOperation.CALL.value, + ).get("tx_hash") + safe_tx_bytes = binascii.unhexlify( + safe_tx_hash[2:], + ) + signatures = { + owner: crypto.sign_message( + message=safe_tx_bytes, + is_deprecated_mode=True, + )[2:] + } + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) + transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( + ledger_api=ledger_api, + contract_address=safe, + sender_address=owner, + owners=(owner,), # type: ignore + to_address=safe, + value=0, + data=txd, + safe_tx_gas=0, + signatures_by_owner=signatures, + operation=SafeOperation.CALL.value, + nonce=ledger_api.api.eth.get_transaction_count(owner), + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None + ) + ledger_api.get_transaction_receipt( + ledger_api.send_signed_transaction( + crypto.sign_transaction( + transaction, + ), + ) + ) + + +def add_owner( + ledger_api: LedgerApi, + crypto: Crypto, + safe: str, + owner: str, +) -> None: + """Add owner to a safe.""" + instance = registry_contracts.gnosis_safe.get_instance( + ledger_api=ledger_api, + contract_address=safe, + ) + txd = instance.encodeABI( + fn_name="addOwnerWithThreshold", + args=[ + owner, + 1, + ], + ) + send_safe_txs( + txd=bytes.fromhex(txd[2:]), + safe=safe, + ledger_api=ledger_api, + crypto=crypto, + ) + + +def swap_owner( # pylint: disable=unused-argument + ledger_api: LedgerApi, + crypto: Crypto, + safe: str, + old_owner: str, + new_owner: str, +) -> None: + """Swap owner on a safe.""" + + +def transfer( + ledger_api: LedgerApi, + crypto: Crypto, + safe: str, + to: str, + amount: t.Union[float, int], +) -> None: + """Transfer assets from safe to given address.""" + amount = int(amount) + owner = ledger_api.api.to_checksum_address( + crypto.address, + ) + safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( + ledger_api=ledger_api, + contract_address=safe, + value=amount, + safe_tx_gas=0, + to_address=to, + data=b"", + operation=SafeOperation.CALL.value, + ).get("tx_hash") + safe_tx_bytes = binascii.unhexlify( + safe_tx_hash[2:], + ) + signatures = { + owner: crypto.sign_message( + message=safe_tx_bytes, + is_deprecated_mode=True, + )[2:] + } + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) + transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction( + ledger_api=ledger_api, + contract_address=safe, + sender_address=owner, + owners=(owner,), # type: ignore + to_address=to, + value=amount, + data=b"", + safe_tx_gas=0, + signatures_by_owner=signatures, + operation=SafeOperation.CALL.value, + nonce=ledger_api.api.eth.get_transaction_count(owner), + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, + ) + ledger_api.get_transaction_receipt( + ledger_api.send_signed_transaction( + crypto.sign_transaction( + transaction, + ), + ) + ) diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 3b8be2af..65b0e85a 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -1,436 +1,441 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2023 Valory AG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Master key implementation""" - -import json -import typing as t -from dataclasses import dataclass, field -from pathlib import Path - -from aea.crypto.base import Crypto, LedgerApi -from aea.crypto.registries import make_ledger_api -from aea_ledger_ethereum.ethereum import EthereumApi, EthereumCrypto -from autonomy.chain.config import ChainType as ChainProfile -from autonomy.chain.tx import TxSettler -from web3 import Account - -from operate.constants import ( - ON_CHAIN_INTERACT_RETRIES, - ON_CHAIN_INTERACT_SLEEP, - ON_CHAIN_INTERACT_TIMEOUT, -) -from operate.ledger import get_default_rpc -from operate.resource import LocalResource -from operate.types import ChainType, LedgerType -from operate.utils.gnosis import add_owner -from operate.utils.gnosis import create_safe as create_gnosis_safe -from operate.utils.gnosis import get_owners, swap_owner -from operate.utils.gnosis import transfer as transfer_from_safe - - -class MasterWallet(LocalResource): - """Master wallet.""" - - path: Path - safes: t.Optional[t.Dict[ChainType, str]] = {} - ledger_type: LedgerType - - _key: str - _crypto: t.Optional[Crypto] = None - _password: t.Optional[str] = None - _crypto_cls: t.Type[Crypto] - - @property - def password(self) -> str: - """Password string.""" - if self._password is None: - raise ValueError("Password not set.") - return self._password - - @password.setter - def password(self, value: str) -> None: - """Set password value.""" - self._password = value - - @property - def crypto(self) -> Crypto: - """Load crypto object.""" - if self._crypto is None: - self._crypto = self._crypto_cls(self.path / self._key, self.password) - return self._crypto - - @property - def key_path(self) -> Path: - """Key path.""" - return self.path / self._key - - def ledger_api( - self, - chain_type: ChainType, - rpc: t.Optional[str] = None, - ) -> LedgerApi: - """Get ledger api object.""" - return make_ledger_api( - self.ledger_type.name.lower(), - address=(rpc or get_default_rpc(chain=chain_type)), - chain_id=chain_type.id, - ) - - def transfer( - self, - to: str, - amount: int, - chain_type: ChainType, - from_safe: bool = True, - rpc: t.Optional[str] = None, - ) -> None: - """Transfer funds to the given account.""" - raise NotImplementedError() - - @staticmethod - def new(password: str, path: Path) -> t.Tuple["MasterWallet", t.List[str]]: - """Create a new master wallet.""" - raise NotImplementedError() - - def create_safe( - self, - chain_type: ChainType, - owner: t.Optional[str] = None, - rpc: t.Optional[str] = None, - ) -> None: - """Create safe.""" - raise NotImplementedError() - - def add_backup_owner( - self, - chain_type: ChainType, - owner: str, - rpc: t.Optional[str] = None, - ) -> None: - """Create safe.""" - raise NotImplementedError() - - def swap_backup_owner( - self, - chain_type: ChainType, - old_owner: str, - new_owner: str, - rpc: t.Optional[str] = None, - ) -> None: - """Create safe.""" - raise NotImplementedError() - - def add_or_swap_owner( - self, - chain_type: ChainType, - owner: str, - rpc: t.Optional[str] = None, - ) -> None: - """Add or swap backup owner.""" - raise NotImplementedError() - - -@dataclass -class EthereumMasterWallet(MasterWallet): - """Master wallet manager.""" - - path: Path - address: str - safe_chains: t.List[ChainType] # For cross-chain support - - ledger_type: LedgerType = LedgerType.ETHEREUM - safes: t.Optional[t.Dict[ChainType, str]] = field(default_factory=dict) - safe_nonce: t.Optional[int] = None # For cross-chain reusability - - _file = ledger_type.config_file - _key = ledger_type.key_file - _crypto_cls = EthereumCrypto - - def _transfer_from_eoa(self, to: str, amount: int, chain_type: ChainType, rpc: t.Optional[str] = None) -> None: - """Transfer funds from EOA wallet.""" - ledger_api = t.cast(EthereumApi, self.ledger_api(chain_type=chain_type, rpc=rpc)) - tx_helper = TxSettler( - ledger_api=ledger_api, - crypto=self.crypto, - chain_type=ChainProfile.CUSTOM, - timeout=ON_CHAIN_INTERACT_TIMEOUT, - retries=ON_CHAIN_INTERACT_RETRIES, - sleep=ON_CHAIN_INTERACT_SLEEP, - ) - - def _build_tx( # pylint: disable=unused-argument - *args: t.Any, **kwargs: t.Any - ) -> t.Dict: - """Build transaction""" - tx = ledger_api.get_transfer_transaction( - sender_address=self.crypto.address, - destination_address=to, - amount=amount, - tx_fee=50000, - tx_nonce="0x", - chain_id=chain_type.id, - raise_on_try=True, - ) - return ledger_api.update_with_gas_estimate( - transaction=tx, - raise_on_try=True, - ) - - setattr(tx_helper, "build", _build_tx) # noqa: B010 - tx_helper.transact(lambda x: x, "", kwargs={}) - - def _transfer_from_safe(self, to: str, amount: int, chain_type: ChainType, rpc: t.Optional[str] = None) -> None: - """Transfer funds from safe wallet.""" - transfer_from_safe( - ledger_api=self.ledger_api(chain_type=chain_type, rpc=rpc), - crypto=self.crypto, - safe=t.cast(str, self.safes[chain_type]), - to=to, - amount=amount, - ) - - def transfer( - self, - to: str, - amount: int, - chain_type: ChainType, - from_safe: bool = True, - rpc: t.Optional[str] = None, - ) -> None: - """Transfer funds to the given account.""" - if from_safe: - return self._transfer_from_safe( - to=to, - amount=amount, - chain_type=chain_type, - rpc=rpc, - ) - return self._transfer_from_eoa( - to=to, - amount=amount, - chain_type=chain_type, - rpc=rpc, - ) - - @classmethod - def new( - cls, password: str, path: Path - ) -> t.Tuple["EthereumMasterWallet", t.List[str]]: - """Create a new master wallet.""" - # Backport support on aea - account = Account() - account.enable_unaudited_hdwallet_features() - crypto, mnemonic = account.create_with_mnemonic() - (path / cls._key).write_text( - data=json.dumps( - Account.encrypt( - private_key=crypto._private_key, # pylint: disable=protected-access - password=password, - ), - indent=2, - ), - encoding="utf-8", - ) - - # Create wallet - wallet = EthereumMasterWallet(path=path, address=crypto.address, safe_chains=[]) - wallet.store() - wallet.password = password - return wallet, mnemonic.split() - - def create_safe( - self, - chain_type: ChainType, - owner: t.Optional[str] = None, - rpc: t.Optional[str] = None, - ) -> None: - """Create safe.""" - if chain_type in self.safe_chains: - return - safe, self.safe_nonce = create_gnosis_safe( - ledger_api=self.ledger_api(chain_type=chain_type, rpc=rpc), - crypto=self.crypto, - owner=owner, - salt_nonce=self.safe_nonce, - ) - self.safe_chains.append(chain_type) - if self.safes is None: - self.safes = {} - self.safes[chain_type] = safe - self.store() - - def add_backup_owner( - self, - chain_type: ChainType, - owner: str, - rpc: t.Optional[str] = None, - ) -> None: - """Add a backup owner.""" - ledger_api = self.ledger_api(chain_type=chain_type, rpc=rpc) - if chain_type not in self.safes: - raise ValueError(f"Safes not created for chain_type {chain_type}!") - safe = t.cast(str, self.safes[chain_type]) - if len(get_owners(ledger_api=ledger_api, safe=safe)) == 2: - raise ValueError("Backup owner already exist!") - add_owner( - ledger_api=ledger_api, - safe=safe, - owner=owner, - crypto=self.crypto, - ) - - def swap_backup_owner( - self, - chain_type: ChainType, - old_owner: str, - new_owner: str, - rpc: t.Optional[str] = None, - ) -> None: - """Swap backup owner.""" - ledger_api = self.ledger_api(chain_type=chain_type, rpc=rpc) - if chain_type not in self.safes: - raise ValueError(f"Safes not created for chain_type {chain_type}!") - safe = t.cast(str, self.safes[chain_type]) - if len(get_owners(ledger_api=ledger_api, safe=safe)) == 1: - raise ValueError("Backup owner does not exist, cannot swap!") - swap_owner( - ledger_api=ledger_api, - safe=safe, - old_owner=old_owner, - new_owner=new_owner, - crypto=self.crypto, - ) - - def add_or_swap_owner( - self, - chain_type: ChainType, - owner: str, - rpc: t.Optional[str] = None, - ) -> None: - """Add or swap backup owner.""" - ledger_api = self.ledger_api(chain_type=chain_type, rpc=rpc) - if chain_type not in self.safes: - raise ValueError(f"Safes not created for chain_type {chain_type}!") - safe = t.cast(str, self.safes[chain_type]) - owners = get_owners(ledger_api=ledger_api, safe=safe) - if len(owners) == 1: - return self.add_backup_owner(chain_type=chain_type, owner=owner, rpc=rpc) - - owners.remove(self.address) - (old_owner,) = owners - if old_owner == owner: - return None - - return self.swap_backup_owner( - chain_type=chain_type, - old_owner=old_owner, - new_owner=owner, - rpc=rpc, - ) - - @classmethod - def load(cls, path: Path) -> "EthereumMasterWallet": - """Load master wallet.""" - raw_ethereum_wallet = super().load(path) # type: ignore - safes = {} - for id_, safe_address in raw_ethereum_wallet.safes.items(): - safes[ChainType(int(id_))] = safe_address - - raw_ethereum_wallet.safes = safes - return t.cast(EthereumMasterWallet, raw_ethereum_wallet) - - -LEDGER_TYPE_TO_WALLET_CLASS = { - LedgerType.ETHEREUM: EthereumMasterWallet, -} - - -class MasterWalletManager: - """Master wallet manager.""" - - def __init__(self, path: Path, password: t.Optional[str] = None) -> None: - """Initialize master wallet manager.""" - self.path = path - self._password = password - - @property - def json(self) -> t.List[t.Dict]: - """List of wallets""" - return [wallet.json for wallet in self] - - @property - def password(self) -> str: - """Password string.""" - if self._password is None: - raise ValueError("Password not set.") - return self._password - - @password.setter - def password(self, value: str) -> None: - """Set password value.""" - self._password = value - - def setup(self) -> "MasterWalletManager": - """Setup wallet manager.""" - self.path.mkdir(exist_ok=True) - return self - - def create(self, ledger_type: LedgerType) -> t.Tuple[MasterWallet, t.List[str]]: - """ - Create a master wallet - - :param ledger_type: Ledger type for the wallet. - :return: Tuple of master wallet and mnemonic - """ - if ledger_type == LedgerType.ETHEREUM: - return EthereumMasterWallet.new(password=self.password, path=self.path) - raise ValueError(f"{ledger_type} is not supported.") - - def exists(self, ledger_type: LedgerType) -> bool: - """ - Check if a wallet exists or not - - :param ledger_type: Ledger type for the wallet. - :return: True if wallet exists, False otherwise. - """ - return (self.path / ledger_type.config_file).exists() and ( - self.path / ledger_type.key_file - ).exists() - - def load(self, ledger_type: LedgerType) -> MasterWallet: - """ - Load master wallet - - :param ledger_type: Ledger type for the wallet. - :return: Master wallet object - """ - if ledger_type == LedgerType.ETHEREUM: - wallet = EthereumMasterWallet.load(path=self.path) - else: - raise ValueError(f"{ledger_type} is not supported.") - wallet.password = self.password - return wallet - - def __iter__(self) -> t.Iterator[MasterWallet]: - """Iterate over master wallets.""" - for ledger_type in LedgerType: - if not self.exists(ledger_type=ledger_type): - continue - yield LEDGER_TYPE_TO_WALLET_CLASS[ledger_type].load(path=self.path) +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Master key implementation""" + +import json +import os +import typing as t +from dataclasses import dataclass, field +from pathlib import Path + +from aea.crypto.base import Crypto, LedgerApi +from aea.crypto.registries import make_ledger_api +from aea_ledger_ethereum.ethereum import EthereumApi, EthereumCrypto +from autonomy.chain.config import ChainType as ChainProfile +from autonomy.chain.tx import TxSettler +from web3 import Account + +from operate.constants import ( + ON_CHAIN_INTERACT_RETRIES, + ON_CHAIN_INTERACT_SLEEP, + ON_CHAIN_INTERACT_TIMEOUT, +) +from operate.ledger import get_default_rpc +from operate.resource import LocalResource +from operate.types import ChainType, LedgerType +from operate.utils.gnosis import add_owner +from operate.utils.gnosis import create_safe as create_gnosis_safe +from operate.utils.gnosis import get_owners, swap_owner +from operate.utils.gnosis import transfer as transfer_from_safe + + +class MasterWallet(LocalResource): + """Master wallet.""" + + path: Path + safes: t.Optional[t.Dict[ChainType, str]] = {} + ledger_type: LedgerType + + _key: str + _crypto: t.Optional[Crypto] = None + _password: t.Optional[str] = None + _crypto_cls: t.Type[Crypto] + + @property + def password(self) -> str: + """Password string.""" + if self._password is None: + raise ValueError("Password not set.") + return self._password + + @password.setter + def password(self, value: str) -> None: + """Set password value.""" + self._password = value + + @property + def crypto(self) -> Crypto: + """Load crypto object.""" + if self._crypto is None: + self._crypto = self._crypto_cls(self.path / self._key, self.password) + return self._crypto + + @property + def key_path(self) -> Path: + """Key path.""" + return self.path / self._key + + def ledger_api( + self, + chain_type: ChainType, + rpc: t.Optional[str] = None, + ) -> LedgerApi: + """Get ledger api object.""" + return make_ledger_api( + self.ledger_type.name.lower(), + address=(rpc or get_default_rpc(chain=chain_type)), + chain_id=chain_type.id, + ) + + def transfer( + self, + to: str, + amount: int, + chain_type: ChainType, + from_safe: bool = True, + rpc: t.Optional[str] = None, + ) -> None: + """Transfer funds to the given account.""" + raise NotImplementedError() + + @staticmethod + def new(password: str, path: Path) -> t.Tuple["MasterWallet", t.List[str]]: + """Create a new master wallet.""" + raise NotImplementedError() + + def create_safe( + self, + chain_type: ChainType, + owner: t.Optional[str] = None, + rpc: t.Optional[str] = None, + ) -> None: + """Create safe.""" + raise NotImplementedError() + + def add_backup_owner( + self, + chain_type: ChainType, + owner: str, + rpc: t.Optional[str] = None, + ) -> None: + """Create safe.""" + raise NotImplementedError() + + def swap_backup_owner( + self, + chain_type: ChainType, + old_owner: str, + new_owner: str, + rpc: t.Optional[str] = None, + ) -> None: + """Create safe.""" + raise NotImplementedError() + + def add_or_swap_owner( + self, + chain_type: ChainType, + owner: str, + rpc: t.Optional[str] = None, + ) -> None: + """Add or swap backup owner.""" + raise NotImplementedError() + + +@dataclass +class EthereumMasterWallet(MasterWallet): + """Master wallet manager.""" + + path: Path + address: str + safe_chains: t.List[ChainType] # For cross-chain support + + ledger_type: LedgerType = LedgerType.ETHEREUM + safes: t.Optional[t.Dict[ChainType, str]] = field(default_factory=dict) + safe_nonce: t.Optional[int] = None # For cross-chain reusability + + _file = ledger_type.config_file + _key = ledger_type.key_file + _crypto_cls = EthereumCrypto + + def _transfer_from_eoa(self, to: str, amount: int, chain_type: ChainType, rpc: t.Optional[str] = None) -> None: + """Transfer funds from EOA wallet.""" + ledger_api = t.cast(EthereumApi, self.ledger_api(chain_type=chain_type, rpc=rpc)) + tx_helper = TxSettler( + ledger_api=ledger_api, + crypto=self.crypto, + chain_type=ChainProfile.CUSTOM, + timeout=ON_CHAIN_INTERACT_TIMEOUT, + retries=ON_CHAIN_INTERACT_RETRIES, + sleep=ON_CHAIN_INTERACT_SLEEP, + ) + + def _build_tx( # pylint: disable=unused-argument + *args: t.Any, **kwargs: t.Any + ) -> t.Dict: + """Build transaction""" + max_priority_fee_per_gas = os.getenv("MAX_PRIORITY_FEE_PER_GAS", None) + max_fee_per_gas = os.getenv("MAX_FEE_PER_GAS", None) + tx = ledger_api.get_transfer_transaction( + sender_address=self.crypto.address, + destination_address=to, + amount=amount, + tx_fee=50000, + tx_nonce="0x", + chain_id=chain_type.id, + raise_on_try=True, + max_fee_per_gas=int(max_fee_per_gas) if max_fee_per_gas else None, + max_priority_fee_per_gas=int(max_priority_fee_per_gas) if max_priority_fee_per_gas else None, + ) + return ledger_api.update_with_gas_estimate( + transaction=tx, + raise_on_try=True, + ) + + setattr(tx_helper, "build", _build_tx) # noqa: B010 + tx_helper.transact(lambda x: x, "", kwargs={}) + + def _transfer_from_safe(self, to: str, amount: int, chain_type: ChainType, rpc: t.Optional[str] = None) -> None: + """Transfer funds from safe wallet.""" + transfer_from_safe( + ledger_api=self.ledger_api(chain_type=chain_type, rpc=rpc), + crypto=self.crypto, + safe=t.cast(str, self.safes[chain_type]), + to=to, + amount=amount, + ) + + def transfer( + self, + to: str, + amount: int, + chain_type: ChainType, + from_safe: bool = True, + rpc: t.Optional[str] = None, + ) -> None: + """Transfer funds to the given account.""" + if from_safe: + return self._transfer_from_safe( + to=to, + amount=amount, + chain_type=chain_type, + rpc=rpc, + ) + return self._transfer_from_eoa( + to=to, + amount=amount, + chain_type=chain_type, + rpc=rpc, + ) + + @classmethod + def new( + cls, password: str, path: Path + ) -> t.Tuple["EthereumMasterWallet", t.List[str]]: + """Create a new master wallet.""" + # Backport support on aea + account = Account() + account.enable_unaudited_hdwallet_features() + crypto, mnemonic = account.create_with_mnemonic() + (path / cls._key).write_text( + data=json.dumps( + Account.encrypt( + private_key=crypto._private_key, # pylint: disable=protected-access + password=password, + ), + indent=2, + ), + encoding="utf-8", + ) + + # Create wallet + wallet = EthereumMasterWallet(path=path, address=crypto.address, safe_chains=[]) + wallet.store() + wallet.password = password + return wallet, mnemonic.split() + + def create_safe( + self, + chain_type: ChainType, + owner: t.Optional[str] = None, + rpc: t.Optional[str] = None, + ) -> None: + """Create safe.""" + if chain_type in self.safe_chains: + return + safe, self.safe_nonce = create_gnosis_safe( + ledger_api=self.ledger_api(chain_type=chain_type, rpc=rpc), + crypto=self.crypto, + owner=owner, + salt_nonce=self.safe_nonce, + ) + self.safe_chains.append(chain_type) + if self.safes is None: + self.safes = {} + self.safes[chain_type] = safe + self.store() + + def add_backup_owner( + self, + chain_type: ChainType, + owner: str, + rpc: t.Optional[str] = None, + ) -> None: + """Add a backup owner.""" + ledger_api = self.ledger_api(chain_type=chain_type, rpc=rpc) + if chain_type not in self.safes: + raise ValueError(f"Safes not created for chain_type {chain_type}!") + safe = t.cast(str, self.safes[chain_type]) + if len(get_owners(ledger_api=ledger_api, safe=safe)) == 2: + raise ValueError("Backup owner already exist!") + add_owner( + ledger_api=ledger_api, + safe=safe, + owner=owner, + crypto=self.crypto, + ) + + def swap_backup_owner( + self, + chain_type: ChainType, + old_owner: str, + new_owner: str, + rpc: t.Optional[str] = None, + ) -> None: + """Swap backup owner.""" + ledger_api = self.ledger_api(chain_type=chain_type, rpc=rpc) + if chain_type not in self.safes: + raise ValueError(f"Safes not created for chain_type {chain_type}!") + safe = t.cast(str, self.safes[chain_type]) + if len(get_owners(ledger_api=ledger_api, safe=safe)) == 1: + raise ValueError("Backup owner does not exist, cannot swap!") + swap_owner( + ledger_api=ledger_api, + safe=safe, + old_owner=old_owner, + new_owner=new_owner, + crypto=self.crypto, + ) + + def add_or_swap_owner( + self, + chain_type: ChainType, + owner: str, + rpc: t.Optional[str] = None, + ) -> None: + """Add or swap backup owner.""" + ledger_api = self.ledger_api(chain_type=chain_type, rpc=rpc) + if chain_type not in self.safes: + raise ValueError(f"Safes not created for chain_type {chain_type}!") + safe = t.cast(str, self.safes[chain_type]) + owners = get_owners(ledger_api=ledger_api, safe=safe) + if len(owners) == 1: + return self.add_backup_owner(chain_type=chain_type, owner=owner, rpc=rpc) + + owners.remove(self.address) + (old_owner,) = owners + if old_owner == owner: + return None + + return self.swap_backup_owner( + chain_type=chain_type, + old_owner=old_owner, + new_owner=owner, + rpc=rpc, + ) + + @classmethod + def load(cls, path: Path) -> "EthereumMasterWallet": + """Load master wallet.""" + raw_ethereum_wallet = super().load(path) # type: ignore + safes = {} + for id_, safe_address in raw_ethereum_wallet.safes.items(): + safes[ChainType(int(id_))] = safe_address + + raw_ethereum_wallet.safes = safes + return t.cast(EthereumMasterWallet, raw_ethereum_wallet) + + +LEDGER_TYPE_TO_WALLET_CLASS = { + LedgerType.ETHEREUM: EthereumMasterWallet, +} + + +class MasterWalletManager: + """Master wallet manager.""" + + def __init__(self, path: Path, password: t.Optional[str] = None) -> None: + """Initialize master wallet manager.""" + self.path = path + self._password = password + + @property + def json(self) -> t.List[t.Dict]: + """List of wallets""" + return [wallet.json for wallet in self] + + @property + def password(self) -> str: + """Password string.""" + if self._password is None: + raise ValueError("Password not set.") + return self._password + + @password.setter + def password(self, value: str) -> None: + """Set password value.""" + self._password = value + + def setup(self) -> "MasterWalletManager": + """Setup wallet manager.""" + self.path.mkdir(exist_ok=True) + return self + + def create(self, ledger_type: LedgerType) -> t.Tuple[MasterWallet, t.List[str]]: + """ + Create a master wallet + + :param ledger_type: Ledger type for the wallet. + :return: Tuple of master wallet and mnemonic + """ + if ledger_type == LedgerType.ETHEREUM: + return EthereumMasterWallet.new(password=self.password, path=self.path) + raise ValueError(f"{ledger_type} is not supported.") + + def exists(self, ledger_type: LedgerType) -> bool: + """ + Check if a wallet exists or not + + :param ledger_type: Ledger type for the wallet. + :return: True if wallet exists, False otherwise. + """ + return (self.path / ledger_type.config_file).exists() and ( + self.path / ledger_type.key_file + ).exists() + + def load(self, ledger_type: LedgerType) -> MasterWallet: + """ + Load master wallet + + :param ledger_type: Ledger type for the wallet. + :return: Master wallet object + """ + if ledger_type == LedgerType.ETHEREUM: + wallet = EthereumMasterWallet.load(path=self.path) + else: + raise ValueError(f"{ledger_type} is not supported.") + wallet.password = self.password + return wallet + + def __iter__(self) -> t.Iterator[MasterWallet]: + """Iterate over master wallets.""" + for ledger_type in LedgerType: + if not self.exists(ledger_type=ledger_type): + continue + yield LEDGER_TYPE_TO_WALLET_CLASS[ledger_type].load(path=self.path) diff --git a/run_service.py b/run_service.py old mode 100755 new mode 100644 index 93160a60..f8f5da7b --- a/run_service.py +++ b/run_service.py @@ -1,601 +1,613 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2024 Valory AG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ -"""Optimus Quickstart script.""" - -import getpass -import json -import os -import sys -import time -import typing as t -from dataclasses import dataclass -from pathlib import Path - -import requests -import yaml -from aea.crypto.base import LedgerApi -from aea_ledger_ethereum import EthereumApi -from dotenv import load_dotenv -from halo import Halo -from termcolor import colored - -from operate.account.user import UserAccount -from operate.cli import OperateApp -from operate.ledger.profiles import OLAS, STAKING -from operate.resource import LocalResource, deserialize -from operate.types import ( - LedgerType, - ServiceTemplate, - ConfigurationTemplate, - FundRequirementsTemplate, ChainType, -) - -load_dotenv() - -SUGGESTED_TOP_UP_DEFAULT = 1_000_000_000_000_000 -SUGGESTED_SAFE_TOP_UP_DEFAULT = 5_000_000_000_000_000 -MASTER_WALLET_MIMIMUM_BALANCE = 7_000_000_000_000_000 -COST_OF_BOND = 1 -COST_OF_BOND_STAKING = 2 * 10 ** 19 -STAKED_BONDING_TOKEN = "OLAS" -USDC_REQUIRED = 10_000_000 -USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" -WARNING_ICON = colored('\u26A0', 'yellow') -OPERATE_HOME = Path.cwd() / ".optimus" - -CHAIN_ID_TO_METADATA = { - 1: { - "name": "Ethereum Mainnet", - "token": "ETH", - "native_token_balance": MASTER_WALLET_MIMIMUM_BALANCE, - "usdcRequired": True, - }, - 10: { - "name": "Optimism", - "token": "ETH", - "usdcRequired": False, - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 9, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, - "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(5_000_000_000), - } - }, - 8453: { - "name": "Base", - "token": "ETH", - "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, - "usdcRequired": False, - "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(5_000_000_000), - } - }, -} - - - -@dataclass -class OptimusConfig(LocalResource): - """Local configuration.""" - - path: Path - optimism_rpc: t.Optional[str] = None - ethereum_rpc: t.Optional[str] = None - base_rpc: t.Optional[str] = None - tenderly_access_key: t.Optional[str] = None - tenderly_account_slug: t.Optional[str] = None - tenderly_project_slug: t.Optional[str] = None - password_migrated: t.Optional[bool] = None - use_staking: t.Optional[bool] = None - - @classmethod - def from_json(cls, obj: t.Dict) -> "LocalResource": - """Load LocalResource from json.""" - kwargs = {} - for pname, ptype in cls.__annotations__.items(): - if pname.startswith("_"): - continue - - # allow for optional types - is_optional_type = t.get_origin(ptype) is t.Union and type(None) in t.get_args(ptype) - value = obj.get(pname, None) - if is_optional_type and value is None: - continue - - kwargs[pname] = deserialize(obj=obj[pname], otype=ptype) - return cls(**kwargs) - - - -def print_box(text: str, margin: int = 1, character: str = '=') -> None: - """Print text centered within a box.""" - - lines = text.split('\n') - text_length = max(len(line) for line in lines) - length = text_length + 2 * margin - - border = character * length - margin_str = ' ' * margin - - print(border) - print(f"{margin_str}{text}{margin_str}") - print(border) - print() - - -def print_title(text: str) -> None: - """Print title.""" - print() - print_box(text, 4, '=') - - -def print_section(text: str) -> None: - """Print section.""" - print_box(text, 1, '-') - - -def wei_to_unit(wei: int) -> float: - """Convert Wei to unit.""" - return wei / 1e18 - - -def wei_to_token(wei: int, token: str = "xDAI") -> str: - """Convert Wei to token.""" - return f"{wei_to_unit(wei):.2f} {token}" - - -def ask_confirm_password() -> str: - password = getpass.getpass("Please enter a password: ") - confirm_password = getpass.getpass("Please confirm your password: ") - - if password == confirm_password: - return password - else: - print("Passwords do not match. Terminating.") - sys.exit(1) - - -def check_rpc(rpc_url: str) -> None: - spinner = Halo(text=f"Checking RPC...", spinner="dots") - spinner.start() - - rpc_data = { - "jsonrpc": "2.0", - "method": "eth_newFilter", - "params": ["invalid"], - "id": 1 - } - - try: - response = requests.post( - rpc_url, - json=rpc_data, - headers={"Content-Type": "application/json"} - ) - response.raise_for_status() - rpc_response = response.json() - except Exception as e: - print("Error: Failed to send RPC request:", e) - sys.exit(1) - - rcp_error_message = rpc_response.get("error", {}).get("message", "Exception processing RCP response") - - if rcp_error_message == "Exception processing RCP response": - print("Error: The received RCP response is malformed. Please verify the RPC address and/or RCP behavior.") - print(" Received response:") - print(" ", rpc_response) - print("") - print("Terminating script.") - sys.exit(1) - elif rcp_error_message == "Out of requests": - print("Error: The provided RCP is out of requests.") - print("Terminating script.") - sys.exit(1) - elif rcp_error_message == "The method eth_newFilter does not exist/is not available": - print("Error: The provided RPC does not support 'eth_newFilter'.") - print("Terminating script.") - sys.exit(1) - elif rcp_error_message == "invalid params": - spinner.succeed("RPC checks passed.") - else: - print("Error: Unknown RCP error.") - print(" Received response:") - print(" ", rpc_response) - print("") - print("Terminating script.") - sys.exit(1) - - -def get_local_config() -> OptimusConfig: - """Get local optimus configuration.""" - path = OPERATE_HOME / "local_config.json" - if path.exists(): - optimus_config = OptimusConfig.load(path) - else: - optimus_config = OptimusConfig(path) - - print_section("API Key Configuration") - - if optimus_config.ethereum_rpc is None: - optimus_config.ethereum_rpc = input("Please enter an Ethereum RPC URL: ") - - if optimus_config.optimism_rpc is None: - optimus_config.optimism_rpc = input("Please enter an Optimism RPC URL: ") - - if optimus_config.base_rpc is None: - optimus_config.base_rpc = input("Please enter a Base RPC URL: ") - - if optimus_config.tenderly_access_key is None: - optimus_config.tenderly_access_key = input( - "Please enter your Tenderly API Key. Get one at https://dashboard.tenderly.co/: " - ) - - if optimus_config.tenderly_account_slug is None: - optimus_config.tenderly_account_slug = input( - "Please enter your Tenderly Account Slug: " - ) - - if optimus_config.tenderly_project_slug is None: - optimus_config.tenderly_project_slug = input( - "Please enter your Tenderly Project Slug: " - ) - - if optimus_config.password_migrated is None: - optimus_config.password_migrated = False - - if optimus_config.use_staking is None: - optimus_config.use_staking = input("Do you want to stake your service? (y/n): ").lower() == 'y' - - optimus_config.store() - return optimus_config - - -def apply_env_vars(env_vars: t.Dict[str, str]) -> None: - """Apply environment variables.""" - for key, value in env_vars.items(): - if value is not None: - os.environ[key] = value - -def handle_password_migration(operate: OperateApp, config: OptimusConfig) -> t.Optional[str]: - """Handle password migration.""" - if not config.password_migrated: - print("Add password...") - old_password, new_password = "12345", ask_confirm_password() - operate.user_account.update(old_password, new_password) - if operate.wallet_manager.exists(LedgerType.ETHEREUM): - operate.password = old_password - wallet = operate.wallet_manager.load(LedgerType.ETHEREUM) - wallet.crypto.dump(str(wallet.key_path), password=new_password) - wallet.password = new_password - wallet.store() - - config.password_migrated = True - config.store() - return new_password - return None - - -def get_service_template(config: OptimusConfig) -> ServiceTemplate: - """Get the service template""" - return ServiceTemplate({ - "name": "Optimus", - "hash": "bafybeibjwknk7bchs24irn7ayogp72i2cbaioqcd5dzssqtdq4gihrocu4", - "description": "Optimus", - "image": "https://operate.olas.network/_next/image?url=%2Fimages%2Fprediction-agent.png&w=3840&q=75", - "service_version": 'v0.18.1', - "home_chain_id": "10", - "configurations": { - "1": ConfigurationTemplate( - { - "staking_program_id": "optimus_alpha", - "rpc": config.ethereum_rpc, - "nft": "bafybeig64atqaladigoc3ds4arltdu63wkdrk3gesjfvnfdmz35amv7faq", - "cost_of_bond": COST_OF_BOND, - "threshold": 1, - "use_staking": False, - "fund_requirements": FundRequirementsTemplate( - { - "agent": SUGGESTED_TOP_UP_DEFAULT, - "safe": SUGGESTED_SAFE_TOP_UP_DEFAULT, - } - ), - } - ), - "10": ConfigurationTemplate( - { - "staking_program_id": "optimus_alpha", - "rpc": config.optimism_rpc, - "nft": "bafybeig64atqaladigoc3ds4arltdu63wkdrk3gesjfvnfdmz35amv7faq", - "cost_of_bond": COST_OF_BOND_STAKING, - "threshold": 1, - "use_staking": config.use_staking, - "fund_requirements": FundRequirementsTemplate( - { - "agent": SUGGESTED_TOP_UP_DEFAULT, - "safe": 0, - } - ), - } - ), - "8453": ConfigurationTemplate( - { - "staking_program_id": "optimus_alpha", - "rpc": config.base_rpc, - "nft": "bafybeig64atqaladigoc3ds4arltdu63wkdrk3gesjfvnfdmz35amv7faq", - "cost_of_bond": COST_OF_BOND, - "threshold": 1, - "use_staking": False, - "fund_requirements": FundRequirementsTemplate( - { - "agent": SUGGESTED_TOP_UP_DEFAULT, - "safe": 0, - } - ), - } - ), - }, -}) - - -def get_erc20_balance(ledger_api: LedgerApi, token: str, account: str) -> int: - """Get ERC-20 token balance of an account.""" - web3 = t.cast(EthereumApi, ledger_api).api - - # ERC20 Token Standard Partial ABI - erc20_abi = [ - { - "constant": True, - "inputs": [{"name": "_owner", "type": "address"}], - "name": "balanceOf", - "outputs": [{"name": "balance", "type": "uint256"}], - "type": "function", - } - ] - - # Create contract instance - contract = web3.eth.contract(address=web3.to_checksum_address(token), abi=erc20_abi) - - # Get the balance of the account - balance = contract.functions.balanceOf(web3.to_checksum_address(account)).call() - - return balance - -FALLBACK_STAKING_PARAMS = dict( - agent_ids=[25], - service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec - staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec - service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec - min_staking_deposit=20000000000000000000, - activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8" # nosec -) - -def add_volumes(docker_compose_path: Path, host_path: str, container_path: str) -> None: - """Add volumes to the docker-compose.""" - with open(docker_compose_path, "r") as f: - docker_compose = yaml.safe_load(f) - - docker_compose["services"]["optimus_abci_0"]["volumes"].append(f"{host_path}:{container_path}:Z") - - with open(docker_compose_path, "w") as f: - yaml.dump(docker_compose, f) - - -def main() -> None: - """Run service.""" - - print_title("Optimus Quickstart") - print("This script will assist you in setting up and running the Optimus service.") - print() - - print_section("Set up local user account") - operate = OperateApp( - home=OPERATE_HOME, - ) - operate.setup() - - optimus_config = get_local_config() - template = get_service_template(optimus_config) - - if operate.user_account is None: - print("Creating a new local user account...") - password = ask_confirm_password() - UserAccount.new( - password=password, - path=operate._path / "user.json", - ) - optimus_config.password_migrated = True - optimus_config.store() - else: - password = handle_password_migration(operate, optimus_config) - if password is None: - password = getpass.getpass("Enter local user account password: ") - if not operate.user_account.is_valid(password=password): - print("Invalid password!") - sys.exit(1) - - operate.password = password - if not operate.wallet_manager.exists(ledger_type=LedgerType.ETHEREUM): - print("Creating the main wallet...") - wallet, mnemonic = operate.wallet_manager.create(ledger_type=LedgerType.ETHEREUM) - wallet.password = password - print() - print_box(f"Please save the mnemonic phrase for the main wallet:\n{', '.join(mnemonic)}", 0, '-') - input("Press enter to continue...") - else: - wallet = operate.wallet_manager.load(ledger_type=LedgerType.ETHEREUM) - - manager = operate.service_manager() - if len(manager.json) > 0: - old_hash = manager.json[0]["hash"] - if old_hash == template["hash"]: - print(f'Loading service {template["hash"]}') - service = manager.load_or_create( - hash=template["hash"], - service_template=template, - ) - else: - print(f"Updating service from {old_hash} to " + template["hash"]) - service = manager.update_service( - old_hash=old_hash, - new_hash=template["hash"], - service_template=template, - ) - else: - print(f'Creating service {template["hash"]}') - service = manager.load_or_create( - hash=template["hash"], - service_template=template, - ) - - for chain_id, configuration in service.chain_configs.items(): - chain_metadata = CHAIN_ID_TO_METADATA[int(chain_id)] - chain_config = service.chain_configs[chain_id] - chain_type = chain_config.ledger_config.chain - ledger_api = wallet.ledger_api( - chain_type=chain_type, - rpc=chain_config.ledger_config.rpc, - ) - - chain_name, token = chain_metadata['name'], chain_metadata["token"] - balance_str = wei_to_token(ledger_api.get_balance(wallet.crypto.address), token) - print( - f"[{chain_name}] Main wallet balance: {balance_str}", - ) - safe_exists = wallet.safes[chain_type] is not None - required_balance = chain_metadata["firstTimeTopUp"] if not safe_exists else chain_metadata["operationalFundReq"] - print( - f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}", - ) - spinner = Halo( - text=f"[{chain_name}] Waiting for funds...", - spinner="dots" - ) - spinner.start() - - while ledger_api.get_balance(wallet.crypto.address) < MASTER_WALLET_MIMIMUM_BALANCE: - time.sleep(1) - - spinner.succeed(f"[{chain_name}] Main wallet updated balance: {wei_to_token(ledger_api.get_balance(wallet.crypto.address), token)}.") - print() - - if wallet.safes.get(chain_type) is not None: - print(f"[{chain_name}] Safe already exists") - else: - print(f"[{chain_name}] Creating Safe") - ledger_type = LedgerType.ETHEREUM - wallet_manager = operate.wallet_manager - wallet = wallet_manager.load(ledger_type=ledger_type) - - wallet.create_safe( # pylint: disable=no-member - chain_type=chain_type, - rpc=chain_config.ledger_config.rpc, - ) - print(f"[{chain_name}] Funding Safe") - wallet.transfer( - to=t.cast(str, wallet.safes[chain_type]), - amount=int(chain_metadata["firstTimeTopUp"]), - chain_type=chain_type, - from_safe=False, - rpc=chain_config.ledger_config.rpc, - ) - - print_section(f"[{chain_name}] Set up the service in the Olas Protocol") - - address = wallet.safes[chain_type] - print( - f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(MASTER_WALLET_MIMIMUM_BALANCE, token)}." - ) - spinner = Halo( - text=f"[{chain_name}] Waiting for funds...", - spinner="dots", - ) - spinner.start() - - while ledger_api.get_balance(address) < first_time_top_up: - time.sleep(1) - - spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") - - if chain_config.chain_data.user_params.use_staking: - print(f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(2 * COST_OF_BOND_STAKING, STAKED_BONDING_TOKEN)}") - - spinner = Halo( - text=f"[{chain_name}] Waiting for {STAKED_BONDING_TOKEN}...", - spinner="dots", - ) - spinner.start() - olas_address = OLAS[chain_type] - while get_erc20_balance(ledger_api, olas_address, address) < 2 * COST_OF_BOND_STAKING: - time.sleep(1) - - balance = get_erc20_balance(ledger_api, olas_address, address) / 10 ** 18 - spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} {STAKED_BONDING_TOKEN}") - - - if chain_metadata.get("usdcRequired", False): - print(f"[{chain_name}] Please make sure address {address} has at least 10 USDC") - - spinner = Halo( - text=f"[{chain_name}] Waiting for USDC...", - spinner="dots", - ) - spinner.start() - - while get_erc20_balance(ledger_api, USDC_ADDRESS, address) < USDC_REQUIRED: - time.sleep(1) - - balance = get_erc20_balance(ledger_api, USDC_ADDRESS, address) / 10 ** 6 - spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} USDC.") - - manager.deploy_service_onchain_from_safe_single_chain( - hash=service.hash, - chain_id=chain_id, - fallback_staking_params=FALLBACK_STAKING_PARAMS, - ) - manager.fund_service(hash=service.hash, chain_id=chain_id) - - safes = { - ChainType.from_id(int(chain)).name.lower(): config.chain_data.multisig - for chain, config in service.chain_configs.items() - } - home_chain_id = service.home_chain_id - home_chain_type = ChainType.from_id(int(home_chain_id)) - target_staking_program_id = service.chain_configs[home_chain_id].chain_data.user_params.staking_program_id - env_vars = { - "SAFE_CONTRACT_ADDRESSES": json.dumps(safes, separators=(',', ':')), - "TENDERLY_ACCESS_KEY": optimus_config.tenderly_access_key, - "TENDERLY_ACCOUNT_SLUG": optimus_config.tenderly_account_slug, - "TENDERLY_PROJECT_SLUG": optimus_config.tenderly_project_slug, - "STAKING_TOKEN_CONTRACT_ADDRESS": STAKING[home_chain_type][target_staking_program_id], - } - apply_env_vars(env_vars) - print("Skipping local deployment") - service.deployment.build(use_docker=True, force=True, chain_id=home_chain_id) - docker_compose_path = service.path / "deployment" / "docker-compose.yaml" - add_volumes(docker_compose_path, str(OPERATE_HOME), "/data") - service.deployment.start(use_docker=True) - - print() - print_section("Running the service") - - -if __name__ == "__main__": - main() +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""Optimus Quickstart script.""" + +import getpass +import json +import os +import sys +import time +import typing as t +from dataclasses import dataclass +from pathlib import Path + +import requests +import yaml +from aea.crypto.base import LedgerApi +from aea_ledger_ethereum import EthereumApi +from dotenv import load_dotenv +from halo import Halo +from termcolor import colored + +from operate.account.user import UserAccount +from operate.cli import OperateApp +from operate.ledger.profiles import OLAS, STAKING +from operate.resource import LocalResource, deserialize +from operate.types import ( + LedgerType, + ServiceTemplate, + ConfigurationTemplate, + FundRequirementsTemplate, ChainType, OnChainState, +) + +load_dotenv() + +SUGGESTED_TOP_UP_DEFAULT = 1_000_000_000_000_000 +SUGGESTED_SAFE_TOP_UP_DEFAULT = 5_000_000_000_000_000 +MASTER_WALLET_MIMIMUM_BALANCE = 6_001_000_000_000_000 +COST_OF_BOND = 1 +COST_OF_BOND_STAKING = 2 * 10 ** 19 +STAKED_BONDING_TOKEN = "OLAS" +USDC_REQUIRED = 10_000_000 +USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" +WARNING_ICON = colored('\u26A0', 'yellow') +OPERATE_HOME = Path.cwd() / ".optimus" + +CHAIN_ID_TO_METADATA = { + 1: { + "name": "Ethereum Mainnet", + "token": "ETH", + "native_token_balance": MASTER_WALLET_MIMIMUM_BALANCE, + "usdcRequired": True, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 10 * 2, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT * 3, + "gasParams": { + # this means default values will be used + "MAX_PRIORITY_FEE_PER_GAS": "", + "MAX_FEE_PER_GAS": "", + } + }, + 10: { + "name": "Optimism", + "token": "ETH", + "usdcRequired": False, + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 5, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, + "gasParams": { + "MAX_PRIORITY_FEE_PER_GAS": str(15_000), + "MAX_FEE_PER_GAS": str(1_000_000_000), + } + }, + 8453: { + "name": "Base", + "token": "ETH", + "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 5, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT / 10, + "usdcRequired": False, + "gasParams": { + "MAX_PRIORITY_FEE_PER_GAS": str(150_000), + "MAX_FEE_PER_GAS": str(500_000_000), + } + }, +} + + + +@dataclass +class OptimusConfig(LocalResource): + """Local configuration.""" + + path: Path + optimism_rpc: t.Optional[str] = None + ethereum_rpc: t.Optional[str] = None + base_rpc: t.Optional[str] = None + tenderly_access_key: t.Optional[str] = None + tenderly_account_slug: t.Optional[str] = None + tenderly_project_slug: t.Optional[str] = None + password_migrated: t.Optional[bool] = None + use_staking: t.Optional[bool] = None + + @classmethod + def from_json(cls, obj: t.Dict) -> "LocalResource": + """Load LocalResource from json.""" + kwargs = {} + for pname, ptype in cls.__annotations__.items(): + if pname.startswith("_"): + continue + + # allow for optional types + is_optional_type = t.get_origin(ptype) is t.Union and type(None) in t.get_args(ptype) + value = obj.get(pname, None) + if is_optional_type and value is None: + continue + + kwargs[pname] = deserialize(obj=obj[pname], otype=ptype) + return cls(**kwargs) + + + +def print_box(text: str, margin: int = 1, character: str = '=') -> None: + """Print text centered within a box.""" + + lines = text.split('\n') + text_length = max(len(line) for line in lines) + length = text_length + 2 * margin + + border = character * length + margin_str = ' ' * margin + + print(border) + print(f"{margin_str}{text}{margin_str}") + print(border) + print() + + +def print_title(text: str) -> None: + """Print title.""" + print() + print_box(text, 4, '=') + + +def print_section(text: str) -> None: + """Print section.""" + print_box(text, 1, '-') + + +def wei_to_unit(wei: int) -> float: + """Convert Wei to unit.""" + return wei / 1e18 + + +def wei_to_token(wei: int, token: str = "xDAI") -> str: + """Convert Wei to token.""" + return f"{wei_to_unit(wei):.6f} {token}" + + +def ask_confirm_password() -> str: + password = getpass.getpass("Please enter a password: ") + confirm_password = getpass.getpass("Please confirm your password: ") + + if password == confirm_password: + return password + else: + print("Passwords do not match. Terminating.") + sys.exit(1) + + +def check_rpc(rpc_url: str) -> None: + spinner = Halo(text=f"Checking RPC...", spinner="dots") + spinner.start() + + rpc_data = { + "jsonrpc": "2.0", + "method": "eth_newFilter", + "params": ["invalid"], + "id": 1 + } + + try: + response = requests.post( + rpc_url, + json=rpc_data, + headers={"Content-Type": "application/json"} + ) + response.raise_for_status() + rpc_response = response.json() + except Exception as e: + print("Error: Failed to send RPC request:", e) + sys.exit(1) + + rcp_error_message = rpc_response.get("error", {}).get("message", "Exception processing RCP response") + + if rcp_error_message == "Exception processing RCP response": + print("Error: The received RCP response is malformed. Please verify the RPC address and/or RCP behavior.") + print(" Received response:") + print(" ", rpc_response) + print("") + print("Terminating script.") + sys.exit(1) + elif rcp_error_message == "Out of requests": + print("Error: The provided RCP is out of requests.") + print("Terminating script.") + sys.exit(1) + elif rcp_error_message == "The method eth_newFilter does not exist/is not available": + print("Error: The provided RPC does not support 'eth_newFilter'.") + print("Terminating script.") + sys.exit(1) + elif rcp_error_message == "invalid params": + spinner.succeed("RPC checks passed.") + else: + print("Error: Unknown RCP error.") + print(" Received response:") + print(" ", rpc_response) + print("") + print("Terminating script.") + sys.exit(1) + + +def get_local_config() -> OptimusConfig: + """Get local optimus configuration.""" + path = OPERATE_HOME / "local_config.json" + if path.exists(): + optimus_config = OptimusConfig.load(path) + else: + optimus_config = OptimusConfig(path) + + print_section("API Key Configuration") + + if optimus_config.ethereum_rpc is None: + optimus_config.ethereum_rpc = input("Please enter an Ethereum RPC URL: ") + + if optimus_config.optimism_rpc is None: + optimus_config.optimism_rpc = input("Please enter an Optimism RPC URL: ") + + if optimus_config.base_rpc is None: + optimus_config.base_rpc = input("Please enter a Base RPC URL: ") + + if optimus_config.tenderly_access_key is None: + optimus_config.tenderly_access_key = input( + "Please enter your Tenderly API Key. Get one at https://dashboard.tenderly.co/: " + ) + + if optimus_config.tenderly_account_slug is None: + optimus_config.tenderly_account_slug = input( + "Please enter your Tenderly Account Slug: " + ) + + if optimus_config.tenderly_project_slug is None: + optimus_config.tenderly_project_slug = input( + "Please enter your Tenderly Project Slug: " + ) + + if optimus_config.password_migrated is None: + optimus_config.password_migrated = False + + if optimus_config.use_staking is None: + optimus_config.use_staking = input("Do you want to stake your service? (y/n): ").lower() == 'y' + + optimus_config.store() + return optimus_config + + +def apply_env_vars(env_vars: t.Dict[str, str]) -> None: + """Apply environment variables.""" + for key, value in env_vars.items(): + if value is not None: + os.environ[key] = value + +def handle_password_migration(operate: OperateApp, config: OptimusConfig) -> t.Optional[str]: + """Handle password migration.""" + if not config.password_migrated: + print("Add password...") + old_password, new_password = "12345", ask_confirm_password() + operate.user_account.update(old_password, new_password) + if operate.wallet_manager.exists(LedgerType.ETHEREUM): + operate.password = old_password + wallet = operate.wallet_manager.load(LedgerType.ETHEREUM) + wallet.crypto.dump(str(wallet.key_path), password=new_password) + wallet.password = new_password + wallet.store() + + config.password_migrated = True + config.store() + return new_password + return None + + +def get_service_template(config: OptimusConfig) -> ServiceTemplate: + """Get the service template""" + return ServiceTemplate({ + "name": "Optimus", + "hash": "bafybeibjwknk7bchs24irn7ayogp72i2cbaioqcd5dzssqtdq4gihrocu4", + "description": "Optimus", + "image": "https://operate.olas.network/_next/image?url=%2Fimages%2Fprediction-agent.png&w=3840&q=75", + "service_version": 'v0.18.1', + "home_chain_id": "10", + "configurations": { + "1": ConfigurationTemplate( + { + "staking_program_id": "optimus_alpha", + "rpc": config.ethereum_rpc, + "nft": "bafybeig64atqaladigoc3ds4arltdu63wkdrk3gesjfvnfdmz35amv7faq", + "cost_of_bond": COST_OF_BOND, + "threshold": 1, + "use_staking": False, + "fund_requirements": FundRequirementsTemplate( + { + "agent": SUGGESTED_TOP_UP_DEFAULT, + "safe": SUGGESTED_SAFE_TOP_UP_DEFAULT, + } + ), + } + ), + "10": ConfigurationTemplate( + { + "staking_program_id": "optimus_alpha", + "rpc": config.optimism_rpc, + "nft": "bafybeig64atqaladigoc3ds4arltdu63wkdrk3gesjfvnfdmz35amv7faq", + "cost_of_bond": COST_OF_BOND_STAKING, + "threshold": 1, + "use_staking": config.use_staking, + "fund_requirements": FundRequirementsTemplate( + { + "agent": SUGGESTED_TOP_UP_DEFAULT, + "safe": 0, + } + ), + } + ), + "8453": ConfigurationTemplate( + { + "staking_program_id": "optimus_alpha", + "rpc": config.base_rpc, + "nft": "bafybeig64atqaladigoc3ds4arltdu63wkdrk3gesjfvnfdmz35amv7faq", + "cost_of_bond": COST_OF_BOND, + "threshold": 1, + "use_staking": False, + "fund_requirements": FundRequirementsTemplate( + { + "agent": SUGGESTED_TOP_UP_DEFAULT, + "safe": 0, + } + ), + } + ), + }, +}) + + +def get_erc20_balance(ledger_api: LedgerApi, token: str, account: str) -> int: + """Get ERC-20 token balance of an account.""" + web3 = t.cast(EthereumApi, ledger_api).api + + # ERC20 Token Standard Partial ABI + erc20_abi = [ + { + "constant": True, + "inputs": [{"name": "_owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "balance", "type": "uint256"}], + "type": "function", + } + ] + + # Create contract instance + contract = web3.eth.contract(address=web3.to_checksum_address(token), abi=erc20_abi) + + # Get the balance of the account + balance = contract.functions.balanceOf(web3.to_checksum_address(account)).call() + + return balance + +FALLBACK_STAKING_PARAMS = dict( + agent_ids=[25], + service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec + staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec + service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec + min_staking_deposit=20000000000000000000, + activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8" # nosec +) + +def add_volumes(docker_compose_path: Path, host_path: str, container_path: str) -> None: + """Add volumes to the docker-compose.""" + with open(docker_compose_path, "r") as f: + docker_compose = yaml.safe_load(f) + + docker_compose["services"]["optimus_abci_0"]["volumes"].append(f"{host_path}:{container_path}:Z") + + with open(docker_compose_path, "w") as f: + yaml.dump(docker_compose, f) + + +def main() -> None: + """Run service.""" + + print_title("Optimus Quickstart") + print("This script will assist you in setting up and running the Optimus service.") + print() + + print_section("Set up local user account") + operate = OperateApp( + home=OPERATE_HOME, + ) + operate.setup() + + optimus_config = get_local_config() + template = get_service_template(optimus_config) + + if operate.user_account is None: + print("Creating a new local user account...") + password = ask_confirm_password() + UserAccount.new( + password=password, + path=operate._path / "user.json", + ) + optimus_config.password_migrated = True + optimus_config.store() + else: + password = handle_password_migration(operate, optimus_config) + if password is None: + password = getpass.getpass("Enter local user account password: ") + if not operate.user_account.is_valid(password=password): + print("Invalid password!") + sys.exit(1) + + operate.password = password + if not operate.wallet_manager.exists(ledger_type=LedgerType.ETHEREUM): + print("Creating the main wallet...") + wallet, mnemonic = operate.wallet_manager.create(ledger_type=LedgerType.ETHEREUM) + wallet.password = password + print() + print_box(f"Please save the mnemonic phrase for the main wallet:\n{', '.join(mnemonic)}", 0, '-') + input("Press enter to continue...") + else: + wallet = operate.wallet_manager.load(ledger_type=LedgerType.ETHEREUM) + + manager = operate.service_manager() + if len(manager.json) > 0: + old_hash = manager.json[0]["hash"] + if old_hash == template["hash"]: + print(f'Loading service {template["hash"]}') + service = manager.load_or_create( + hash=template["hash"], + service_template=template, + ) + else: + print(f"Updating service from {old_hash} to " + template["hash"]) + service = manager.update_service( + old_hash=old_hash, + new_hash=template["hash"], + service_template=template, + ) + else: + print(f'Creating service {template["hash"]}') + service = manager.load_or_create( + hash=template["hash"], + service_template=template, + ) + + for chain_id, configuration in service.chain_configs.items(): + chain_metadata = CHAIN_ID_TO_METADATA[int(chain_id)] + chain_config = service.chain_configs[chain_id] + chain_type = chain_config.ledger_config.chain + ledger_api = wallet.ledger_api( + chain_type=chain_type, + rpc=chain_config.ledger_config.rpc, + ) + os.environ["CUSTOM_CHAIN_RPC"] = chain_config.ledger_config.rpc + os.environ["OPEN_AUTONOMY_SUBGRAPH_URL"] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" + os.environ["MAX_PRIORITY_FEE_PER_GAS"] = chain_metadata["gasParams"]["MAX_PRIORITY_FEE_PER_GAS"] + os.environ["MAX_FEE_PER_GAS"] = chain_metadata["gasParams"]["MAX_FEE_PER_GAS"] + service_exists = manager._get_on_chain_state(chain_config) != OnChainState.NON_EXISTENT + + chain_name, token = chain_metadata['name'], chain_metadata["token"] + balance_str = wei_to_token(ledger_api.get_balance(wallet.crypto.address), token) + print( + f"[{chain_name}] Main wallet balance: {balance_str}", + ) + safe_exists = wallet.safes.get(chain_type) is not None + required_balance = chain_metadata["firstTimeTopUp"] if not safe_exists else chain_metadata["operationalFundReq"] + print( + f"[{chain_name}] Please make sure main wallet {wallet.crypto.address} has at least {wei_to_token(required_balance, token)}", + ) + spinner = Halo( + text=f"[{chain_name}] Waiting for funds...", + spinner="dots" + ) + spinner.start() + + while ledger_api.get_balance(wallet.crypto.address) < required_balance: + time.sleep(1) + + spinner.succeed(f"[{chain_name}] Main wallet updated balance: {wei_to_token(ledger_api.get_balance(wallet.crypto.address), token)}.") + print() + + if not safe_exists: + print(f"[{chain_name}] Creating Safe") + ledger_type = LedgerType.ETHEREUM + wallet_manager = operate.wallet_manager + wallet = wallet_manager.load(ledger_type=ledger_type) + + wallet.create_safe( # pylint: disable=no-member + chain_type=chain_type, + rpc=chain_config.ledger_config.rpc, + ) + + print_section(f"[{chain_name}] Set up the service in the Olas Protocol") + + address = wallet.safes[chain_type] + if not service_exists: + first_time_top_up = chain_metadata["firstTimeTopUp"] + print( + f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(first_time_top_up, token)}." + ) + spinner = Halo( + text=f"[{chain_name}] Waiting for funds...", + spinner="dots", + ) + spinner.start() + + while ledger_api.get_balance(address) < first_time_top_up: + print(f"[{chain_name}] Funding Safe") + wallet.transfer( + to=t.cast(str, wallet.safes[chain_type]), + amount=int(chain_metadata["firstTimeTopUp"]), + chain_type=chain_type, + from_safe=False, + rpc=chain_config.ledger_config.rpc, + ) + time.sleep(1) + + spinner.succeed(f"[{chain_name}] Safe updated balance: {wei_to_token(ledger_api.get_balance(address), token)}.") + + if chain_config.chain_data.user_params.use_staking and not service_exists: + print(f"[{chain_name}] Please make sure address {address} has at least {wei_to_token(2 * COST_OF_BOND_STAKING, STAKED_BONDING_TOKEN)}") + + spinner = Halo( + text=f"[{chain_name}] Waiting for {STAKED_BONDING_TOKEN}...", + spinner="dots", + ) + spinner.start() + olas_address = OLAS[chain_type] + while get_erc20_balance(ledger_api, olas_address, address) < 2 * COST_OF_BOND_STAKING: + time.sleep(1) + + balance = get_erc20_balance(ledger_api, olas_address, address) / 10 ** 18 + spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} {STAKED_BONDING_TOKEN}") + + + if chain_metadata.get("usdcRequired", False) and not service_exists: + print(f"[{chain_name}] Please make sure address {address} has at least 10 USDC") + + spinner = Halo( + text=f"[{chain_name}] Waiting for USDC...", + spinner="dots", + ) + spinner.start() + + while get_erc20_balance(ledger_api, USDC_ADDRESS, address) < USDC_REQUIRED: + time.sleep(1) + + balance = get_erc20_balance(ledger_api, USDC_ADDRESS, address) / 10 ** 6 + spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} USDC.") + + manager.deploy_service_onchain_from_safe_single_chain( + hash=service.hash, + chain_id=chain_id, + fallback_staking_params=FALLBACK_STAKING_PARAMS, + ) + manager.fund_service(hash=service.hash, chain_id=chain_id) + + safes = { + ChainType.from_id(int(chain)).name.lower(): config.chain_data.multisig + for chain, config in service.chain_configs.items() + } + home_chain_id = service.home_chain_id + home_chain_type = ChainType.from_id(int(home_chain_id)) + target_staking_program_id = service.chain_configs[home_chain_id].chain_data.user_params.staking_program_id + env_vars = { + "SAFE_CONTRACT_ADDRESSES": json.dumps(safes, separators=(',', ':')), + "TENDERLY_ACCESS_KEY": optimus_config.tenderly_access_key, + "TENDERLY_ACCOUNT_SLUG": optimus_config.tenderly_account_slug, + "TENDERLY_PROJECT_SLUG": optimus_config.tenderly_project_slug, + "STAKING_TOKEN_CONTRACT_ADDRESS": STAKING[home_chain_type][target_staking_program_id], + } + apply_env_vars(env_vars) + print("Skipping local deployment") + service.deployment.build(use_docker=True, force=True, chain_id=home_chain_id) + docker_compose_path = service.path / "deployment" / "docker-compose.yaml" + add_volumes(docker_compose_path, str(OPERATE_HOME), "/data") + service.deployment.start(use_docker=True) + + print() + print_section("Running the service") + + +if __name__ == "__main__": + main() From a5455c1e5eabd91038f4e0060623599cd745d817 Mon Sep 17 00:00:00 2001 From: Ardian Date: Tue, 17 Sep 2024 23:44:41 +0200 Subject: [PATCH 16/19] feat: add erc20 --- operate/services/manage.py | 68 ++ operate/utils/gnosis.py | 35 +- operate/wallet/master.py | 49 +- poetry.lock | 1434 +++++++++++++++++++----------------- pyproject.toml | 10 +- run_service.py | 109 ++- 6 files changed, 1011 insertions(+), 694 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index ce6270de..65b2ee95 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -33,6 +33,7 @@ from aea.helpers.base import IPFSHash from aea.helpers.logging import setup_logger from autonomy.chain.base import registry_contracts +from pycparser.ply.yacc import token from operate.keys import Key, KeysManager from operate.ledger import PUBLIC_RPCS @@ -1102,6 +1103,73 @@ def fund_service( # pylint: disable=too-many-arguments rpc=rpc or ledger_config.rpc, ) + def fund_service_erc20( # pylint: disable=too-many-arguments + self, + hash: str, + token: str, + rpc: t.Optional[str] = None, + agent_topup: t.Optional[float] = None, + safe_topup: t.Optional[float] = None, + agent_fund_threshold: t.Optional[float] = None, + safe_fund_treshold: t.Optional[float] = None, + from_safe: bool = True, + chain_id: str = "10", + ) -> None: + """Fund service if required.""" + service = self.load_or_create(hash=hash) + chain_config = service.chain_configs[chain_id] + ledger_config = chain_config.ledger_config + chain_data = chain_config.chain_data + wallet = self.wallet_manager.load(ledger_config.type) + ledger_api = wallet.ledger_api(chain_type=ledger_config.chain, rpc=rpc or ledger_config.rpc) + agent_fund_threshold = ( + agent_fund_threshold + or chain_data.user_params.fund_requirements.agent + ) + + for key in service.keys: + agent_balance = ledger_api.get_balance(address=key.address) + self.logger.info(f"Agent {key.address} balance: {agent_balance}") + self.logger.info(f"Required balance: {agent_fund_threshold}") + if agent_balance < agent_fund_threshold: + self.logger.info("Funding agents") + to_transfer = ( + agent_topup + or chain_data.user_params.fund_requirements.agent + ) + self.logger.info(f"Transferring {to_transfer} units to {key.address}") + wallet.transfer_erc20( + token=token, + to=key.address, + amount=int(to_transfer), + chain_type=ledger_config.chain, + from_safe=from_safe, + rpc=rpc or ledger_config.rpc, + ) + + safe_balance = registry_contracts.erc20.get_instance(ledger_api, token).functions.balanceOf(chain_data.multisig).call() + safe_fund_treshold = ( + safe_fund_treshold or chain_data.user_params.fund_requirements.safe + ) + self.logger.info(f"Safe {chain_data.multisig} balance: {safe_balance}") + self.logger.info(f"Required balance: {safe_fund_treshold}") + if safe_balance < safe_fund_treshold: + self.logger.info("Funding safe") + to_transfer = ( + safe_topup or chain_data.user_params.fund_requirements.safe + ) + self.logger.info( + f"Transferring {to_transfer} units to {chain_data.multisig}" + ) + wallet.transfer_erc20( + token=token, + to=t.cast(str, chain_data.multisig), + amount=int(to_transfer), + chain_type=ledger_config.chain, + rpc=rpc or ledger_config.rpc, + ) + + async def funding_job( self, hash: str, diff --git a/operate/utils/gnosis.py b/operate/utils/gnosis.py index 9285f291..3fffba0f 100644 --- a/operate/utils/gnosis.py +++ b/operate/utils/gnosis.py @@ -215,17 +215,19 @@ def send_safe_txs( safe: str, ledger_api: LedgerApi, crypto: Crypto, + to: t.Optional[str] = None ) -> None: """Send internal safe transaction.""" owner = ledger_api.api.to_checksum_address( crypto.address, ) + to_address = to or safe safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( ledger_api=ledger_api, contract_address=safe, value=0, safe_tx_gas=0, - to_address=safe, + to_address=to_address, data=txd, operation=SafeOperation.CALL.value, ).get("tx_hash") @@ -245,7 +247,7 @@ def send_safe_txs( contract_address=safe, sender_address=owner, owners=(owner,), # type: ignore - to_address=safe, + to_address=to_address, value=0, data=txd, safe_tx_gas=0, @@ -354,3 +356,32 @@ def transfer( ), ) ) + +def transfer_erc20_from_safe( + ledger_api: LedgerApi, + crypto: Crypto, + safe: str, + token: str, + to: str, + amount: t.Union[float, int], +) -> None: + """Transfer ERC20 assets from safe to given address.""" + amount = int(amount) + instance = registry_contracts.erc20.get_instance( + ledger_api=ledger_api, + contract_address=token, + ) + txd = instance.encodeABI( + fn_name="transfer", + args=[ + to, + amount, + ], + ) + send_safe_txs( + txd=bytes.fromhex(txd[2:]), + safe=safe, + ledger_api=ledger_api, + crypto=crypto, + to=token, + ) diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 65b0e85a..4c98155c 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -40,7 +40,7 @@ from operate.ledger import get_default_rpc from operate.resource import LocalResource from operate.types import ChainType, LedgerType -from operate.utils.gnosis import add_owner +from operate.utils.gnosis import add_owner, transfer_erc20_from_safe from operate.utils.gnosis import create_safe as create_gnosis_safe from operate.utils.gnosis import get_owners, swap_owner from operate.utils.gnosis import transfer as transfer_from_safe @@ -105,6 +105,19 @@ def transfer( """Transfer funds to the given account.""" raise NotImplementedError() + def transfer_erc20( + self, + token: str, + to: str, + amount: int, + chain_type: ChainType, + from_safe: bool = True, + rpc: t.Optional[str] = None, + ) -> None: + """Transfer funds to the given account.""" + raise NotImplementedError() + + @staticmethod def new(password: str, path: Path) -> t.Tuple["MasterWallet", t.List[str]]: """Create a new master wallet.""" @@ -211,6 +224,20 @@ def _transfer_from_safe(self, to: str, amount: int, chain_type: ChainType, rpc: amount=amount, ) + def _transfer_erc20_from_safe( + self, token: str, to: str, amount: int, chain_type: ChainType, rpc: t.Optional[str] = None + ) -> None: + """Transfer funds from safe wallet.""" + transfer_erc20_from_safe( + ledger_api=self.ledger_api(chain_type=chain_type, rpc=rpc), + crypto=self.crypto, + token=token, + safe=t.cast(str, self.safes[chain_type]), + to=to, + amount=amount, + ) + + def transfer( self, to: str, @@ -234,6 +261,26 @@ def transfer( rpc=rpc, ) + def transfer_erc20( + self, + token: str, + to: str, + amount: int, + chain_type: ChainType, + from_safe: bool = True, + rpc: t.Optional[str] = None, + ) -> None: + """Transfer funds to the given account.""" + if not from_safe: + raise NotImplementedError() + return self._transfer_erc20_from_safe( + token=token, + to=to, + amount=amount, + chain_type=chain_type, + rpc=rpc, + ) + @classmethod def new( cls, password: str, path: Path diff --git a/poetry.lock b/poetry.lock index eef74897..f15a2859 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -196,22 +196,22 @@ files = [ [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "backoff" @@ -422,74 +422,89 @@ files = [ [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -712,63 +727,83 @@ requests = "*" [[package]] name = "coverage" -version = "7.6.0" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, - {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, - {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, - {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, - {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, - {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, - {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, - {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, - {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, - {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, - {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, - {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, - {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, - {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -776,38 +811,38 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "43.0.0" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, - {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, - {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, - {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, - {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, - {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, - {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -820,7 +855,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1222,13 +1257,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.0.1" +version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, ] [package.extras] @@ -1255,19 +1290,19 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "filelock" -version = "3.15.4" +version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "flask" @@ -1379,13 +1414,13 @@ files = [ [[package]] name = "googleapis-common-protos" -version = "1.63.2" +version = "1.65.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, - {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, + {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, + {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, ] [package.dependencies] @@ -1424,72 +1459,72 @@ websockets = ["websockets (>=10,<12)"] [[package]] name = "graphql-core" -version = "3.2.3" +version = "3.2.4" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." optional = false -python-versions = ">=3.6,<4" +python-versions = "<4,>=3.6" files = [ - {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, - {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, + {file = "graphql-core-3.2.4.tar.gz", hash = "sha256:acbe2e800980d0e39b4685dd058c2f4042660b89ebca38af83020fd872ff1264"}, + {file = "graphql_core-3.2.4-py3-none-any.whl", hash = "sha256:1604f2042edc5f3114f49cac9d77e25863be51b23a54a61a23245cf32f6476f0"}, ] [[package]] name = "grpcio" -version = "1.65.1" +version = "1.66.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.65.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:3dc5f928815b8972fb83b78d8db5039559f39e004ec93ebac316403fe031a062"}, - {file = "grpcio-1.65.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:8333ca46053c35484c9f2f7e8d8ec98c1383a8675a449163cea31a2076d93de8"}, - {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:7af64838b6e615fff0ec711960ed9b6ee83086edfa8c32670eafb736f169d719"}, - {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb64b4166362d9326f7efbf75b1c72106c1aa87f13a8c8b56a1224fac152f5c"}, - {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8422dc13ad93ec8caa2612b5032a2b9cd6421c13ed87f54db4a3a2c93afaf77"}, - {file = "grpcio-1.65.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4effc0562b6c65d4add6a873ca132e46ba5e5a46f07c93502c37a9ae7f043857"}, - {file = "grpcio-1.65.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a6c71575a2fedf259724981fd73a18906513d2f306169c46262a5bae956e6364"}, - {file = "grpcio-1.65.1-cp310-cp310-win32.whl", hash = "sha256:34966cf526ef0ea616e008d40d989463e3db157abb213b2f20c6ce0ae7928875"}, - {file = "grpcio-1.65.1-cp310-cp310-win_amd64.whl", hash = "sha256:ca931de5dd6d9eb94ff19a2c9434b23923bce6f767179fef04dfa991f282eaad"}, - {file = "grpcio-1.65.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:bbb46330cc643ecf10bd9bd4ca8e7419a14b6b9dedd05f671c90fb2c813c6037"}, - {file = "grpcio-1.65.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d827a6fb9215b961eb73459ad7977edb9e748b23e3407d21c845d1d8ef6597e5"}, - {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:6e71aed8835f8d9fbcb84babc93a9da95955d1685021cceb7089f4f1e717d719"}, - {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1c84560b3b2d34695c9ba53ab0264e2802721c530678a8f0a227951f453462"}, - {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27adee2338d697e71143ed147fe286c05810965d5d30ec14dd09c22479bfe48a"}, - {file = "grpcio-1.65.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f62652ddcadc75d0e7aa629e96bb61658f85a993e748333715b4ab667192e4e8"}, - {file = "grpcio-1.65.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:71a05fd814700dd9cb7d9a507f2f6a1ef85866733ccaf557eedacec32d65e4c2"}, - {file = "grpcio-1.65.1-cp311-cp311-win32.whl", hash = "sha256:b590f1ad056294dfaeac0b7e1b71d3d5ace638d8dd1f1147ce4bd13458783ba8"}, - {file = "grpcio-1.65.1-cp311-cp311-win_amd64.whl", hash = "sha256:12e9bdf3b5fd48e5fbe5b3da382ad8f97c08b47969f3cca81dd9b36b86ed39e2"}, - {file = "grpcio-1.65.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:54cb822e177374b318b233e54b6856c692c24cdbd5a3ba5335f18a47396bac8f"}, - {file = "grpcio-1.65.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aaf3c54419a28d45bd1681372029f40e5bfb58e5265e3882eaf21e4a5f81a119"}, - {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:557de35bdfbe8bafea0a003dbd0f4da6d89223ac6c4c7549d78e20f92ead95d9"}, - {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8bfd95ef3b097f0cc86ade54eafefa1c8ed623aa01a26fbbdcd1a3650494dd11"}, - {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e6a8f3d6c41e6b642870afe6cafbaf7b61c57317f9ec66d0efdaf19db992b90"}, - {file = "grpcio-1.65.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1faaf7355ceed07ceaef0b9dcefa4c98daf1dd8840ed75c2de128c3f4a4d859d"}, - {file = "grpcio-1.65.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:60f1f38eed830488ad2a1b11579ef0f345ff16fffdad1d24d9fbc97ba31804ff"}, - {file = "grpcio-1.65.1-cp312-cp312-win32.whl", hash = "sha256:e75acfa52daf5ea0712e8aa82f0003bba964de7ae22c26d208cbd7bc08500177"}, - {file = "grpcio-1.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff5a84907e51924973aa05ed8759210d8cdae7ffcf9e44fd17646cf4a902df59"}, - {file = "grpcio-1.65.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1fbd6331f18c3acd7e09d17fd840c096f56eaf0ef830fbd50af45ae9dc8dfd83"}, - {file = "grpcio-1.65.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:de5b6be29116e094c5ef9d9e4252e7eb143e3d5f6bd6d50a78075553ab4930b0"}, - {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e4a3cdba62b2d6aeae6027ae65f350de6dc082b72e6215eccf82628e79efe9ba"}, - {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941c4869aa229d88706b78187d60d66aca77fe5c32518b79e3c3e03fc26109a2"}, - {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f40cebe5edb518d78b8131e87cb83b3ee688984de38a232024b9b44e74ee53d3"}, - {file = "grpcio-1.65.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2ca684ba331fb249d8a1ce88db5394e70dbcd96e58d8c4b7e0d7b141a453dce9"}, - {file = "grpcio-1.65.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8558f0083ddaf5de64a59c790bffd7568e353914c0c551eae2955f54ee4b857f"}, - {file = "grpcio-1.65.1-cp38-cp38-win32.whl", hash = "sha256:8d8143a3e3966f85dce6c5cc45387ec36552174ba5712c5dc6fcc0898fb324c0"}, - {file = "grpcio-1.65.1-cp38-cp38-win_amd64.whl", hash = "sha256:76e81a86424d6ca1ce7c16b15bdd6a964a42b40544bf796a48da241fdaf61153"}, - {file = "grpcio-1.65.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb5175f45c980ff418998723ea1b3869cce3766d2ab4e4916fbd3cedbc9d0ed3"}, - {file = "grpcio-1.65.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b12c1aa7b95abe73b3e04e052c8b362655b41c7798da69f1eaf8d186c7d204df"}, - {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:3019fb50128b21a5e018d89569ffaaaa361680e1346c2f261bb84a91082eb3d3"}, - {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ae15275ed98ea267f64ee9ddedf8ecd5306a5b5bb87972a48bfe24af24153e8"}, - {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f096ffb881f37e8d4f958b63c74bfc400c7cebd7a944b027357cd2fb8d91a57"}, - {file = "grpcio-1.65.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2f56b5a68fdcf17a0a1d524bf177218c3c69b3947cb239ea222c6f1867c3ab68"}, - {file = "grpcio-1.65.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:941596d419b9736ab548aa0feb5bbba922f98872668847bf0720b42d1d227b9e"}, - {file = "grpcio-1.65.1-cp39-cp39-win32.whl", hash = "sha256:5fd7337a823b890215f07d429f4f193d24b80d62a5485cf88ee06648591a0c57"}, - {file = "grpcio-1.65.1-cp39-cp39-win_amd64.whl", hash = "sha256:1bceeec568372cbebf554eae1b436b06c2ff24cfaf04afade729fb9035408c6c"}, - {file = "grpcio-1.65.1.tar.gz", hash = "sha256:3c492301988cd720cd145d84e17318d45af342e29ef93141228f9cd73222368b"}, + {file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"}, + {file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a013c5fbb12bfb5f927444b477a26f1080755a931d5d362e6a9a720ca7dbae60"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1b24c23d51a1e8790b25514157d43f0a4dce1ac12b3f0b8e9f66a5e2c4c132f"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffb8ea674d68de4cac6f57d2498fef477cef582f1fa849e9f844863af50083"}, + {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:307b1d538140f19ccbd3aed7a93d8f71103c5d525f3c96f8616111614b14bf2a"}, + {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c17ebcec157cfb8dd445890a03e20caf6209a5bd4ac5b040ae9dbc59eef091d"}, + {file = "grpcio-1.66.1-cp310-cp310-win32.whl", hash = "sha256:ef82d361ed5849d34cf09105d00b94b6728d289d6b9235513cb2fcc79f7c432c"}, + {file = "grpcio-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:292a846b92cdcd40ecca46e694997dd6b9be6c4c01a94a0dfb3fcb75d20da858"}, + {file = "grpcio-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:c30aeceeaff11cd5ddbc348f37c58bcb96da8d5aa93fed78ab329de5f37a0d7a"}, + {file = "grpcio-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a1e224ce6f740dbb6b24c58f885422deebd7eb724aff0671a847f8951857c26"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a66fe4dc35d2330c185cfbb42959f57ad36f257e0cc4557d11d9f0a3f14311df"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ba04659e4fce609de2658fe4dbf7d6ed21987a94460f5f92df7579fd5d0e22"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4573608e23f7e091acfbe3e84ac2045680b69751d8d67685ffa193a4429fedb1"}, + {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e06aa1f764ec8265b19d8f00140b8c4b6ca179a6dc67aa9413867c47e1fb04e"}, + {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3885f037eb11f1cacc41f207b705f38a44b69478086f40608959bf5ad85826dd"}, + {file = "grpcio-1.66.1-cp311-cp311-win32.whl", hash = "sha256:97ae7edd3f3f91480e48ede5d3e7d431ad6005bfdbd65c1b56913799ec79e791"}, + {file = "grpcio-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:cfd349de4158d797db2bd82d2020554a121674e98fbe6b15328456b3bf2495bb"}, + {file = "grpcio-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:a92c4f58c01c77205df6ff999faa008540475c39b835277fb8883b11cada127a"}, + {file = "grpcio-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fdb14bad0835914f325349ed34a51940bc2ad965142eb3090081593c6e347be9"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f03a5884c56256e08fd9e262e11b5cfacf1af96e2ce78dc095d2c41ccae2c80d"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2559692d8e7e245d456877a85ee41525f3ed425aa97eb7a70fc9a79df91a0"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca1be089fb4446490dd1135828bd42a7c7f8421e74fa581611f7afdf7ab761"}, + {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d639c939ad7c440c7b2819a28d559179a4508783f7e5b991166f8d7a34b52815"}, + {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9feb4e5ec8dc2d15709f4d5fc367794d69277f5d680baf1910fc9915c633524"}, + {file = "grpcio-1.66.1-cp312-cp312-win32.whl", hash = "sha256:7101db1bd4cd9b880294dec41a93fcdce465bdbb602cd8dc5bd2d6362b618759"}, + {file = "grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734"}, + {file = "grpcio-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:ecfe735e7a59e5a98208447293ff8580e9db1e890e232b8b292dc8bd15afc0d2"}, + {file = "grpcio-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4825a3aa5648010842e1c9d35a082187746aa0cdbf1b7a2a930595a94fb10fce"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f517fd7259fe823ef3bd21e508b653d5492e706e9f0ef82c16ce3347a8a5620c"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fe60d0772831d96d263b53d83fb9a3d050a94b0e94b6d004a5ad111faa5b5b"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31a049daa428f928f21090403e5d18ea02670e3d5d172581670be006100db9ef"}, + {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f914386e52cbdeb5d2a7ce3bf1fdfacbe9d818dd81b6099a05b741aaf3848bb"}, + {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bff2096bdba686019fb32d2dde45b95981f0d1490e054400f70fc9a8af34b49d"}, + {file = "grpcio-1.66.1-cp38-cp38-win32.whl", hash = "sha256:aa8ba945c96e73de29d25331b26f3e416e0c0f621e984a3ebdb2d0d0b596a3b3"}, + {file = "grpcio-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:161d5c535c2bdf61b95080e7f0f017a1dfcb812bf54093e71e5562b16225b4ce"}, + {file = "grpcio-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:d0cd7050397b3609ea51727b1811e663ffda8bda39c6a5bb69525ef12414b503"}, + {file = "grpcio-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0e6c9b42ded5d02b6b1fea3a25f036a2236eeb75d0579bfd43c0018c88bf0a3e"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c9f80f9fad93a8cf71c7f161778ba47fd730d13a343a46258065c4deb4b550c0"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dd67ed9da78e5121efc5c510f0122a972216808d6de70953a740560c572eb44"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48b0d92d45ce3be2084b92fb5bae2f64c208fea8ceed7fccf6a7b524d3c4942e"}, + {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d813316d1a752be6f5c4360c49f55b06d4fe212d7df03253dfdae90c8a402bb"}, + {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c9bebc6627873ec27a70fc800f6083a13c70b23a5564788754b9ee52c5aef6c"}, + {file = "grpcio-1.66.1-cp39-cp39-win32.whl", hash = "sha256:30a1c2cf9390c894c90bbc70147f2372130ad189cffef161f0432d0157973f45"}, + {file = "grpcio-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:17663598aadbedc3cacd7bbde432f541c8e07d2496564e22b214b22c7523dac8"}, + {file = "grpcio-1.66.1.tar.gz", hash = "sha256:35334f9c9745add3e357e3372756fd32d925bd52c41da97f4dfdafbde0bf0ee2"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.65.1)"] +protobuf = ["grpcio-tools (>=1.66.1)"] [[package]] name = "h11" @@ -1559,33 +1594,40 @@ pygments = ">=2.2.0" [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {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 = "importlib-metadata" -version = "8.2.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -1992,18 +2034,18 @@ nicer-shell = ["ipython"] [[package]] name = "open-aea" -version = "1.53.0" +version = "1.56.0" description = "Open Autonomous Economic Agent framework (without vendor lock-in)" optional = false python-versions = ">=3.8" files = [ - {file = "open_aea-1.53.0-py3-none-any.whl", hash = "sha256:1244855875effe09e2c965750ac401a973a1ae56cdc880045eea70f19ed90dcb"}, - {file = "open_aea-1.53.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d0a848e33798599953dc764d37083fc37642140a91624e06a56b8d2f95ae6cb9"}, - {file = "open_aea-1.53.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:48f5715f13a0389799b9461bc776e9638e6ae38173d0f2bf6d28bad637232914"}, - {file = "open_aea-1.53.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:87e4ae903ec9a7268eba0ead449f9693cb5bedb6dfc5846c90f94f2354bd35ab"}, - {file = "open_aea-1.53.0-py3-none-win32.whl", hash = "sha256:182e9f8a77fe8efcc768360ac7308a0951ec16bcb12e2ad4f55886b12258d702"}, - {file = "open_aea-1.53.0-py3-none-win_amd64.whl", hash = "sha256:cef9d4d177ea7d959ebacab962fbd1d03d7243c0b72d10a3ee7850df8d922e06"}, - {file = "open_aea-1.53.0.tar.gz", hash = "sha256:6fcb82ceed0b6841c0f41d116b207308556e3d18807fe140e9ef1a898c7a8a0b"}, + {file = "open_aea-1.56.0-py3-none-any.whl", hash = "sha256:453ffdff37310c03f833e9059c9a38771e11e9f6334b4c44223f2a5df78cd3b9"}, + {file = "open_aea-1.56.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:ecbfd23628a1dc6151bfd95795347817884a4b833e89f1a8c82edb11abc43632"}, + {file = "open_aea-1.56.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:fc8280faebb411356ccc10d42923ea347907bd321b3b709caf2d66ef792c30b7"}, + {file = "open_aea-1.56.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5e029c428533d30ab63ea4c02a0d9f60c4d782fdb02c0812aa7a43c56f09fbf0"}, + {file = "open_aea-1.56.0-py3-none-win32.whl", hash = "sha256:a863ce340a04b0fbde58f14157adc6f268af82586ed7ee4fcb50bfaa1024c432"}, + {file = "open_aea-1.56.0-py3-none-win_amd64.whl", hash = "sha256:9be33a5aa932c1800df336861d3c8913ec170d1708d8b01f65ee988edd867201"}, + {file = "open_aea-1.56.0.tar.gz", hash = "sha256:65559011b27ab42c711be9046c7a51c65d629ce57999f105186c0e224706ef15"}, ] [package.dependencies] @@ -2020,7 +2062,7 @@ py-multicodec = ">=0.2.0" pymultihash = "0.8.2" pytest = {version = ">=7.0.0,<7.3.0", optional = true, markers = "extra == \"all\""} python-dotenv = ">=0.14.0,<1.0.1" -pyyaml = {version = ">=6.0.1,<7", optional = true, markers = "extra == \"all\""} +pyyaml = ">=6.0.1,<7" requests = ">=2.28.1,<3" semver = ">=2.9.1,<3.0.0" @@ -2031,13 +2073,13 @@ test-tools = ["click (>=8.1.0,<9)", "coverage (>=6.4.4,<8.0.0)", "jsonschema (>= [[package]] name = "open-aea-cli-ipfs" -version = "1.53.0" +version = "1.56.0" description = "CLI extension for open AEA framework wrapping IPFS functionality." optional = false python-versions = "*" files = [ - {file = "open_aea_cli_ipfs-1.53.0-py3-none-any.whl", hash = "sha256:335a02473d1c6820bb63a31e2931749ba644f586a7acbc650da9efc4c7168efa"}, - {file = "open_aea_cli_ipfs-1.53.0.tar.gz", hash = "sha256:499a66a45925064e6946ec72f482182390263cc7726409832f1317fe831b6289"}, + {file = "open_aea_cli_ipfs-1.56.0-py3-none-any.whl", hash = "sha256:23b88c1d5e431d37966ecfe72eb6a6b3456bfa22536c5163606e1b64c312f097"}, + {file = "open_aea_cli_ipfs-1.56.0.tar.gz", hash = "sha256:7ee9e265222616a4ff21187c2a191d2645f57c4cd6eea629824869f65e731278"}, ] [package.dependencies] @@ -2062,13 +2104,13 @@ web3 = ">=6.0.0,<7" [[package]] name = "open-aea-ledger-cosmos" -version = "1.53.0" +version = "1.56.0" description = "Python package wrapping the public and private key cryptography and ledger api of Cosmos." optional = false python-versions = "*" files = [ - {file = "open_aea_ledger_cosmos-1.53.0-py3-none-any.whl", hash = "sha256:79b2eafc8723593634b72973da39e3e3c01bceae04b330b51a5059eb1200262b"}, - {file = "open_aea_ledger_cosmos-1.53.0.tar.gz", hash = "sha256:14a032918d7f0659fedbe21a53fa668b24505bb9e97358340b20325943f9fa26"}, + {file = "open_aea_ledger_cosmos-1.56.0-py3-none-any.whl", hash = "sha256:695a73a05473ef12d1802711c5116d68cf16ecb586fb7ec16ca7c3d4a63d9462"}, + {file = "open_aea_ledger_cosmos-1.56.0.tar.gz", hash = "sha256:75738429936d3372ad17643a8149b2858dfbd16e298b6fc409271060b16eab8c"}, ] [package.dependencies] @@ -2080,13 +2122,13 @@ pycryptodome = ">=3.10.1,<4.0.0" [[package]] name = "open-aea-ledger-ethereum" -version = "1.53.0" +version = "1.56.0" description = "Python package wrapping the public and private key cryptography and ledger api of Ethereum." optional = false python-versions = "*" files = [ - {file = "open_aea_ledger_ethereum-1.53.0-py3-none-any.whl", hash = "sha256:501eb9e9e228552bf9f6f8811d780f33dbbf477ddb33b63d335caaeb24840274"}, - {file = "open_aea_ledger_ethereum-1.53.0.tar.gz", hash = "sha256:734f335061c33a171b945ab1bcea5d47be9b9532457ed361b3aab727e85bca0d"}, + {file = "open_aea_ledger_ethereum-1.56.0-py3-none-any.whl", hash = "sha256:6118828453c7dbd24330d782194e0ada607a8fa27c28f73fd60a85db180546e7"}, + {file = "open_aea_ledger_ethereum-1.56.0.tar.gz", hash = "sha256:b5b77f89ea2f85450e3c3c1b769adcadd5548831acdb56beaea0459e512bf80d"}, ] [package.dependencies] @@ -2097,28 +2139,28 @@ web3 = ">=6.0.0,<7" [[package]] name = "open-aea-ledger-ethereum-flashbots" -version = "1.53.0" +version = "1.56.0" description = "Python package extending the default open-aea ethereum ledger plugin to add support for flashbots." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "open_aea_ledger_ethereum_flashbots-1.53.0-py3-none-any.whl", hash = "sha256:04e1d04b68159f092dbe8c94d419221ad45bac17497c8feed9cbe92270a675b3"}, - {file = "open_aea_ledger_ethereum_flashbots-1.53.0.tar.gz", hash = "sha256:683e0c73c04413a0098f4903e3afa8c1570f8c51591ad6cef66fd38cfad463d8"}, + {file = "open_aea_ledger_ethereum_flashbots-1.56.0-py3-none-any.whl", hash = "sha256:33baa373d66e04f729fab114736efcfd0ba0de6a9bb9f9b8ff1e9115bd189216"}, + {file = "open_aea_ledger_ethereum_flashbots-1.56.0.tar.gz", hash = "sha256:dab5a4261200fcee9e51ee1b5a7be4f2ae3e28bf0f0e53370d807e15f8d9b5a9"}, ] [package.dependencies] open-aea-flashbots = "1.4.0" -open-aea-ledger-ethereum = ">=1.53.0,<1.54.0" +open-aea-ledger-ethereum = ">=1.56.0,<1.57.0" [[package]] name = "open-autonomy" -version = "0.14.14.post2" +version = "0.16.0" description = "A framework for the creation of autonomous agent services." optional = false python-versions = ">=3.8" files = [ - {file = "open_autonomy-0.14.14.post2-py3-none-any.whl", hash = "sha256:76efc0ce814c748eb02176477eef5b5ca4c6a84d77230e01a40c3df5ad2254b8"}, - {file = "open_autonomy-0.14.14.post2.tar.gz", hash = "sha256:0e1e7f3207fde38b8778a260c43153737a3b9fefe6d7abf373710a1aae9e267b"}, + {file = "open_autonomy-0.16.0-py3-none-any.whl", hash = "sha256:a9107402e749e3a69cd36990909f10ae4d660be23a0206ad99630fc1f2d86107"}, + {file = "open_autonomy-0.16.0.tar.gz", hash = "sha256:493f761de2ef7fd09e66c26a96c404dfab59e46f392d823aab16780e7e9e3403"}, ] [package.dependencies] @@ -2130,8 +2172,8 @@ Flask = ">=2.0.2,<3.0.0" gql = "3.5.0" hexbytes = "*" jsonschema = ">=4.3.0,<4.4.0" -open-aea = {version = "1.53.0", extras = ["all"]} -open-aea-cli-ipfs = "1.53.0" +open-aea = {version = "1.56.0", extras = ["all"]} +open-aea-cli-ipfs = "1.56.0" protobuf = ">=4.21.6,<4.25.0" pytest = "7.2.1" python-dotenv = ">=0.14.5,<0.22.0" @@ -2144,8 +2186,8 @@ watchdog = ">=2.1.6" werkzeug = "2.0.3" [package.extras] -all = ["click (>=8.1.0,<9)", "coverage (>=6.4.4,<8.0.0)", "open-aea-cli-ipfs (==1.53.0)", "pytest (>=7.0.0,<7.3.0)", "python-dotenv (>=0.14.5,<0.22.0)", "texttable (==1.6.7)"] -cli = ["click (>=8.1.0,<9)", "coverage (>=6.4.4,<8.0.0)", "open-aea-cli-ipfs (==1.53.0)", "pytest (>=7.0.0,<7.3.0)", "python-dotenv (>=0.14.5,<0.22.0)", "texttable (==1.6.7)"] +all = ["click (>=8.1.0,<9)", "coverage (>=6.4.4,<8.0.0)", "open-aea-cli-ipfs (==1.56.0)", "pytest (>=7.0.0,<7.3.0)", "python-dotenv (>=0.14.5,<0.22.0)", "texttable (==1.6.7)"] +cli = ["click (>=8.1.0,<9)", "coverage (>=6.4.4,<8.0.0)", "open-aea-cli-ipfs (==1.56.0)", "pytest (>=7.0.0,<7.3.0)", "python-dotenv (>=0.14.5,<0.22.0)", "texttable (==1.6.7)"] [[package]] name = "packaging" @@ -2160,13 +2202,13 @@ files = [ [[package]] name = "paramiko" -version = "3.4.0" +version = "3.5.0" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ - {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, - {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, + {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, + {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, ] [package.dependencies] @@ -2195,30 +2237,30 @@ regex = ">=2022.3.15" [[package]] name = "pefile" -version = "2023.2.7" +version = "2024.8.26" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" files = [ - {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, - {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, + {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, + {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, ] [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" @@ -2382,119 +2424,120 @@ files = [ [[package]] name = "pydantic" -version = "2.8.2" +version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [package.dependencies] @@ -2516,23 +2559,23 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyinstaller" -version = "6.9.0" +version = "6.10.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false -python-versions = "<3.13,>=3.8" +python-versions = "<3.14,>=3.8" files = [ - {file = "pyinstaller-6.9.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ced2e83acf222b936ea94abc5a5cc96588705654b39138af8fb321d9cf2b954"}, - {file = "pyinstaller-6.9.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:f18a3d551834ef8fb7830d48d4cc1527004d0e6b51ded7181e78374ad6111846"}, - {file = "pyinstaller-6.9.0-py3-none-manylinux2014_i686.whl", hash = "sha256:f2fc568de3d6d2a176716a3fc9f20da06d351e8bea5ddd10ecb5659fce3a05b0"}, - {file = "pyinstaller-6.9.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:a0f378f64ad0655d11ade9fde7877e7573fd3d5066231608ce7dfa9040faecdd"}, - {file = "pyinstaller-6.9.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:7bf0c13c5a8560c89540746ae742f4f4b82290e95a6b478374d9f34959fe25d6"}, - {file = "pyinstaller-6.9.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:da994aba14c5686db88796684de265a8665733b4df09b939f7ebdf097d18df72"}, - {file = "pyinstaller-6.9.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:4e3e50743c091a06e6d01c59bdd6d03967b453ee5384a9e790759be4129db4a4"}, - {file = "pyinstaller-6.9.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b041be2fe78da47a269604d62c940d68c62f9a3913bdf64af4123f7689d47099"}, - {file = "pyinstaller-6.9.0-py3-none-win32.whl", hash = "sha256:2bf4de17a1c63c0b797b38e13bfb4d03b5ee7c0a68e28b915a7eaacf6b76087f"}, - {file = "pyinstaller-6.9.0-py3-none-win_amd64.whl", hash = "sha256:43709c70b1da8441a730327a8ed362bfcfdc3d42c1bf89f3e2b0a163cc4e7d33"}, - {file = "pyinstaller-6.9.0-py3-none-win_arm64.whl", hash = "sha256:f15c1ef11ed5ceb32447dfbdab687017d6adbef7fc32aa359d584369bfe56eda"}, - {file = "pyinstaller-6.9.0.tar.gz", hash = "sha256:f4a75c552facc2e2a370f1e422b971b5e5cdb4058ff38cea0235aa21fc0b378f"}, + {file = "pyinstaller-6.10.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:d60fb22859e11483af735aec115fdde09467cdbb29edd9844839f2c920b748c0"}, + {file = "pyinstaller-6.10.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:46d75359668993ddd98630a3669dc5249f3c446e35239b43bc7f4155bc574748"}, + {file = "pyinstaller-6.10.0-py3-none-manylinux2014_i686.whl", hash = "sha256:3398a98fa17d47ccb31f8779ecbdacec025f7adb2f22757a54b706ac8b4fe906"}, + {file = "pyinstaller-6.10.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e9989f354ae4ed8a3bec7bdb37ae0d170751d6520e500f049c7cd0632d31d5c3"}, + {file = "pyinstaller-6.10.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b7c90c91921b3749083115b28f30f40abf2bb481ceff196d2b2ce0eaa2b3d429"}, + {file = "pyinstaller-6.10.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf876d7d93b8b4f28d1ad57fa24645cf43119c79e985dd5e5f7a801245e6f53"}, + {file = "pyinstaller-6.10.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:db05e3f2f10f9f78c56f1fb163d9cb453433429fe4281218ebaf1ebfd39ba942"}, + {file = "pyinstaller-6.10.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:28eca3817f176fdc19747e1afcf434f13bb9f17a644f611be2c5a61b1f498ed7"}, + {file = "pyinstaller-6.10.0-py3-none-win32.whl", hash = "sha256:703e041718987e46ba0568a2c71ecf2459fddef57cf9edf3efeed4a53e3dae3f"}, + {file = "pyinstaller-6.10.0-py3-none-win_amd64.whl", hash = "sha256:95b55966e563e8b8f31a43882aea10169e9a11fdf38e626d86a2907b640c0701"}, + {file = "pyinstaller-6.10.0-py3-none-win_arm64.whl", hash = "sha256:308e0a8670c9c9ac0cebbf1bbb492e71b6675606f2ec78bc4adfc830d209e087"}, + {file = "pyinstaller-6.10.0.tar.gz", hash = "sha256:143840f8056ff7b910bf8f16f6cd92cc10a6c2680bb76d0a25d558d543d21270"}, ] [package.dependencies] @@ -2541,7 +2584,7 @@ importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2024.7" +pyinstaller-hooks-contrib = ">=2024.8" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" @@ -2551,13 +2594,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2024.7" +version = "2024.8" description = "Community maintained hooks for PyInstaller" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl", hash = "sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8"}, - {file = "pyinstaller_hooks_contrib-2024.7.tar.gz", hash = "sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"}, + {file = "pyinstaller_hooks_contrib-2024.8-py3-none-any.whl", hash = "sha256:0057fe9a5c398d3f580e73e58793a1d4a8315ca91c3df01efea1c14ed557825a"}, + {file = "pyinstaller_hooks_contrib-2024.8.tar.gz", hash = "sha256:29b68d878ab739e967055b56a93eb9b58e529d5b054fbab7a2f2bacf80cef3e2"}, ] [package.dependencies] @@ -2733,13 +2776,13 @@ files = [ [[package]] name = "pywin32-ctypes" -version = "0.2.2" +version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" files = [ - {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, - {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, ] [[package]] @@ -2804,90 +2847,105 @@ files = [ [[package]] name = "regex" -version = "2024.7.24" +version = "2024.9.11" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, - {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, - {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, - {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, - {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, - {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, - {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, - {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, - {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, - {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, - {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, - {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, + {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, + {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, + {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, + {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, + {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, + {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, + {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, + {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, + {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, + {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, + {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, + {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, + {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, ] [[package]] @@ -2959,19 +3017,23 @@ files = [ [[package]] name = "setuptools" -version = "71.1.0" +version = "75.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, - {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, + {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, + {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, ] [package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "six" @@ -3141,13 +3203,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.2" +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.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -3215,13 +3277,13 @@ files = [ [[package]] name = "virtualenv" -version = "20.26.3" +version = "20.26.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, + {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, + {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, ] [package.dependencies] @@ -3235,43 +3297,41 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "4.0.1" +version = "5.0.2" description = "Filesystem events monitoring" optional = false -python-versions = ">=3.8" -files = [ - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, - {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, - {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, - {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, - {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, +python-versions = ">=3.9" +files = [ + {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d961f4123bb3c447d9fcdcb67e1530c366f10ab3a0c7d1c0c9943050936d4877"}, + {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72990192cb63872c47d5e5fefe230a401b87fd59d257ee577d61c9e5564c62e5"}, + {file = "watchdog-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bec703ad90b35a848e05e1b40bf0050da7ca28ead7ac4be724ae5ac2653a1a0"}, + {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae7a1879918f6544201d33666909b040a46421054a50e0f773e0d870ed7438d"}, + {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4a440f725f3b99133de610bfec93d570b13826f89616377715b9cd60424db6e"}, + {file = "watchdog-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b2918c19e0d48f5f20df458c84692e2a054f02d9df25e6c3c930063eca64c1"}, + {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa9cd6e24126d4afb3752a3e70fce39f92d0e1a58a236ddf6ee823ff7dba28ee"}, + {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f627c5bf5759fdd90195b0c0431f99cff4867d212a67b384442c51136a098ed7"}, + {file = "watchdog-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7594a6d32cda2b49df3fd9abf9b37c8d2f3eab5df45c24056b4a671ac661619"}, + {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba32efcccfe2c58f4d01115440d1672b4eb26cdd6fc5b5818f1fb41f7c3e1889"}, + {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:963f7c4c91e3f51c998eeff1b3fb24a52a8a34da4f956e470f4b068bb47b78ee"}, + {file = "watchdog-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c47150aa12f775e22efff1eee9f0f6beee542a7aa1a985c271b1997d340184f"}, + {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14dd4ed023d79d1f670aa659f449bcd2733c33a35c8ffd88689d9d243885198b"}, + {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84bff0391ad4abe25c2740c7aec0e3de316fdf7764007f41e248422a7760a7f"}, + {file = "watchdog-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e8d5ff39f0a9968952cce548e8e08f849141a4fcc1290b1c17c032ba697b9d7"}, + {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fb223456db6e5f7bd9bbd5cd969f05aae82ae21acc00643b60d81c770abd402b"}, + {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9814adb768c23727a27792c77812cf4e2fd9853cd280eafa2bcfa62a99e8bd6e"}, + {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:901ee48c23f70193d1a7bc2d9ee297df66081dd5f46f0ca011be4f70dec80dab"}, + {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:638bcca3d5b1885c6ec47be67bf712b00a9ab3d4b22ec0881f4889ad870bc7e8"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5597c051587f8757798216f2485e85eac583c3b343e9aa09127a3a6f82c65ee8"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:53ed1bf71fcb8475dd0ef4912ab139c294c87b903724b6f4a8bd98e026862e6d"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:29e4a2607bd407d9552c502d38b45a05ec26a8e40cc7e94db9bb48f861fa5abc"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:b6dc8f1d770a8280997e4beae7b9a75a33b268c59e033e72c8a10990097e5fde"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:d2ab34adc9bf1489452965cdb16a924e97d4452fcf88a50b21859068b50b5c3b"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:7d1aa7e4bb0f0c65a1a91ba37c10e19dabf7eaaa282c5787e51371f090748f4b"}, + {file = "watchdog-5.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:726eef8f8c634ac6584f86c9c53353a010d9f311f6c15a034f3800a7a891d941"}, + {file = "watchdog-5.0.2-py3-none-win32.whl", hash = "sha256:bda40c57115684d0216556671875e008279dea2dc00fcd3dde126ac8e0d7a2fb"}, + {file = "watchdog-5.0.2-py3-none-win_amd64.whl", hash = "sha256:d010be060c996db725fbce7e3ef14687cdcc76f4ca0e4339a68cc4532c382a73"}, + {file = "watchdog-5.0.2-py3-none-win_ia64.whl", hash = "sha256:3960136b2b619510569b90f0cd96408591d6c251a75c97690f4553ca88889769"}, + {file = "watchdog-5.0.2.tar.gz", hash = "sha256:dcebf7e475001d2cdeb020be630dc5b687e9acdd60d16fea6bb4508e7b94cf76"}, ] [package.extras] @@ -3326,83 +3386,97 @@ six = "*" [[package]] name = "websockets" -version = "12.0" +version = "13.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, + {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, + {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, + {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, + {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, + {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, + {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, + {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, + {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, + {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, + {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, + {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, + {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, + {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, + {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, ] [[package]] @@ -3421,101 +3495,103 @@ watchdog = ["watchdog"] [[package]] name = "yarl" -version = "1.9.4" +version = "1.11.1" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, + {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, + {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, + {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, + {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, + {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, + {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, + {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, + {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, + {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, + {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, + {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, + {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, + {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, + {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, ] [package.dependencies] @@ -3524,20 +3600,24 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.19.2" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "<3.12,>=3.9" -content-hash = "efabbc96c08f1cfc84fee39a52507329221bd3c6d6e436b887a369392294b155" +content-hash = "e1f6f0dab9ac1ffd84ccd9d7ce01806a0f55888e993f12092282d5f6ea0943a3" diff --git a/pyproject.toml b/pyproject.toml index 36b98787..b0a0c2b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,11 +19,11 @@ operate = "operate.cli:main" [tool.poetry.dependencies] python = "<3.12,>=3.9" -open-autonomy = "==0.14.14.post2" -open-aea-ledger-cosmos = "==1.53.0" -open-aea-ledger-ethereum = "==1.53.0" -open-aea-ledger-ethereum-flashbots = "==1.53.0" -open-aea-cli-ipfs = "==1.53.0" +open-autonomy = "==0.16.0" +open-aea-ledger-cosmos = "==1.56.0" +open-aea-ledger-ethereum = "==1.56.0" +open-aea-ledger-ethereum-flashbots = "==1.56.0" +open-aea-cli-ipfs = "==1.56.0" clea = "==0.1.0rc4" cytoolz = "==0.12.3" docker = "6.1.2" diff --git a/run_service.py b/run_service.py index f8f5da7b..4b0696ad 100644 --- a/run_service.py +++ b/run_service.py @@ -30,10 +30,13 @@ import requests import yaml from aea.crypto.base import LedgerApi -from aea_ledger_ethereum import EthereumApi +from aea_ledger_ethereum import EthereumApi, EIP1559, wei_to_gwei, estimate_priority_fee, get_base_fee_multiplier from dotenv import load_dotenv +from eth_utils import to_wei from halo import Halo from termcolor import colored +from web3 import Web3 +from web3.types import Wei, TxParams from operate.account.user import UserAccount from operate.cli import OperateApp @@ -78,10 +81,11 @@ "token": "ETH", "usdcRequired": False, "firstTimeTopUp": SUGGESTED_TOP_UP_DEFAULT * 5, - "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT, + "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT / 10, "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(15_000), - "MAX_FEE_PER_GAS": str(1_000_000_000), + # this means default values will be used + "MAX_PRIORITY_FEE_PER_GAS": "", + "MAX_FEE_PER_GAS": "", } }, 8453: { @@ -91,14 +95,90 @@ "operationalFundReq": SUGGESTED_TOP_UP_DEFAULT / 10, "usdcRequired": False, "gasParams": { - "MAX_PRIORITY_FEE_PER_GAS": str(150_000), - "MAX_FEE_PER_GAS": str(500_000_000), + # this means default values will be used + "MAX_PRIORITY_FEE_PER_GAS": "", + "MAX_FEE_PER_GAS": "", } }, } +def patched_get_gas_price_strategy_eip1559( + max_gas_fast: int, + fee_history_blocks: int, + fee_history_percentile: int, + default_priority_fee: t.Optional[int], + fallback_estimate: t.Dict[str, t.Optional[int]], + priority_fee_increase_boundary: int, +) -> t.Callable[[Web3, TxParams], t.Dict[str, Wei]]: + """Get the gas price strategy.""" + + def eip1559_price_strategy( + web3: Web3, # pylint: disable=redefined-outer-name + transaction_params: TxParams, # pylint: disable=unused-argument + ) -> t.Dict[str, Wei]: + """ + Get gas price using EIP1559. + + Visit `https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md` + for more information. + + :param web3: web3 instance + :param transaction_params: transaction parameters + :return: dictionary containing gas price strategy + """ + + latest_block = web3.eth.get_block("latest") + base_fee = latest_block.get("baseFeePerGas") + block_number = latest_block.get("number") + base_fee_gwei = wei_to_gwei(base_fee) + + estimated_priority_fee = estimate_priority_fee( + web3, + block_number, + default_priority_fee=default_priority_fee, + fee_history_blocks=fee_history_blocks, + fee_history_percentile=fee_history_percentile, + priority_fee_increase_boundary=priority_fee_increase_boundary, + ) + + if estimated_priority_fee is None: + + return fallback_estimate + + max_priority_fee_per_gas = max( + estimated_priority_fee, + to_wei(default_priority_fee, "gwei") + if default_priority_fee is not None + else -1, + ) + multiplier = get_base_fee_multiplier(base_fee_gwei) + + potential_max_fee = base_fee * multiplier + max_fee_per_gas = ( + (potential_max_fee + max_priority_fee_per_gas) + if max_priority_fee_per_gas > potential_max_fee + else potential_max_fee + ) + + if ( + wei_to_gwei(max_fee_per_gas) >= max_gas_fast + or wei_to_gwei(max_priority_fee_per_gas) >= max_gas_fast + ): + return fallback_estimate + + return { + "maxFeePerGas": Wei(int(max_fee_per_gas)), + "maxPriorityFeePerGas": Wei(int(max_priority_fee_per_gas)), + } + + return eip1559_price_strategy + + +EthereumApi._gas_price_strategy_callables[EIP1559] = patched_get_gas_price_strategy_eip1559 + + @dataclass class OptimusConfig(LocalResource): """Local configuration.""" @@ -561,7 +641,6 @@ def main() -> None: balance = get_erc20_balance(ledger_api, olas_address, address) / 10 ** 18 spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} {STAKED_BONDING_TOKEN}") - if chain_metadata.get("usdcRequired", False) and not service_exists: print(f"[{chain_name}] Please make sure address {address} has at least 10 USDC") @@ -574,8 +653,8 @@ def main() -> None: while get_erc20_balance(ledger_api, USDC_ADDRESS, address) < USDC_REQUIRED: time.sleep(1) - balance = get_erc20_balance(ledger_api, USDC_ADDRESS, address) / 10 ** 6 - spinner.succeed(f"[{chain_name}] Safe updated balance: {balance} USDC.") + usdc_balance = get_erc20_balance(ledger_api, USDC_ADDRESS, address) / 10 ** 6 + spinner.succeed(f"[{chain_name}] Safe updated balance: {usdc_balance} USDC.") manager.deploy_service_onchain_from_safe_single_chain( hash=service.hash, @@ -584,6 +663,18 @@ def main() -> None: ) manager.fund_service(hash=service.hash, chain_id=chain_id) + usdc_balance = get_erc20_balance(ledger_api, USDC_ADDRESS, address) if chain_metadata.get("usdcRequired", False) else 0 + if usdc_balance > 0: + # transfer all the usdc balance into the service safe + manager.fund_service_erc20( + hash=service.hash, + chain_id=chain_id, + token=USDC_ADDRESS, + safe_topup=usdc_balance, + agent_topup=0, + safe_fund_treshold=USDC_REQUIRED + usdc_balance, + ) + safes = { ChainType.from_id(int(chain)).name.lower(): config.chain_data.multisig for chain, config in service.chain_configs.items() From 2346e78860f323d17048562fd0df2ec3c1fd5c61 Mon Sep 17 00:00:00 2001 From: Ardian Date: Wed, 18 Sep 2024 13:04:12 +0200 Subject: [PATCH 17/19] fix: priority fee estimation --- run_service.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/run_service.py b/run_service.py index 4b0696ad..a5f3a3e4 100644 --- a/run_service.py +++ b/run_service.py @@ -30,7 +30,7 @@ import requests import yaml from aea.crypto.base import LedgerApi -from aea_ledger_ethereum import EthereumApi, EIP1559, wei_to_gwei, estimate_priority_fee, get_base_fee_multiplier +from aea_ledger_ethereum import EthereumApi, EIP1559, wei_to_gwei, get_base_fee_multiplier from dotenv import load_dotenv from eth_utils import to_wei from halo import Halo @@ -103,6 +103,48 @@ } +def estimate_priority_fee( + web3_object: Web3, + block_number: int, + default_priority_fee: t.Optional[int], + fee_history_blocks: int, + fee_history_percentile: int, + priority_fee_increase_boundary: int, +) -> t.Optional[int]: + """Estimate priority fee from base fee.""" + + if default_priority_fee is not None: + return default_priority_fee + + fee_history = web3_object.eth.fee_history( + fee_history_blocks, block_number, [fee_history_percentile] # type: ignore + ) + + # This is going to break if more percentiles are introduced in the future, + # i.e., `fee_history_percentile` param becomes a `List[int]`. + rewards = sorted([reward[0] for reward in fee_history.get("reward", []) if reward[0] > 0]) + if len(rewards) == 0: + return None + + # Calculate percentage increases from between ordered list of fees + percentage_increases = [ + ((j - i) / i) * 100 if i != 0 else 0 for i, j in zip(rewards[:-1], rewards[1:]) + ] + highest_increase = max(*percentage_increases) + highest_increase_index = percentage_increases.index(highest_increase) + + values = rewards.copy() + # If we have big increase in value, we could be considering "outliers" in our estimate + # Skip the low elements and take a new median + if ( + highest_increase > priority_fee_increase_boundary + and highest_increase_index >= len(values) // 2 + ): + values = values[highest_increase_index:] + + return values[len(values) // 2] + + def patched_get_gas_price_strategy_eip1559( max_gas_fast: int, @@ -144,7 +186,6 @@ def eip1559_price_strategy( ) if estimated_priority_fee is None: - return fallback_estimate max_priority_fee_per_gas = max( From a04403e5df17259f55d00e5ec2393827887c2dc9 Mon Sep 17 00:00:00 2001 From: Ardian Date: Wed, 18 Sep 2024 16:52:45 +0200 Subject: [PATCH 18/19] chore: bump optimus --- run_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_service.py b/run_service.py index a5f3a3e4..37b63887 100644 --- a/run_service.py +++ b/run_service.py @@ -425,7 +425,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate: """Get the service template""" return ServiceTemplate({ "name": "Optimus", - "hash": "bafybeibjwknk7bchs24irn7ayogp72i2cbaioqcd5dzssqtdq4gihrocu4", + "hash": "bafybeiakhjobjcnqgx2gcdsxhpybwlgcyoll3jovwoygs6hk2hx67cjiiy", "description": "Optimus", "image": "https://operate.olas.network/_next/image?url=%2Fimages%2Fprediction-agent.png&w=3840&q=75", "service_version": 'v0.18.1', From 7ae87ef8305f25d65ca7ff102615c5467bcfc8b1 Mon Sep 17 00:00:00 2001 From: Ardian Date: Wed, 18 Sep 2024 17:33:30 +0200 Subject: [PATCH 19/19] fix: less update txs --- operate/services/manage.py | 2 +- run_service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index 65b2ee95..9d4376e4 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -476,7 +476,7 @@ def deploy_service_onchain_from_safe_single_chain( # pylint: disable=too-many-s is_update = ( (not is_first_mint) and (on_chain_hash is not None) - and (on_chain_hash != service.hash or current_agent_id != agent_id) + and (current_agent_id != agent_id) ) if is_update: diff --git a/run_service.py b/run_service.py index 37b63887..b8618a03 100644 --- a/run_service.py +++ b/run_service.py @@ -441,7 +441,7 @@ def get_service_template(config: OptimusConfig) -> ServiceTemplate: "use_staking": False, "fund_requirements": FundRequirementsTemplate( { - "agent": SUGGESTED_TOP_UP_DEFAULT, + "agent": SUGGESTED_TOP_UP_DEFAULT * 5, "safe": SUGGESTED_SAFE_TOP_UP_DEFAULT, } ),