From d7e2aac44bd3f34fa7aea844657873c3ae5a31a3 Mon Sep 17 00:00:00 2001 From: Uxio Fuentefria <6909403+Uxio0@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:36:02 +0100 Subject: [PATCH] Update dependencies - Closes #574 --- README.md | 3 ++- pyproject.toml | 4 ++-- requirements-test.txt | 4 ++-- requirements.txt | 6 +++--- src/safe_cli/operators/safe_operator.py | 20 ++++++++++---------- tests/test_safe_cli_entry_point.py | 4 ++++ tests/test_safe_operator.py | 6 +++--- 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d22c387..bce3fae 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ![Python 3.10](https://img.shields.io/badge/Python-3.10-blue.svg) ![Python 3.11](https://img.shields.io/badge/Python-3.11-blue.svg) ![Python 3.12](https://img.shields.io/badge/Python-3.12-blue.svg) +![Python 3.13](https://img.shields.io/badge/Python-3.13-blue.svg) [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/safeglobal/safe-cli?label=Docker&sort=semver)](https://hub.docker.com/r/safeglobal/safe-cli) # Safe CLI @@ -29,7 +30,7 @@ docker run -it safeglobal/safe-cli safe-cli = 3.10 (Python 3.12 is recommended). +**Prerequisite:** [Python](https://www.python.org/downloads/) >= 3.10 (Python 3.13 is recommended). Once Python is installed on your system, run the following command to install Safe CLI: ```bash diff --git a/pyproject.toml b/pyproject.toml index ceb5956..cc441a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] description = "Command Line Interface for Safe" readme = "README.md" license = "MIT" -requires-python = ">=3.8" +requires-python = ">=3.10" authors = [{ name = "Uxío Fuentefría", email = "uxio@safe.global" }] classifiers = [ "License :: OSI Approved :: MIT License", @@ -26,7 +26,7 @@ dependencies = [ "prompt_toolkit>=3", "pygments>=2", "requests>=2", - "safe-eth-py>=7", + "safe-eth-py==7.17.0", "tabulate>=0.8", "typer>=0.14.0", ] diff --git a/requirements-test.txt b/requirements-test.txt index 63620a1..edc7c3a 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ -r requirements.txt -coverage==7.10.6 +coverage==7.12.0 flake8==7.3.0 -pytest==8.4.2 +pytest==9.0.1 pytest-sugar==1.1.1 diff --git a/requirements.txt b/requirements.txt index 454fc17..2e9a25e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,8 +5,8 @@ packaging>=24.2 prompt_toolkit==3.0.52 pygments==2.19.2 requests==2.32.5 -safe-eth-py==7.6.1 +safe-eth-py==7.17.0 tabulate==0.9.0 trezor==0.13.10 -typer==0.16.0 -web3==7.12.0 +typer==0.20.0 +web3==7.14.0 diff --git a/src/safe_cli/operators/safe_operator.py b/src/safe_cli/operators/safe_operator.py index d3928f7..2e29d35 100644 --- a/src/safe_cli/operators/safe_operator.py +++ b/src/safe_cli/operators/safe_operator.py @@ -18,7 +18,7 @@ EthereumNetworkNotSupported, TxSpeed, ) -from safe_eth.eth.clients import EtherscanClient, EtherscanClientConfigurationProblem +from safe_eth.eth.clients import EtherscanClientV2 from safe_eth.eth.constants import NULL_ADDRESS, SENTINEL_ADDRESS from safe_eth.eth.contracts import ( get_erc20_contract, @@ -112,7 +112,7 @@ def decorated(self, *args, **kwargs): ) ) if self.etherscan: - url = f"{self.etherscan.base_url}/address/{self.address}" + url = f"{self.etherscan.get_base_url()}address/{self.address}" print_formatted_text(HTML(f"Try Etherscan instead {url}")) else: return f(self, *args, **kwargs) @@ -141,7 +141,6 @@ class SafeOperator: ethereum_client: EthereumClient ens: ENS network: EthereumNetwork - etherscan: Optional[EtherscanClient] safe_tx_service: Optional[TransactionServiceApi] safe: Safe safe_contract: Contract @@ -161,11 +160,6 @@ def __init__( self.ethereum_client = EthereumClient(self.node_url) self.ens = ENS.from_web3(self.ethereum_client.w3) self.network: EthereumNetwork = self.ethereum_client.get_network() - try: - self.etherscan = EtherscanClient(self.network) - except EtherscanClientConfigurationProblem: - self.etherscan = None - try: self.safe_tx_service = TransactionServiceApi.from_ethereum_client( self.ethereum_client @@ -196,6 +190,12 @@ def __init__( self.hw_wallet_manager = get_hw_wallet_manager() self.interactive = interactive # Disable prompt dialogs + @cached_property + def etherscan(self) -> Optional[EtherscanClientV2]: + if EtherscanClientV2.is_supported_network(self.network): + return EtherscanClientV2(self.network) + return None + @cached_property def last_default_fallback_handler_address(self) -> ChecksumAddress: """ @@ -843,7 +843,7 @@ def print_info(self): ) if self.etherscan: - url = f"{self.etherscan.base_url}/address/{self.address}" + url = f"{self.etherscan.get_base_url()}/address/{self.address}" print_formatted_text( HTML( f"Etherscan=" @@ -903,7 +903,7 @@ def get_safe_cli_info(self) -> SafeCliInfo: safe_info.master_copy, safe_info.modules, safe_info.fallback_handler, - safe_info.guard, + safe_info.transaction_guard, balance_ether, safe_info.version, ) diff --git a/tests/test_safe_cli_entry_point.py b/tests/test_safe_cli_entry_point.py index 7edab6b..239f5a6 100644 --- a/tests/test_safe_cli_entry_point.py +++ b/tests/test_safe_cli_entry_point.py @@ -13,6 +13,7 @@ from safe_eth.safe.safe import SafeInfo from safe_eth.util.util import to_0x_hex_str from typer.testing import CliRunner +from web3.constants import CHECKSUM_ADDRESSS_ZERO from safe_cli import VERSION from safe_cli.main import app @@ -330,6 +331,7 @@ def test_build_safe_cli(self, retrieve_all_info_mock: MagicMock): [safe_owner], 1, "1.4.1", + CHECKSUM_ADDRESSS_ZERO, ) result = runner.invoke( app, @@ -362,6 +364,7 @@ def test_build_safe_cli_for_owner( [safe_owner], 1, "1.4.1", + CHECKSUM_ADDRESSS_ZERO, ) get_safes_for_owner_mock.return_value = [] @@ -396,6 +399,7 @@ def test_parse_operator_mode( [safe_owner], 1, "1.4.1", + CHECKSUM_ADDRESSS_ZERO, ) get_command_mock.side_effect = ["tx-service", "exit"] diff --git a/tests/test_safe_operator.py b/tests/test_safe_operator.py index 4c0ea8e..1008fc6 100644 --- a/tests/test_safe_operator.py +++ b/tests/test_safe_operator.py @@ -278,7 +278,7 @@ def test_change_guard(self): safe_operator = self.setup_operator(version="1.4.1") safe = Safe(safe_operator.address, self.ethereum_client) - current_guard = safe.retrieve_guard() + current_guard = safe.retrieve_transaction_guard() with self.assertRaises(SameGuardException): safe_operator.change_guard(current_guard) @@ -286,10 +286,10 @@ def test_change_guard(self): with self.assertRaises(InvalidGuardException): # Contract does not exist self.assertTrue(safe_operator.change_guard(not_valid_guard)) - new_guard = self.deploy_example_guard() + new_guard = self.deploy_example_transaction_guard() self.assertTrue(safe_operator.change_guard(new_guard)) self.assertEqual(safe_operator.safe_cli_info.guard, new_guard) - self.assertEqual(safe.retrieve_guard(), new_guard) + self.assertEqual(safe.retrieve_transaction_guard(), new_guard) def test_change_master_copy(self): safe_operator = self.setup_operator(version="1.3.0")