Repository: safe-global/safe-eth-py (language: python)

Issue:
Improve typing
Run mypy on the project and check if there are some easy typing problems that we can fix. It shouldn't be 100%, but I think with a little effort we can improve it and catch some potential bugs

This ticket should take at much 1 day of work


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index 72aa22894..98340c0fc 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -29,7 +29,7 @@ jobs:
           path: ~/.cache/pre-commit
           key: pre-commit|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
       - name: Install dependencies
-        run: pip install -U pre-commit
+        run: pip install -U pre-commit -r requirements-test.txt
       - name: Run pre-commit
         run: pre-commit run --all-files
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4a9665210..6dc59770e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,6 +13,13 @@ repos:
     rev: 6.1.0
     hooks:
       - id: flake8
+  - repo: https://github.com/pre-commit/mirrors-mypy
+    rev: v1.11.2
+    hooks:
+      - id: mypy
+        exclude: env|docs|config|.github
+        args: [--ignore-missing-imports, --warn-unused-ignores, --warn-redundant-casts, --warn-unused-configs]
+        language: system
   - repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v4.5.0
     hooks:
diff --git a/mypy.ini b/mypy.ini
index 9a35f7a2e..f13c9de0e 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,23 +1,8 @@
 [mypy]
-
-
-[mypy-django_filters.*]
-ignore_missing_imports = True
-[mypy-faker.*]
-ignore_missing_imports = True
-[mypy-pytest.*]
-ignore_missing_imports = True
-[mypy-safe_eth.eth.contracts]
-ignore_missing_imports = True
-[mypy-py_eth_sig_utils.*]
-ignore_missing_imports = True
-[mypy-rlp.*]
-ignore_missing_imports = True
-[mypy-eth_account.*]
-ignore_missing_imports = True
-[mypy-ethereum.*]
-ignore_missing_imports = True
-[mypy-rest_framework.*]
-ignore_missing_imports = True
-[mypy-django.*]
+exclude = env|docs|config|.github
+python_version = 3.9
 ignore_missing_imports = True
+# check_untyped_defs = True  # Type-checks the interior of functions without type annotations.
+warn_unused_ignores = True
+warn_redundant_casts = True
+warn_unused_configs = True
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 727660a00..7cd74a6dc 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -6,7 +6,6 @@ hatch
 ipdb
 ipython
 isort
-mypy
 pre-commit
 sphinx
 sphinx-rtd-theme
diff --git a/safe_eth/eth/account_abstraction/bundler_client.py b/safe_eth/eth/account_abstraction/bundler_client.py
index adafe8ccd..3c0854632 100644
--- a/safe_eth/eth/account_abstraction/bundler_client.py
+++ b/safe_eth/eth/account_abstraction/bundler_client.py
@@ -2,13 +2,13 @@
 from functools import cache, lru_cache
 from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
 
-from eth_typing import ChecksumAddress, HexStr
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 from hexbytes import HexBytes
 
 from safe_eth.util.http import prepare_http_session
 
 from .exceptions import BundlerClientConnectionException, BundlerClientResponseException
-from .user_operation import UserOperation
+from .user_operation import UserOperation, UserOperationV07
 from .user_operation_receipt import UserOperationReceipt
 
 logger = logging.getLogger(__name__)
@@ -33,7 +33,11 @@ def __str__(self):
 
     def _do_request(
         self, payload: Union[Dict[Any, Any], Sequence[Dict[Any, Any]]]
-    ) -> Union[Optional[Union[Dict[str, Any]]], Optional[List[Dict[str, Any]]]]:
+    ) -> Union[
+        Optional[Union[Dict[str, Any]]],
+        Optional[List[Dict[str, Any]]],
+        Optional[List[str]],
+    ]:
         """
         :param payload: Allows simple request or a batch request
         :return: Result of the request
@@ -49,7 +53,7 @@ def _do_request(
 
         if not response.ok:
             raise BundlerClientConnectionException(
-                f"Error connecting to bundler {self.url} : {response.status_code} {response.content}"
+                f"Error connecting to bundler {self.url} : {response.status_code} {response.content!r}"
             )
 
         bundler_responses = response.json()
@@ -122,7 +126,7 @@ def get_chain_id(self):
     @lru_cache(maxsize=1024)
     def get_user_operation_by_hash(
         self, user_operation_hash: HexStr
-    ) -> Optional[UserOperation]:
+    ) -> Optional[Union[UserOperation, UserOperationV07]]:
         """
         https://docs.alchemy.com/reference/eth-getuseroperationbyhash
 
@@ -133,11 +137,10 @@ def get_user_operation_by_hash(
         """
         payload = self._get_user_operation_by_hash_payload(user_operation_hash)
         result = self._do_request(payload)
-        return (
-            UserOperation.from_bundler_response(user_operation_hash, result)
-            if result
-            else None
-        )
+        if result and isinstance(result, dict):
+            return UserOperation.from_bundler_response(user_operation_hash, result)
+        else:
+            return None
 
     @lru_cache(maxsize=1024)
     def get_user_operation_receipt(
@@ -153,12 +156,15 @@ def get_user_operation_receipt(
         """
         payload = self._get_user_operation_receipt_payload(user_operation_hash)
         result = self._do_request(payload)
-        return UserOperationReceipt.from_bundler_response(result) if result else None
+        if result and isinstance(result, dict):
+            return UserOperationReceipt.from_bundler_response(result)
+        else:
+            return None
 
     @lru_cache(maxsize=1024)
     def get_user_operation_and_receipt(
         self, user_operation_hash: HexStr
-    ) -> Optional[Tuple[UserOperation, UserOperationReceipt]]:
+    ) -> Optional[Tuple[Union[UserOperation, UserOperationV07], UserOperationReceipt]]:
         """
         Get UserOperation and UserOperationReceipt in the same request using a batch query.
         NOTE: Batch requests are not supported by Pimlico
@@ -172,13 +178,19 @@ def get_user_operation_and_receipt(
             self._get_user_operation_by_hash_payload(user_operation_hash, request_id=1),
             self._get_user_operation_receipt_payload(user_operation_hash, request_id=2),
         ]
-        result = self._do_request(payload)
-        if not (result and result[0]):
-            return None
-
-        return UserOperation.from_bundler_response(
-            user_operation_hash, result[0]
-        ), self._parse_user_operation_receipt(result[1])
+        results = self._do_request(payload)
+        if (
+            results
+            and isinstance(results, list)
+            and len(results) >= 2
+            and isinstance(results[0], dict)
+            and isinstance(results[1], dict)
+        ):
+            return UserOperation.from_bundler_response(
+                user_operation_hash, results[0]
+            ), self._parse_user_operation_receipt(results[1])
+
+        return None
 
     @lru_cache(maxsize=None)
     def supported_entry_points(self) -> List[ChecksumAddress]:
@@ -195,4 +207,12 @@ def supported_entry_points(self) -> List[ChecksumAddress]:
             "params": [],
             "id": 1,
         }
-        return self._do_request(payload)
+        result = self._do_request(payload)
+        if result and isinstance(result, list):
+            return [
+                ChecksumAddress(HexAddress(HexStr(address)))
+                for address in result
+                if isinstance(address, str)
+            ]
+
+        return []
diff --git a/safe_eth/eth/account_abstraction/user_operation.py b/safe_eth/eth/account_abstraction/user_operation.py
index b6cd5e4a9..6a2619d4c 100644
--- a/safe_eth/eth/account_abstraction/user_operation.py
+++ b/safe_eth/eth/account_abstraction/user_operation.py
@@ -204,7 +204,12 @@ def gas_fees(self) -> bytes:
 
     @property
     def paymaster_and_data(self) -> bytes:
-        if not self.paymaster:
+        if (
+            not self.paymaster
+            or not self.paymaster_verification_gas_limit
+            or not self.paymaster_post_op_gas_limit
+            or not self.paymaster_data
+        ):
             return b""
         return (
             HexBytes(self.paymaster).rjust(20, b"\x00")
diff --git a/safe_eth/eth/clients/blockscout_client.py b/safe_eth/eth/clients/blockscout_client.py
index 4fd80c320..ea15adda9 100644
--- a/safe_eth/eth/clients/blockscout_client.py
+++ b/safe_eth/eth/clients/blockscout_client.py
@@ -135,8 +135,8 @@ class BlockscoutClient:
 
     def __init__(self, network: EthereumNetwork):
         self.network = network
-        self.grahpql_url = self.NETWORK_WITH_URL.get(network)
-        if self.grahpql_url is None:
+        self.grahpql_url = self.NETWORK_WITH_URL.get(network, "")
+        if not self.grahpql_url:
             raise BlockScoutConfigurationProblem(
                 f"Network {network.name} - {network.value} not supported"
             )
@@ -167,3 +167,4 @@ def get_contract_metadata(
             return ContractMetadata(
                 smart_contract["name"], json.loads(smart_contract["abi"]), False
             )
+        return None
diff --git a/safe_eth/eth/clients/cowswap/cow_swap_api.py b/safe_eth/eth/clients/cowswap/cow_swap_api.py
index 3d57fdeea..0c8fa9288 100644
--- a/safe_eth/eth/clients/cowswap/cow_swap_api.py
+++ b/safe_eth/eth/clients/cowswap/cow_swap_api.py
@@ -4,7 +4,7 @@
 
 from eth_account import Account
 from eth_account.messages import encode_defunct
-from eth_typing import AnyAddress, ChecksumAddress, HexStr
+from eth_typing import Address, ChecksumAddress, HexAddress, HexStr
 
 from safe_eth.eth import EthereumNetwork, EthereumNetworkNotSupported
 from safe_eth.eth.constants import NULL_ADDRESS
@@ -13,6 +13,8 @@
 
 from .order import Order, OrderKind
 
+AnyAddressType = Union[Address, HexAddress, ChecksumAddress]
+
 
 class TradeResponse(TypedDict):
     blockNumber: int
@@ -21,9 +23,9 @@ class TradeResponse(TypedDict):
     buyAmount: str  # Stringified int
     sellAmount: str  # Stringified int
     sellAmountBeforeFees: str  # Stringified int
-    owner: AnyAddress  # Not checksummed
-    buyToken: AnyAddress
-    sellToken: AnyAddress
+    owner: AnyAddressType  # Not checksummed
+    buyToken: AnyAddressType
+    sellToken: AnyAddressType
     txHash: HexStr
 
 
@@ -73,10 +75,14 @@ def weth_address(self) -> ChecksumAddress:
         :return: Wrapped ether checksummed address
         """
         if self.network == EthereumNetwork.GNOSIS:  # WXDAI
-            return ChecksumAddress("0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d")
+            return ChecksumAddress(
+                HexAddress(HexStr("0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d"))
+            )
 
         # Mainnet WETH9
-        return ChecksumAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
+        return ChecksumAddress(
+            HexAddress(HexStr("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"))
+        )
 
     def get_quote(
         self, order: Order, from_address: ChecksumAddress
@@ -99,7 +105,11 @@ def get_quote(
         if r.ok:
             return r.json()
         else:
-            return ErrorResponse(r.json())
+            response_dict = r.json()
+            return ErrorResponse(
+                errorType=response_dict.get("errorType", "Error getting quote"),
+                description=response_dict.get("description", ""),
+            )
 
     def get_fee(
         self, order: Order, from_address: ChecksumAddress
@@ -107,9 +117,11 @@ def get_fee(
         quote = self.get_quote(order, from_address)
 
         if "quote" in quote:
-            return int(quote["quote"]["feeAmount"])
+            result = cast(Dict[str, Any], quote)
+            return int(result["quote"]["feeAmount"])
         else:
-            return quote
+            error = cast(ErrorResponse, quote)
+            return error
 
     def place_order(
         self, order: Order, private_key: HexStr
@@ -134,7 +146,8 @@ def place_order(
 
         signable_hash = eip712_encode_hash(
             order.get_eip712_structured_data(
-                self.network.value, self.settlement_contract_address
+                self.network.value,
+                ChecksumAddress(HexAddress(HexStr(self.settlement_contract_address))),
             )
         )
         message = encode_defunct(primitive=signable_hash)
@@ -158,11 +171,15 @@ def place_order(
         if r.ok:
             return HexStr(r.json())
         else:
-            return ErrorResponse(r.json())
+            response_dict = r.json()
+            return ErrorResponse(
+                errorType=response_dict.get("errorType", "Error placing order"),
+                description=response_dict.get("description", ""),
+            )
 
     def get_orders(
         self, owner: ChecksumAddress, offset: int = 0, limit=10
-    ) -> List[Dict[str, Any]]:
+    ) -> Union[List[Dict[str, Any]], ErrorResponse]:
         """
         :param owner:
         :param offset: Defaults to 0
@@ -178,11 +195,15 @@ def get_orders(
         if r.ok:
             return cast(List[Dict[str, Any]], r.json())
         else:
-            return ErrorResponse(r.json())
+            response_dict = r.json()
+            return ErrorResponse(
+                errorType=response_dict.get("errorType", "Error getting orders"),
+                description=response_dict.get("description", ""),
+            )
 
     def get_trades(
         self, order_ui: Optional[HexStr] = None, owner: Optional[ChecksumAddress] = None
-    ) -> List[TradeResponse]:
+    ) -> Union[List[TradeResponse], ErrorResponse]:
         assert bool(order_ui) ^ bool(
             owner
         ), "order_ui or owner must be provided, but not both"
@@ -196,7 +217,11 @@ def get_trades(
         if r.ok:
             return cast(List[TradeResponse], r.json())
         else:
-            return ErrorResponse(r.json())
+            response_dict = r.json()
+            return ErrorResponse(
+                errorType=response_dict.get("errorType", "Error getting trades"),
+                description=response_dict.get("description", ""),
+            )
 
     def get_estimated_amount(
         self,
@@ -222,7 +247,7 @@ def get_estimated_amount(
             validTo=0,  # Valid for 1 hour
             appData={},
             feeAmount=0,
-            kind=kind.name.lower(),  # `sell` or `buy`
+            kind="sell" if kind == OrderKind.SELL else "buy",  # `sell` or `buy`
             partiallyFillable=False,
             sellTokenBalance="erc20",  # `erc20`, `external` or `internal`
             buyTokenBalance="erc20",  # `erc20` or `internal`
@@ -230,9 +255,11 @@ def get_estimated_amount(
 
         quote = self.get_quote(order, NULL_ADDRESS)
         if "quote" in quote:
+            result = cast(Dict[str, Any], quote)
             return {
-                "buyAmount": int(quote["quote"]["buyAmount"]),
-                "sellAmount": int(quote["quote"]["sellAmount"]),
+                "buyAmount": int(result["quote"]["buyAmount"]),
+                "sellAmount": int(result["quote"]["sellAmount"]),
             }
         else:
-            return quote
+            error = cast(ErrorResponse, quote)
+            return error
diff --git a/safe_eth/eth/clients/etherscan_client.py b/safe_eth/eth/clients/etherscan_client.py
index b06cb996f..efccafdeb 100644
--- a/safe_eth/eth/clients/etherscan_client.py
+++ b/safe_eth/eth/clients/etherscan_client.py
@@ -1,7 +1,7 @@
 import json
 import os
 import time
-from typing import Any, Dict, Optional
+from typing import Any, Dict, List, MutableMapping, Optional, Union
 from urllib.parse import urljoin
 
 from ...util.http import prepare_http_session
@@ -116,7 +116,7 @@ class EtherscanClient:
         EthereumNetwork.HOLESKY: "https://api-holesky.etherscan.io",
         EthereumNetwork.LINEA_SEPOLIA: "https://api-sepolia.lineascan.build",
     }
-    HTTP_HEADERS = {
+    HTTP_HEADERS: MutableMapping[str, Union[str, bytes]] = {
         "User-Agent": "curl/7.77.0",
     }
 
@@ -130,9 +130,9 @@ def __init__(
     ):
         self.network = network
         self.api_key = api_key
-        self.base_url = self.NETWORK_WITH_URL.get(network)
-        self.base_api_url = self.NETWORK_WITH_API_URL.get(network)
-        if self.base_api_url is None:
+        self.base_url = self.NETWORK_WITH_URL.get(network, "")
+        self.base_api_url = self.NETWORK_WITH_API_URL.get(network, "")
+        if not self.base_api_url:
             raise EtherscanClientConfigurationProblem(
                 f"Network {network.name} - {network.value} not supported"
             )
@@ -146,7 +146,7 @@ def build_url(self, path: str):
             url += f"&apikey={self.api_key}"
         return url
 
-    def _do_request(self, url: str) -> Optional[Dict[str, Any]]:
+    def _do_request(self, url: str) -> Optional[Union[Dict[str, Any], List[Any], str]]:
         response = self.http_session.get(url, timeout=self.request_timeout)
 
         if response.ok:
@@ -157,8 +157,11 @@ def _do_request(self, url: str) -> Optional[Dict[str, Any]]:
                 raise EtherscanRateLimitError
             if response_json["status"] == "1":
                 return result
+        return None
 
-    def _retry_request(self, url: str, retry: bool = True) -> Optional[Dict[str, Any]]:
+    def _retry_request(
+        self, url: str, retry: bool = True
+    ) -> Optional[Union[Dict[str, Any], List[Any], str]]:
         for _ in range(3):
             try:
                 return self._do_request(url)
@@ -167,6 +170,7 @@ def _retry_request(self, url: str, retry: bool = True) -> Optional[Dict[str, Any
                     raise exc
                 else:
                     time.sleep(5)
+        return None
 
     def get_contract_metadata(
         self, contract_address: str, retry: bool = True
@@ -179,6 +183,7 @@ def get_contract_metadata(
             contract_abi = contract_source_code["ABI"]
             if contract_abi:
                 return ContractMetadata(contract_name, contract_abi, False)
+        return None
 
     def get_contract_source_code(self, contract_address: str, retry: bool = True):
         """
@@ -203,11 +208,19 @@ def get_contract_source_code(self, contract_address: str, retry: bool = True):
         url = self.build_url(
             f"api?module=contract&action=getsourcecode&address={contract_address}"
         )
-        result = self._retry_request(url, retry=retry)  # Returns a list
-        if result:
-            result = result[0]
-            abi_str = result["ABI"]
-            result["ABI"] = json.loads(abi_str) if abi_str.startswith("[") else None
+        response = self._retry_request(url, retry=retry)  # Returns a list
+        if response and isinstance(response, list):
+            result = response[0]
+            abi_str = result.get("ABI")
+
+            if isinstance(abi_str, str) and abi_str.startswith("["):
+                try:
+                    result["ABI"] = json.loads(abi_str)
+                except json.JSONDecodeError:
+                    result["ABI"] = None  # Handle the case where JSON decoding fails
+            else:
+                result["ABI"] = None
+
             return result
 
     def get_contract_abi(self, contract_address: str, retry: bool = True):
@@ -215,5 +228,11 @@ def get_contract_abi(self, contract_address: str, retry: bool = True):
             f"api?module=contract&action=getabi&address={contract_address}"
         )
         result = self._retry_request(url, retry=retry)
-        if result:
-            return json.loads(result)
+        if isinstance(result, dict):
+            return result
+        elif isinstance(result, str):
+            try:
+                return json.loads(result)
+            except json.JSONDecodeError:
+                pass
+        return None
diff --git a/safe_eth/eth/clients/sourcify_client.py b/safe_eth/eth/clients/sourcify_client.py
index 2c79ba6e1..4c8a68ce2 100644
--- a/safe_eth/eth/clients/sourcify_client.py
+++ b/safe_eth/eth/clients/sourcify_client.py
@@ -56,6 +56,7 @@ def _get_name_from_metadata(self, metadata: Dict[str, Any]) -> Optional[str]:
         values = list(metadata["settings"].get("compilationTarget", {}).values())
         if values:
             return values[0]
+        return None
 
     def _do_request(self, url: str) -> Optional[Dict[str, Any]]:
         response = self.http_session.get(url, timeout=self.request_timeout)
@@ -68,12 +69,24 @@ def is_chain_supported(self, chain_id: int) -> bool:
         chains = self.get_chains()
         if not chains:
             raise IOError("Cannot get chains for SourcifyClient")
-        return chain_id in (int(chain["chainId"]) for chain in self.get_chains())
+        for chain in chains:
+            if not isinstance(chain, dict):
+                continue
+            chain_id_str = chain.get("chainId")
+            if chain_id_str is None:
+                continue
+            try:
+                if chain_id == int(chain_id_str):
+                    return True
+            except ValueError:
+                continue
+        return False
 
     @cache
     def get_chains(self) -> Dict[str, Any]:
         url = urljoin(self.base_url_api, "/server/chains")
-        return self._do_request(url)
+        result = self._do_request(url)
+        return result or {}
 
     def get_contract_metadata(
         self, contract_address: str
diff --git a/safe_eth/eth/contracts/__init__.py b/safe_eth/eth/contracts/__init__.py
index 24d4a4fa8..931adf98e 100644
--- a/safe_eth/eth/contracts/__init__.py
+++ b/safe_eth/eth/contracts/__init__.py
@@ -31,7 +31,7 @@
 import os
 import sys
 from functools import cache
-from typing import Any, Callable, Dict, Optional
+from typing import Any, Callable, Dict, Optional, Type, Union
 
 from eth_typing import ChecksumAddress
 from hexbytes import HexBytes
@@ -101,7 +101,7 @@ def _load_json_file(path) -> Dict[str, Any]:
 
 def generate_contract_fn(
     contract: Dict[str, Any]
-) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
+) -> Callable[[Web3, Optional[ChecksumAddress]], Union[Type[Contract], Contract]]:
     """
     Dynamically generate a function to build a Web3 Contract for the provided contract ABI
 
@@ -109,10 +109,13 @@ def generate_contract_fn(
     :return: function that will return a Web3 Contract from an ABI
     """
 
-    def fn(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
-        return w3.eth.contract(
+    def fn(
+        w3: Web3, address: Optional[ChecksumAddress] = None
+    ) -> Union[Type[Contract], Contract]:
+        eth_contract = w3.eth.contract(
             address=address, abi=contract["abi"], bytecode=contract.get("bytecode")
         )
+        return eth_contract
 
     return fn
 
@@ -130,27 +133,27 @@ def get_safe_contract(w3: Web3, address: Optional[ChecksumAddress] = None) -> Co
 def get_safe_V0_0_1_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_safe_V1_0_0_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_safe_V1_1_1_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_safe_V1_3_0_contract(w3: Web3, address: Optional[str] = None) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_safe_V1_4_1_contract(w3: Web3, address: Optional[str] = None) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_compatibility_fallback_handler_contract(
@@ -167,59 +170,59 @@ def get_compatibility_fallback_handler_contract(
 def get_compatibility_fallback_handler_V1_3_0_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_compatibility_fallback_handler_V1_4_1_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_sign_message_lib_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_erc20_contract(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_erc721_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_erc1155_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_example_erc20_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_delegate_constructor_proxy_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_multi_send_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_paying_proxy_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_proxy_factory_contract(w3: Web3, address: Optional[str] = None) -> Contract:
@@ -234,77 +237,77 @@ def get_proxy_factory_contract(w3: Web3, address: Optional[str] = None) -> Contr
 def get_proxy_factory_V1_0_0_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_proxy_factory_V1_1_1_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_proxy_factory_V1_3_0_contract(
     w3: Web3, address: Optional[str] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_proxy_factory_V1_4_1_contract(
     w3: Web3, address: Optional[str] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_proxy_contract(w3: Web3, address: Optional[ChecksumAddress] = None) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_simulate_tx_accessor_V1_4_1_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_uniswap_exchange_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_uniswap_factory_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_uniswap_v2_factory_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_uniswap_v2_pair_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_uniswap_v2_router_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_kyber_network_proxy_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_cpk_factory_contract(
     w3: Web3, address: Optional[ChecksumAddress] = None
 ) -> Contract:
-    pass
+    raise NotImplementedError
 
 
 def get_multicall_v3_contract(w3: Web3, address: Optional[ChecksumAddress] = None):
diff --git a/safe_eth/eth/contracts/contract_base.py b/safe_eth/eth/contracts/contract_base.py
index c5ce5ccb3..c2312f1d6 100644
--- a/safe_eth/eth/contracts/contract_base.py
+++ b/safe_eth/eth/contracts/contract_base.py
@@ -14,7 +14,7 @@ class ContractBase(metaclass=ABCMeta):
     def __init__(
         self,
         address: ChecksumAddress,
-        ethereum_client: "EthereumClient",  # noqa F821
+        ethereum_client: "EthereumClient",  # type: ignore # noqa F821
         *args,
         **kwargs,
     ):
diff --git a/safe_eth/eth/django/forms.py b/safe_eth/eth/django/forms.py
index 560a0b1f5..7675cb709 100644
--- a/safe_eth/eth/django/forms.py
+++ b/safe_eth/eth/django/forms.py
@@ -1,5 +1,5 @@
 import binascii
-from typing import Any, Optional
+from typing import Any, Optional, Union
 
 from django import forms
 from django.core import exceptions
@@ -49,7 +49,10 @@ def to_python(self, value: Optional[Any]) -> Optional[HexBytes]:
         try:
             if isinstance(value, str):
                 value = value.strip()
-            return HexBytes(value)
+            if isinstance(value, (str, bytes, bytearray, int)):
+                return HexBytes(value)
+            else:
+                raise TypeError(f"Unsupported type for HexBytes: {type(value)}")
         except (binascii.Error, TypeError, ValueError):
             raise exceptions.ValidationError(
                 self.error_messages["invalid"],
@@ -64,11 +67,13 @@ class Keccak256FieldForm(HexFieldForm):
         "length": _('"%(value)s" keccak256 hash should be 32 bytes.'),
     }
 
-    def prepare_value(self, value: str) -> str:
+    def prepare_value(self, value: Union[str, memoryview]) -> str:
         # Keccak field already returns a hex str
-        return value
+        if isinstance(value, str):
+            return value
+        return super().prepare_value(value)
 
-    def to_python(self, value: Optional[Any]) -> HexBytes:
+    def to_python(self, value: Optional[Any]) -> Optional[HexBytes]:
         python_value: Optional[HexBytes] = super().to_python(value)
         if python_value and len(python_value) != 32:
             raise ValidationError(
diff --git a/safe_eth/eth/django/models.py b/safe_eth/eth/django/models.py
index 5dff66d53..0167d8754 100644
--- a/safe_eth/eth/django/models.py
+++ b/safe_eth/eth/django/models.py
@@ -45,6 +45,7 @@ def from_db_value(
     ) -> Optional[ChecksumAddress]:
         if value:
             return fast_bytes_to_checksum_address(value)
+        return None
 
     def get_prep_value(self, value: ChecksumAddress) -> Optional[bytes]:
         if value:
@@ -56,6 +57,7 @@ def get_prep_value(self, value: ChecksumAddress) -> Optional[bytes]:
                     code="invalid",
                     params={"value": value},
                 )
+        return None
 
     def to_python(self, value) -> Optional[ChecksumAddress]:
         if value is not None:
@@ -67,6 +69,7 @@ def to_python(self, value) -> Optional[ChecksumAddress]:
                     code="invalid",
                     params={"value": value},
                 )
+        return None
 
     def formfield(self, **kwargs):
         defaults = {
@@ -91,9 +94,11 @@ class EthereumAddressFastBinaryField(EthereumAddressBinaryField):
 
     def from_db_value(
         self, value: memoryview, expression, connection
-    ) -> Optional[HexAddress]:
+    ) -> Optional[ChecksumAddress]:
         if value:
-            return HexAddress(HexStr("0x" + bytes(value).hex()))
+            return ChecksumAddress(HexAddress(HexStr("0x" + bytes(value).hex())))
+
+        return None
 
     def to_python(self, value) -> Optional[ChecksumAddress]:
         if value is not None:
@@ -103,13 +108,16 @@ def to_python(self, value) -> Optional[ChecksumAddress]:
                         raise ValueError(
                             "Cannot convert %s to a checksum address, 20 bytes were expected"
                         )
-                return HexAddress(HexStr(to_normalized_address(value)[2:]))
+                return ChecksumAddress(
+                    HexAddress(HexStr(to_normalized_address(value)[2:]))
+                )
             except ValueError:
                 raise exceptions.ValidationError(
                     self.error_messages["invalid"],
                     code="invalid",
                     params={"value": value},
                 )
+        return None
 
 
 class UnsignedDecimal(models.DecimalField):
@@ -218,10 +226,12 @@ def _to_bytes(self, value) -> Optional[bytes]:
     def from_db_value(self, value: memoryview, expression, connection) -> Optional[str]:
         if value:
             return HexBytes(value.tobytes()).hex()
+        return None
 
     def get_prep_value(self, value: Union[bytes, str]) -> Optional[bytes]:
         if value:
             return self._to_bytes(value)
+        return None
 
     def value_to_string(self, obj):
         return str(self.value_from_object(obj))
@@ -236,6 +246,7 @@ def to_python(self, value) -> Optional[bytes]:
                     code="invalid",
                     params={"value": value},
                 )
+        return None
 
     def formfield(self, **kwargs):
         defaults = {
diff --git a/safe_eth/eth/ethereum_client.py b/safe_eth/eth/ethereum_client.py
index d4ffb237b..3f63bb8e4 100644
--- a/safe_eth/eth/ethereum_client.py
+++ b/safe_eth/eth/ethereum_client.py
@@ -112,23 +112,33 @@ def tx_with_exception_handling(func):
     error_with_exception: Dict[str, Exception] = {
         "EIP-155": ChainIdIsRequired,
         "Transaction with the same hash was already imported": TransactionAlreadyImported,
-        "replacement transaction underpriced": ReplacementTransactionUnderpriced,  # https://github.com/ethereum/go-ethereum/blob/eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd/core/tx_pool.go#L72
-        "There is another transaction with same nonce in the queue": ReplacementTransactionUnderpriced,  # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L374
+        "replacement transaction underpriced": ReplacementTransactionUnderpriced,
+        # https://github.com/ethereum/go-ethereum/blob/eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd/core/tx_pool.go#L72
+        "There is another transaction with same nonce in the queue": ReplacementTransactionUnderpriced,
+        # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L374
         "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing "
-        "the fee": TransactionQueueLimitReached,  # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L380
-        "txpool is full": TransactionQueueLimitReached,  # https://github.com/ethereum/go-ethereum/blob/eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd/core/tx_pool.go#L68
-        "transaction underpriced": TransactionGasPriceTooLow,  # https://github.com/ethereum/go-ethereum/blob/eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd/core/tx_pool.go#L64
-        "Transaction gas price is too low": TransactionGasPriceTooLow,  # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L386
+        "the fee": TransactionQueueLimitReached,
+        # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L380
+        "txpool is full": TransactionQueueLimitReached,
+        # https://github.com/ethereum/go-ethereum/blob/eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd/core/tx_pool.go#L68
+        "transaction underpriced": TransactionGasPriceTooLow,
+        # https://github.com/ethereum/go-ethereum/blob/eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd/core/tx_pool.go#L64
+        "Transaction gas price is too low": TransactionGasPriceTooLow,
+        # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L386
         "from not found": FromAddressNotFound,
         "correct nonce": InvalidNonce,
-        "nonce too low": NonceTooLow,  # https://github.com/ethereum/go-ethereum/blob/bbfb1e4008a359a8b57ec654330c0e674623e52f/core/error.go#L46
-        "nonce too high": NonceTooHigh,  # https://github.com/ethereum/go-ethereum/blob/bbfb1e4008a359a8b57ec654330c0e674623e52f/core/error.go#L46
-        "insufficient funds": InsufficientFunds,  # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L389
+        "nonce too low": NonceTooLow,
+        # https://github.com/ethereum/go-ethereum/blob/bbfb1e4008a359a8b57ec654330c0e674623e52f/core/error.go#L46
+        "nonce too high": NonceTooHigh,
+        # https://github.com/ethereum/go-ethereum/blob/bbfb1e4008a359a8b57ec654330c0e674623e52f/core/error.go#L46
+        "insufficient funds": InsufficientFunds,
+        # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L389
         "doesn't have enough funds": InsufficientFunds,
         "sender account not recognized": SenderAccountNotFoundInNode,
         "unknown account": UnknownAccount,
         "exceeds block gas limit": GasLimitExceeded,  # Geth
-        "exceeds current gas limit": GasLimitExceeded,  # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L392
+        "exceeds current gas limit": GasLimitExceeded,
+        # https://github.com/openethereum/openethereum/blob/f1dc6821689c7f47d8fd07dfc0a2c5ad557b98ec/crates/rpc/src/v1/helpers/errors.rs#L392
     }
 
     @wraps(func)
@@ -538,12 +548,10 @@ def get_balances(
         )
 
         return_balances = [
-            {
-                "token_address": token_address,
-                "balance": balance
-                if isinstance(balance, int)
-                else 0,  # A `revert` with bytes can be returned
-            }
+            BalanceDict(
+                balance=balance if isinstance(balance, int) else 0,
+                token_address=token_address,
+            )
             for token_address, balance in zip(token_addresses, balances)
         ]
 
@@ -552,10 +560,9 @@ def get_balances(
 
         # Add ether balance response
         return [
-            {
-                "token_address": None,
-                "balance": self.ethereum_client.get_balance(address),
-            }
+            BalanceDict(
+                balance=self.ethereum_client.get_balance(address), token_address=None
+            )
         ] + return_balances
 
     def get_name(self, erc20_address: ChecksumAddress) -> str:
@@ -710,7 +717,7 @@ def get_total_transfer_history(
                 for address in addresses
             ]
             # Topics for transfer `to` and `from` an address
-            all_topics = [
+            all_topics: List[Sequence[Any]] = [
                 [topic_0, addresses_encoded],  # Topics from
                 [topic_0, None, addresses_encoded],  # Topics to
             ]
@@ -729,11 +736,9 @@ def get_total_transfer_history(
 
             # Decode events. Just pick valid ERC20 Transfer events (ERC721 `Transfer` has the same signature)
             for event in self.slow_w3.eth.get_logs(parameters):
-                event["args"] = self._decode_transfer_log(
-                    event["data"], event["topics"]
-                )
-                if event["args"]:
-                    erc20_events.append(LogReceiptDecoded(event))
+                event_args = self._decode_transfer_log(event["data"], event["topics"])
+                if event_args:
+                    erc20_events.append(LogReceiptDecoded(**event, args=event_args))
 
         erc20_events.sort(key=lambda x: x["blockNumber"])
         return erc20_events
@@ -788,7 +793,7 @@ def get_transfer_history(
         if to_address:
             argument_filters["to"] = to_address
 
-        return erc20.events.Transfer.create_filter(
+        return erc20.events.Transfer.create_filter(  # type: ignore[attr-defined]
             fromBlock=from_block,
             toBlock=to_block,
             address=token_address,
@@ -1000,6 +1005,7 @@ def get_previous_trace(
                     trace_address = trace_address[:-1]
                 else:
                     return trace
+        return None
 
     def get_next_traces(
         self,
@@ -1035,7 +1041,7 @@ def get_next_traces(
         return traces
 
     def trace_block(self, block_identifier: BlockIdentifier) -> List[BlockTrace]:
-        return self.slow_w3.tracing.trace_block(block_identifier)
+        return self.slow_w3.tracing.trace_block(block_identifier)  # type: ignore[attr-defined]
 
     def trace_blocks(
         self, block_identifiers: Sequence[BlockIdentifier]
@@ -1057,14 +1063,14 @@ def trace_blocks(
         ]
 
         results = self.ethereum_client.raw_batch_request(payload)
-        return [trace_list_result_formatter(block_traces) for block_traces in results]
+        return [trace_list_result_formatter(block_traces) for block_traces in results]  # type: ignore[arg-type]
 
     def trace_transaction(self, tx_hash: EthereumHash) -> List[FilterTrace]:
         """
         :param tx_hash:
         :return: List of internal txs for `tx_hash`
         """
-        return self.slow_w3.tracing.trace_transaction(tx_hash)
+        return self.slow_w3.tracing.trace_transaction(tx_hash)  # type: ignore[attr-defined]
 
     def trace_transactions(
         self, tx_hashes: Sequence[EthereumHash]
@@ -1085,7 +1091,7 @@ def trace_transactions(
             for i, tx_hash in enumerate(tx_hashes)
         ]
         results = self.ethereum_client.raw_batch_request(payload)
-        return [trace_list_result_formatter(tx_traces) for tx_traces in results]
+        return [trace_list_result_formatter(tx_traces) for tx_traces in results]  # type: ignore[arg-type]
 
     def trace_filter(
         self,
@@ -1186,7 +1192,7 @@ def trace_filter(
         if to_address:
             parameters["toAddress"] = to_address
 
-        return self.slow_w3.tracing.trace_filter(parameters)
+        return self.slow_w3.tracing.trace_filter(parameters)  # type: ignore[attr-defined]
 
 
 class EthereumClient:
@@ -1238,7 +1244,7 @@ def __init__(
             # Don't spend resources con converting dictionaries to attribute dictionaries
             w3.middleware_onion.remove("attrdict")
             # Disable web3 automatic retry, it's handled on our HTTP Session
-            w3.provider.middlewares = []
+            w3.provider.middlewares = ()
             if self.use_caching_middleware:
                 w3.middleware_onion.add(simple_cache_middleware)
 
@@ -1264,7 +1270,7 @@ def __str__(self):
 
     def raw_batch_request(
         self, payload: Sequence[Dict[str, Any]], batch_size: Optional[int] = None
-    ) -> Iterable[Optional[Dict[str, Any]]]:
+    ) -> Iterable[Union[Optional[Dict[str, Any]], List[Dict[str, Any]]]]:
         """
         Perform a raw batch JSON RPC call
 
@@ -1290,7 +1296,7 @@ def raw_batch_request(
                     response.status_code,
                     response.content,
                 )
-                raise ValueError(f"Batch request error: {response.content}")
+                raise ValueError(f"Batch request error: {response.content!r}")
 
             results = response.json()
 
@@ -1328,7 +1334,7 @@ def get_client_version(self) -> str:
         """
         :return: RPC version information
         """
-        return self.w3.clientVersion
+        return self.w3.client_version
 
     def get_network(self) -> EthereumNetwork:
         """
@@ -1353,8 +1359,9 @@ def get_singleton_factory_address(self) -> Optional[ChecksumAddress]:
         address = os.environ.get(
             "SAFE_SINGLETON_FACTORY_ADDRESS", SAFE_SINGLETON_FACTORY_ADDRESS
         )
-        if self.is_contract(address):
-            return address
+        address_checksum = ChecksumAddress(HexAddress(HexStr(address)))
+        if self.is_contract(address_checksum):
+            return address_checksum
         return None
 
     @cache
@@ -1369,7 +1376,7 @@ def is_eip1559_supported(self) -> bool:
             return False
 
     @cached_property
-    def multicall(self) -> "Multicall":  # noqa F821
+    def multicall(self) -> "Multicall":  # type: ignore # noqa F821
         from .multicall import Multicall
 
         try:
@@ -1567,13 +1574,13 @@ def estimate_gas(
         if from_:
             tx["from"] = from_
         if value:
-            tx["value"] = value
+            tx["value"] = Wei(value)
         if data:
             tx["data"] = data
         if gas:
             tx["gas"] = gas
         if gas_price:
-            tx["gasPrice"] = gas_price
+            tx["gasPrice"] = Wei(gas_price)
         try:
             return self.w3.eth.estimate_gas(tx, block_identifier=block_identifier)
         except (Web3Exception, ValueError):
@@ -1641,15 +1648,15 @@ def set_eip1559_fees(
         :raises: ValueError if EIP1559 not supported
         """
         base_fee_per_gas, max_priority_fee_per_gas = self.estimate_fee_eip1559(tx_speed)
-        tx = TxParams(tx)  # Don't modify provided tx
+        tx = TxParams(**tx)  # Don't modify provided tx
         if "gasPrice" in tx:
             del tx["gasPrice"]
 
         if "chainId" not in tx:
             tx["chainId"] = self.get_chain_id()
 
-        tx["maxPriorityFeePerGas"] = max_priority_fee_per_gas
-        tx["maxFeePerGas"] = base_fee_per_gas + max_priority_fee_per_gas
+        tx["maxPriorityFeePerGas"] = Wei(max_priority_fee_per_gas)
+        tx["maxFeePerGas"] = Wei(base_fee_per_gas + max_priority_fee_per_gas)
         return tx
 
     def get_balance(
@@ -1726,7 +1733,11 @@ def get_transaction_receipts(
         receipts = []
         for tx_receipt in results:
             # Parity returns tx_receipt even is tx is still pending, so we check `blockNumber` is not None
-            if tx_receipt and tx_receipt["blockNumber"] is not None:
+            if (
+                tx_receipt
+                and isinstance(tx_receipt, dict)
+                and tx_receipt["blockNumber"] is not None
+            ):
                 receipts.append(receipt_formatter(tx_receipt))
             else:
                 receipts.append(None)
@@ -1774,7 +1785,7 @@ def get_blocks(
         results = self.raw_batch_request(payload)
         blocks = []
         for raw_block in results:
-            if raw_block:
+            if raw_block and isinstance(raw_block, dict):
                 if "extraData" in raw_block:
                     del raw_block[
                         "extraData"
@@ -1813,30 +1824,30 @@ def build_tx_params(
         :return:
         """
 
-        tx_params: TxParams = tx_params or {}
+        new_tx_params: TxParams = tx_params if tx_params is not None else {}
 
         if from_address:
-            tx_params["from"] = from_address
+            new_tx_params["from"] = from_address
 
         if to_address:
-            tx_params["to"] = to_address
+            new_tx_params["to"] = to_address
 
         if value is not None:
-            tx_params["value"] = value
+            new_tx_params["value"] = Wei(value)
 
         if gas_price is not None:
-            tx_params["gasPrice"] = gas_price
+            new_tx_params["gasPrice"] = Wei(gas_price)
 
         if gas is not None:
-            tx_params["gas"] = gas
+            new_tx_params["gas"] = gas
 
         if nonce is not None:
-            tx_params["nonce"] = nonce
+            new_tx_params["nonce"] = Nonce(nonce)
 
         if chain_id is not None:
-            tx_params["chainId"] = chain_id
+            new_tx_params["chainId"] = chain_id
 
-        return tx_params
+        return new_tx_params
 
     @tx_with_exception_handling
     def send_transaction(self, transaction_dict: TxParams) -> HexBytes:
@@ -1844,7 +1855,13 @@ def send_transaction(self, transaction_dict: TxParams) -> HexBytes:
 
     @tx_with_exception_handling
     def send_raw_transaction(self, raw_transaction: EthereumData) -> HexBytes:
-        return self.w3.eth.send_raw_transaction(bytes(raw_transaction))
+        if isinstance(raw_transaction, bytes):
+            value_bytes = raw_transaction
+        else:
+            value_bytes = bytes.fromhex(
+                raw_transaction.replace("0x", "")
+            )  # Remove '0x' and convert
+        return self.w3.eth.send_raw_transaction(value_bytes)
 
     def send_unsigned_transaction(
         self,
@@ -1936,6 +1953,7 @@ def send_unsigned_transaction(
                     address, block_identifier=block_identifier
                 )
                 number_errors -= 1
+        return HexBytes("")
 
     def send_eth_to(
         self,
diff --git a/safe_eth/eth/multicall.py b/safe_eth/eth/multicall.py
index eb12a6967..b6f29e5c4 100644
--- a/safe_eth/eth/multicall.py
+++ b/safe_eth/eth/multicall.py
@@ -9,7 +9,7 @@
 import eth_abi
 from eth_abi.exceptions import DecodingError
 from eth_account.signers.local import LocalAccount
-from eth_typing import BlockIdentifier, BlockNumber, ChecksumAddress
+from eth_typing import BlockIdentifier, BlockNumber, ChecksumAddress, HexAddress, HexStr
 from hexbytes import HexBytes
 from web3 import Web3
 from web3._utils.abi import map_abi_data
@@ -77,14 +77,19 @@ def __init__(
     ):
         ethereum_network = ethereum_client.get_network()
         address = multicall_contract_address or self.ADDRESSES.get(ethereum_network)
-        if not address:
+        mainnet_address = self.ADDRESSES.get(EthereumNetwork.MAINNET)
+        if not address and mainnet_address:
             # Try with Multicall V3 deterministic address
-            address = self.ADDRESSES.get(EthereumNetwork.MAINNET)
+            address = ChecksumAddress(HexAddress(HexStr(mainnet_address)))
             if not ethereum_client.is_contract(address):
                 raise EthereumNetworkNotSupported(
                     "Multicall contract not available for %s", ethereum_network.name
                 )
-        super().__init__(address, ethereum_client)
+
+        if not address:
+            raise ValueError("Contract address cannot be none")
+
+        super().__init__(ChecksumAddress(HexAddress(HexStr(address))), ethereum_client)
 
     def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_multicall_v3_contract
@@ -100,8 +105,8 @@ def deploy_contract(
         :param deployer_account: Ethereum Account
         :return: ``EthereumTxSent`` with the deployed contract address, ``None`` if already deployed
         """
-        contract_fn = cls.get_contract_fn(cls)
-        contract = contract_fn(ethereum_client.w3)
+        contract_fn = cls.get_contract_fn(cls)  # type: ignore[arg-type]
+        contract = contract_fn(ethereum_client.w3, None)
         constructor_data = contract.constructor().build_transaction(
             get_empty_tx_params()
         )["data"]
@@ -111,6 +116,7 @@ def deploy_contract(
                 deployer_account, constructor_data
             )
 
+            assert ethereum_tx_sent.contract_address is not None
             contract_address = ethereum_tx_sent.contract_address
             logger.info(
                 "Deployed Multicall V2 Contract %s by %s",
@@ -127,7 +133,7 @@ def deploy_contract(
     @staticmethod
     def _build_payload(
         contract_functions: Sequence[ContractFunction],
-    ) -> Tuple[List[Tuple[ChecksumAddress, bytes]], List[List[Any]]]:
+    ) -> Tuple[List[Tuple[ChecksumAddress, HexBytes]], List[List[Any]]]:
         targets_with_data = []
         output_types = []
         for contract_function in contract_functions:
@@ -147,7 +153,7 @@ def _build_payload_same_function(
         self,
         contract_function: ContractFunction,
         contract_addresses: Sequence[ChecksumAddress],
-    ) -> Tuple[List[Tuple[ChecksumAddress, bytes]], List[List[Any]]]:
+    ) -> Tuple[List[Tuple[ChecksumAddress, HexBytes]], List[List[Any]]]:
         targets_with_data = []
         output_types = []
         tx_data = HexBytes(contract_function._encode_transaction_data())
@@ -182,6 +188,7 @@ def _decode_data(self, output_type: Sequence[str], data: bytes) -> Optional[Any]
                     "Cannot decode %s using output-type %s", data, output_type
                 )
                 return data
+        return None
 
     def _aggregate(
         self,
@@ -200,7 +207,7 @@ def _aggregate(
         ]
         try:
             return self.contract.functions.aggregate(aggregate_parameter).call(
-                block_identifier=block_identifier
+                block_identifier=block_identifier or "latest"
             )
         except (ContractLogicError, OverflowError):
             raise BatchCallFunctionFailed
@@ -223,7 +230,7 @@ def aggregate(
             targets_with_data, block_identifier=block_identifier
         )
         decoded_results = [
-            self._decode_data(output_type, data)
+            self._decode_data(output_type, data) if data is not None else None
             for output_type, data in zip(output_types, results)
         ]
         return block_number, decoded_results
@@ -250,7 +257,7 @@ def _try_aggregate(
         try:
             result = self.contract.functions.tryAggregate(
                 require_success, aggregate_parameter
-            ).call(block_identifier=block_identifier)
+            ).call(block_identifier=block_identifier or "latest")
 
             if require_success and b"" in (data for _, data in result):
                 # `b''` values are decoding errors/missing contracts/missing functions
@@ -287,7 +294,7 @@ def try_aggregate(
             MulticallDecodedResult(
                 multicall_result.success,
                 self._decode_data(output_type, multicall_result.return_data)
-                if multicall_result.success
+                if multicall_result.success and multicall_result.return_data is not None
                 else multicall_result.return_data,
             )
             for output_type, multicall_result in zip(output_types, results)
@@ -323,7 +330,7 @@ def try_aggregate_same_function(
             MulticallDecodedResult(
                 multicall_result.success,
                 self._decode_data(output_type, multicall_result.return_data)
-                if multicall_result.success
+                if multicall_result.success and multicall_result.return_data is not None
                 else multicall_result.return_data,
             )
             for output_type, multicall_result in zip(output_types, results)
diff --git a/safe_eth/eth/oracles/cowswap.py b/safe_eth/eth/oracles/cowswap.py
index a52a23806..881b47c0f 100644
--- a/safe_eth/eth/oracles/cowswap.py
+++ b/safe_eth/eth/oracles/cowswap.py
@@ -1,7 +1,10 @@
 import logging
-from typing import Optional
+from typing import Optional, Union, cast
+
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 
 from safe_eth.eth.clients.cowswap import CowSwapAPI, OrderKind
+from safe_eth.eth.clients.cowswap.cow_swap_api import AmountResponse, ErrorResponse
 
 from .. import EthereumClient, EthereumNetworkNotSupported
 from .exceptions import CannotGetPriceFromOracle
@@ -50,15 +53,24 @@ def get_price(
         token_address_2 = token_address_2 or self.api.weth_address
         if token_address_1 == token_address_2:
             return 1.0
-
-        token_1_decimals = get_decimals(token_address_1, self.ethereum_client)
+        token_address_1_checksum = ChecksumAddress(HexAddress(HexStr(token_address_1)))
+        token_address_2_checksum = ChecksumAddress(HexAddress(HexStr(token_address_2)))
+        token_1_decimals = get_decimals(token_address_1_checksum, self.ethereum_client)
         try:
-            result = self.api.get_estimated_amount(
-                token_address_1, token_address_2, OrderKind.SELL, 10**token_1_decimals
+            result: Union[
+                AmountResponse, ErrorResponse
+            ] = self.api.get_estimated_amount(
+                token_address_1_checksum,
+                token_address_2_checksum,
+                OrderKind.SELL,
+                10**token_1_decimals,
             )
-            if "buyAmount" in result:
+            if "buyAmount" in result and "sellAmount" in result:
+                result = cast(AmountResponse, result)
                 # Decimals needs to be adjusted
-                token_2_decimals = get_decimals(token_address_2, self.ethereum_client)
+                token_2_decimals = get_decimals(
+                    token_address_2_checksum, self.ethereum_client
+                )
                 return (
                     float(result["buyAmount"])
                     / result["sellAmount"]
@@ -68,10 +80,9 @@ def get_price(
             exception = None
         except IOError as exc:
             exception = exc
-            result = {}
 
         message = (
-            f"Cannot get price from CowSwap {result} "
+            f"Cannot get price from CowSwap {{}} "
             f"for token-1={token_address_1} to token-2={token_address_2}"
         )
         logger.debug(message)
diff --git a/safe_eth/eth/oracles/oracles.py b/safe_eth/eth/oracles/oracles.py
index 37dab3688..3768a8aab 100644
--- a/safe_eth/eth/oracles/oracles.py
+++ b/safe_eth/eth/oracles/oracles.py
@@ -9,7 +9,7 @@
 from eth_abi import decode as decode_abi
 from eth_abi.exceptions import DecodingError
 from eth_abi.packed import encode_packed
-from eth_typing import ChecksumAddress
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 from hexbytes import HexBytes
 from web3.contract import Contract
 from web3.exceptions import Web3Exception
@@ -23,7 +23,12 @@
     get_uniswap_v2_pair_contract,
     get_uniswap_v2_router_contract,
 )
-from ..utils import fast_bytes_to_checksum_address, fast_keccak, get_empty_tx_params
+from ..utils import (
+    bytes_to_float,
+    fast_bytes_to_checksum_address,
+    fast_keccak,
+    get_empty_tx_params,
+)
 from .abis.aave_abis import AAVE_ATOKEN_ABI
 from .abis.balancer_abis import balancer_pool_abi
 from .abis.cream_abis import cream_ctoken_abi
@@ -70,7 +75,7 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
 
 class ComposedPriceOracle(BaseOracle):
     @abstractmethod
-    def get_underlying_tokens(self, *args) -> List[Tuple[UnderlyingToken]]:
+    def get_underlying_tokens(self, *args) -> List[UnderlyingToken]:
         raise NotImplementedError
 
 
@@ -132,10 +137,14 @@ def get_uniswap_exchange(self, token_address: str) -> str:
     def _get_balances_without_batching(
         self, uniswap_exchange_address: str, token_address: str
     ):
-        balance = self.ethereum_client.get_balance(uniswap_exchange_address)
-        token_decimals = self.ethereum_client.erc20.get_decimals(token_address)
+        uniswap_exchange_address_checksum = ChecksumAddress(
+            HexAddress(HexStr(uniswap_exchange_address))
+        )
+        token_address_checksum = ChecksumAddress(HexAddress(HexStr(token_address)))
+        balance = self.ethereum_client.get_balance(uniswap_exchange_address_checksum)
+        token_decimals = self.ethereum_client.erc20.get_decimals(token_address_checksum)
         token_balance = self.ethereum_client.erc20.get_balance(
-            uniswap_exchange_address, token_address
+            uniswap_exchange_address_checksum, token_address_checksum
         )
         return balance, token_decimals, token_balance
 
@@ -149,7 +158,9 @@ def _get_balances_using_batching(
             "params": [uniswap_exchange_address, "latest"],
             "id": 0,
         }
-        erc20 = get_erc20_contract(self.w3, token_address)
+        erc20 = get_erc20_contract(
+            self.w3, ChecksumAddress(HexAddress(HexStr(token_address)))
+        )
         params = get_empty_tx_params()
         decimals_data = erc20.functions.decimals().build_transaction(params)["data"]
         token_balance_data = erc20.functions.balanceOf(
@@ -252,7 +263,7 @@ def __init__(
             self.ROUTER_ADDRESSES[EthereumNetwork.MAINNET],
         )
         self.router = get_uniswap_v2_router_contract(
-            ethereum_client.w3, self.router_address
+            ethereum_client.w3, ChecksumAddress(HexAddress(HexStr(self.router_address)))
         )
 
     @classmethod
@@ -265,9 +276,15 @@ def is_available(
         :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise
         """
         return ethereum_client.is_contract(
-            cls.ROUTER_ADDRESSES.get(
-                ethereum_client.get_network(),
-                cls.ROUTER_ADDRESSES[EthereumNetwork.MAINNET],
+            ChecksumAddress(
+                HexAddress(
+                    HexStr(
+                        cls.ROUTER_ADDRESSES.get(
+                            ethereum_client.get_network(),
+                            cls.ROUTER_ADDRESSES[EthereumNetwork.MAINNET],
+                        )
+                    )
+                )
             )
         )
 
@@ -346,7 +363,7 @@ def get_reserves(self, pair_address: str) -> Tuple[int, int]:
         :return: Reserves of `token_address` and `token_address_2` used to price trades and distribute liquidity.
         """
         pair_contract = get_uniswap_v2_pair_contract(
-            self.ethereum_client.w3, pair_address
+            self.ethereum_client.w3, ChecksumAddress(HexAddress(HexStr(pair_address)))
         )
         # Reserves return token_1 reserves, token_2 reserves and block timestamp (mod 2**32) of last interaction
         reserves_1, reserves_2, _ = pair_contract.functions.getReserves().call()
@@ -424,10 +441,10 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
                 self.ethereum_client.w3, pool_token_address
             )
             (
-                (reserves_1, reserves_2, _),
+                token_reserves,
                 token_address_1,
                 token_address_2,
-                total_supply,
+                total_supply_response,
             ) = self.ethereum_client.batch_call(
                 [
                     pair_contract.functions.getReserves(),
@@ -436,6 +453,12 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
                     pair_contract.functions.totalSupply(),
                 ]
             )
+            if token_reserves is not None:
+                (reserves_1, reserves_2, _) = token_reserves
+            else:
+                reserves_1, reserves_2 = 0.0, 0.0
+
+            total_supply = bytes_to_float(total_supply_response)
             decimals_1 = get_decimals(token_address_1, self.ethereum_client)
             decimals_2 = get_decimals(token_address_2, self.ethereum_client)
 
@@ -447,7 +470,7 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
                 (reserves_1, reserves_2),
             ):
                 try:
-                    price = self.get_price(token_address)
+                    price = self.get_price(str(token_address))
                     total_value = (reserves / 10**decimals_1) * price
                     return (total_value * 2) / (total_supply / 1e18)
                 except CannotGetPriceFromOracle:
@@ -461,6 +484,7 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
             message = f"Cannot get uniswap v2 price for pool token={pool_token_address}"
             logger.debug(message)
             raise CannotGetPriceFromOracle(message) from e
+        return 0
 
 
 class AaveOracle(PriceOracle):
@@ -493,8 +517,9 @@ def get_price(self, token_address: str) -> float:
                 "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9"
             )
         try:
+            token_address_checksum = ChecksumAddress(HexAddress(HexStr(token_address)))
             underlying_token = (
-                self.w3.eth.contract(token_address, abi=AAVE_ATOKEN_ABI)
+                self.w3.eth.contract(token_address_checksum, abi=AAVE_ATOKEN_ABI)
                 .functions.UNDERLYING_ASSET_ADDRESS()
                 .call()
             )
@@ -529,8 +554,9 @@ def is_available(
 
     def get_price(self, token_address: str) -> float:
         try:
+            address_checksum = ChecksumAddress(HexAddress(HexStr(token_address)))
             underlying_token = (
-                self.w3.eth.contract(token_address, abi=cream_ctoken_abi)
+                self.w3.eth.contract(address_checksum, abi=cream_ctoken_abi)
                 .functions.underlying()
                 .call()
             )
@@ -542,7 +568,7 @@ def get_price(self, token_address: str) -> float:
 
 
 class ZerionComposedOracle(ComposedPriceOracle):
-    ZERION_ADAPTER_ADDRESS = None
+    ZERION_ADAPTER_ADDRESS: Optional[str] = None
 
     def __init__(
         self,
@@ -580,10 +606,14 @@ def zerion_adapter_contract(self) -> Optional[Contract]:
         """
         :return: https://curve.readthedocs.io/registry-registry.html
         """
-        if self.ethereum_client.is_contract(self.zerion_adapter_address):
-            return self.w3.eth.contract(
-                self.zerion_adapter_address, abi=ZERION_TOKEN_ADAPTER_ABI
-            )
+        address = (
+            ChecksumAddress(HexAddress(HexStr(self.zerion_adapter_address)))
+            if self.zerion_adapter_address
+            else None
+        )
+        if address and self.ethereum_client.is_contract(address):
+            return self.w3.eth.contract(address, abi=ZERION_TOKEN_ADAPTER_ABI)
+        return None
 
     def get_underlying_tokens(
         self, token_address: ChecksumAddress
@@ -643,7 +673,10 @@ def get_underlying_tokens(
         More info on https://resources.curve.fi/base-features/understanding-gauges
         """
         if CURVE_GAUGE_TO_LP_TOKEN.get(token_address):
-            return super().get_underlying_tokens(CURVE_GAUGE_TO_LP_TOKEN[token_address])
+            address = ChecksumAddress(
+                HexAddress(HexStr(CURVE_GAUGE_TO_LP_TOKEN[token_address]))
+            )
+            return super().get_underlying_tokens(address)
 
         return super().get_underlying_tokens(token_address)
 
@@ -710,7 +743,7 @@ def is_available(
 
     def get_underlying_tokens(
         self, token_address: ChecksumAddress
-    ) -> List[Tuple[float, ChecksumAddress]]:
+    ) -> List[UnderlyingToken]:
         """
         :param token_address:
         :return: Price per share and underlying token
@@ -766,12 +799,13 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
             balancer_pool_contract = self.w3.eth.contract(
                 pool_token_address, abi=balancer_pool_abi
             )
-            current_tokens, total_supply = self.ethereum_client.batch_call(
+            current_tokens, total_supply_response = self.ethereum_client.batch_call(
                 [
                     balancer_pool_contract.functions.getCurrentTokens(),
                     balancer_pool_contract.functions.totalSupply(),
                 ]
             )
+            total_supply = bytes_to_float(total_supply_response)
             if not current_tokens:
                 raise ValueError
             number_tokens = len(current_tokens)
@@ -785,7 +819,9 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
                     for token_address in current_tokens
                 ]
                 + [
-                    get_erc20_contract(self.w3, token_address).functions.decimals()
+                    get_erc20_contract(
+                        self.w3, ChecksumAddress(HexAddress(HexStr(str(token_address))))
+                    ).functions.decimals()
                     for token_address in current_tokens
                 ]
             )
@@ -795,10 +831,12 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
                 self.price_oracle.get_price(token_address)
                 for token_address in current_tokens
             ]
-            total_eth_value = 0
-            for token_balance, token_decimal, token_price in zip(
+            total_eth_value = 0.0
+            for token_balance_bytes, token_decimal_bytes, token_price in zip(
                 token_balances, token_decimals, token_prices
             ):
+                token_balance = bytes_to_float(token_balance_bytes)
+                token_decimal = bytes_to_float(token_decimal_bytes)
                 total_eth_value += (token_balance / 10**token_decimal) * token_price
             return total_eth_value / (total_supply / 1e18)
         except (Web3Exception, DecodingError, ValueError):
@@ -821,12 +859,14 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
             balancer_pool_contract = self.w3.eth.contract(
                 pool_token_address, abi=mooniswap_abi
             )
-            tokens, total_supply = self.ethereum_client.batch_call(
+            tokens, total_supply_response = self.ethereum_client.batch_call(
                 [
                     balancer_pool_contract.functions.getTokens(),
                     balancer_pool_contract.functions.totalSupply(),
                 ]
             )
+            total_supply = bytes_to_float(total_supply_response)
+
             if not tokens:
                 raise ValueError
             if len(tokens) == 1 or any(
@@ -838,13 +878,21 @@ def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float:
                 for token in tokens:
                     try:
                         price = self.price_oracle.get_price(token)
-                        token_contract = get_erc20_contract(self.w3, token)
-                        token_balance, token_decimals = self.ethereum_client.batch_call(
+                        token_contract = get_erc20_contract(
+                            self.w3, ChecksumAddress(HexAddress(HexStr(str(token))))
+                        )
+                        (
+                            token_balance_response,
+                            token_decimals_response,
+                        ) = self.ethereum_client.batch_call(
                             [
                                 token_contract.functions.balanceOf(pool_token_address),
                                 token_contract.functions.decimals(),
                             ]
                         )
+                        token_balance = bytes_to_float(token_balance_response)
+                        token_decimals = bytes_to_float(token_decimals_response)
+
                         total_value = (token_balance / 10**token_decimals) * price
                         return (total_value * 2) / (total_supply / 1e18)
                     except CannotGetPriceFromOracle:
diff --git a/safe_eth/eth/oracles/superfluid.py b/safe_eth/eth/oracles/superfluid.py
index ad7f56c30..b62cfdeec 100644
--- a/safe_eth/eth/oracles/superfluid.py
+++ b/safe_eth/eth/oracles/superfluid.py
@@ -1,4 +1,5 @@
 from eth_abi.exceptions import DecodingError
+from eth_utils import to_checksum_address
 from web3.exceptions import Web3Exception
 
 from .. import EthereumClient, EthereumNetwork
@@ -35,8 +36,11 @@ def is_available(
 
     def get_price(self, token_address: str) -> float:
         try:
+            token_address_checksum = to_checksum_address(token_address)
             underlying_token = (
-                self.w3.eth.contract(token_address, abi=super_token_abi)
+                self.w3.eth.contract(
+                    address=token_address_checksum, abi=super_token_abi
+                )
                 .functions.getUnderlyingToken()
                 .call()
             )
diff --git a/safe_eth/eth/oracles/uniswap_v3.py b/safe_eth/eth/oracles/uniswap_v3.py
index e31b227d1..157e389c3 100644
--- a/safe_eth/eth/oracles/uniswap_v3.py
+++ b/safe_eth/eth/oracles/uniswap_v3.py
@@ -4,7 +4,7 @@
 from typing import Optional
 
 from eth_abi.exceptions import DecodingError
-from eth_typing import ChecksumAddress
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 from web3.contract import Contract
 from web3.exceptions import Web3Exception
 
@@ -63,8 +63,15 @@ def is_available(
         :param uniswap_v3_router_address: Provide a custom `SwapRouter02` address
         :return: `True` if Uniswap V3 is available for the EthereumClient provided, `False` otherwise
         """
-        router_address = uniswap_v3_router_address or cls.ROUTER_ADDRESSES.get(
-            ethereum_client.get_network(), cls.DEFAULT_ROUTER_ADDRESS
+        router_address = ChecksumAddress(
+            HexAddress(
+                HexStr(
+                    uniswap_v3_router_address
+                    or cls.ROUTER_ADDRESSES.get(
+                        ethereum_client.get_network(), cls.DEFAULT_ROUTER_ADDRESS
+                    )
+                )
+            )
         )
         return ethereum_client.is_contract(router_address)
 
@@ -89,7 +96,8 @@ def router(self) -> Contract:
 
         :return: Uniswap V3 Router Contract
         """
-        return self.w3.eth.contract(self.router_address, abi=uniswap_v3_router_abi)
+        router_address = ChecksumAddress(HexAddress(HexStr(self.router_address)))
+        return self.w3.eth.contract(address=router_address, abi=uniswap_v3_router_abi)
 
     @cached_property
     def weth_address(self) -> ChecksumAddress:
@@ -151,23 +159,35 @@ def get_price(
         token_decimals = get_decimals(token_address, self.ethereum_client)
         token_2_decimals = get_decimals(token_address_2, self.ethereum_client)
 
+        token_address_checksum = ChecksumAddress(HexAddress(HexStr(token_address)))
+        token_address2_checksum = (
+            ChecksumAddress(HexAddress(HexStr(token_address_2)))
+            if token_address_2
+            else None
+        )
+
         pool_contract = self.w3.eth.contract(pool_address, abi=uniswap_v3_pool_abi)
         try:
             (
                 token_balance,
                 token_2_balance,
-                (sqrt_price_x96, _, _, _, _, _, _),
+                pool_contract_balance,
             ) = self.ethereum_client.batch_call(
                 [
                     get_erc20_contract(
-                        self.ethereum_client.w3, token_address
+                        self.ethereum_client.w3, token_address_checksum
                     ).functions.balanceOf(pool_address),
                     get_erc20_contract(
-                        self.ethereum_client.w3, token_address_2
+                        self.ethereum_client.w3, token_address2_checksum
                     ).functions.balanceOf(pool_address),
                     pool_contract.functions.slot0(),
                 ]
             )
+            if pool_contract_balance is not None:
+                (sqrt_price_x96, _, _, _, _, _, _) = pool_contract_balance
+            else:
+                sqrt_price_x96 = 0
+
             if (token_balance / 10**token_decimals) < 2 or (
                 token_2_balance / 10**token_2_decimals
             ) < 2:
diff --git a/safe_eth/eth/utils.py b/safe_eth/eth/utils.py
index c6a691962..36185ec18 100644
--- a/safe_eth/eth/utils.py
+++ b/safe_eth/eth/utils.py
@@ -1,6 +1,6 @@
 import os
 from functools import lru_cache
-from typing import Union
+from typing import Any, Union
 
 import eth_abi
 from eth._utils.address import generate_contract_address
@@ -23,7 +23,7 @@ def get_empty_tx_params() -> TxParams:
 
 
 @lru_cache(maxsize=int(os.getenv("CACHE_KECCAK", 512)))
-def _keccak_256(value: bytes) -> bytes:
+def _keccak_256(value: bytes) -> keccak_256:
     return keccak_256(value)
 
 
@@ -34,7 +34,7 @@ def fast_keccak(value: bytes) -> Hash32:
     :param value:
     :return: Keccak256 used by ethereum as `HexBytes`
     """
-    return HexBytes(_keccak_256(value).digest())
+    return Hash32(HexBytes(_keccak_256(value).digest()))
 
 
 def fast_keccak_text(value: str) -> Hash32:
@@ -233,3 +233,23 @@ def mk_contract_address_2(
     init_code_hash = fast_keccak(init_code)
     contract_address = fast_keccak(HexBytes("ff") + from_ + salt + init_code_hash)
     return fast_bytes_to_checksum_address(contract_address[12:])
+
+
+def bytes_to_float(value: Any) -> float:
+    """
+    Convert a value of type Any to float.
+
+    :param value: The value to convert.
+    :return: The converted float value.
+    :raises ValueError: If the value cannot be converted to float.
+    """
+    assert value is not None, "Cannot convert None to float"
+    if isinstance(value, (int, float)):
+        return float(value)
+    elif isinstance(value, bytes):
+        try:
+            return float(int.from_bytes(value, "big"))
+        except (ValueError, OverflowError) as e:
+            raise ValueError(f"Cannot convert bytes to float: {e}")
+    else:
+        raise ValueError(f"Unsupported type for conversion to float: {type(value)}")
diff --git a/safe_eth/safe/addresses.py b/safe_eth/safe/addresses.py
index 8c320dd83..290fbfd6a 100644
--- a/safe_eth/safe/addresses.py
+++ b/safe_eth/safe/addresses.py
@@ -5,12 +5,12 @@
 """
 from typing import Dict, List, Tuple
 
-from eth_typing import ChecksumAddress
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 
 from safe_eth.eth import EthereumNetwork
 
-SAFE_SIMULATE_TX_ACCESSOR_ADDRESS: ChecksumAddress = (
-    "0x3d4BA2E0884aa488718476ca2FB8Efc291A46199"
+SAFE_SIMULATE_TX_ACCESSOR_ADDRESS: ChecksumAddress = ChecksumAddress(
+    HexAddress(HexStr("0x3d4BA2E0884aa488718476ca2FB8Efc291A46199"))
 )
 
 MASTER_COPIES: Dict[EthereumNetwork, List[Tuple[str, int, str]]] = {
diff --git a/safe_eth/safe/api/base_api.py b/safe_eth/safe/api/base_api.py
index da19ba4bc..9e28ea8fc 100644
--- a/safe_eth/safe/api/base_api.py
+++ b/safe_eth/safe/api/base_api.py
@@ -34,7 +34,7 @@ def __init__(
         """
         self.network = network
         self.ethereum_client = ethereum_client
-        self.base_url = base_url or self.URL_BY_NETWORK.get(network)
+        self.base_url = base_url or self.URL_BY_NETWORK.get(network, "")
         if not self.base_url:
             raise EthereumNetworkNotSupported(network)
         self.http_session = prepare_http_session(10, 100)
diff --git a/safe_eth/safe/api/transaction_service_api/entities.py b/safe_eth/safe/api/transaction_service_api/entities.py
index 9e0161748..49d54d5ed 100644
--- a/safe_eth/safe/api/transaction_service_api/entities.py
+++ b/safe_eth/safe/api/transaction_service_api/entities.py
@@ -1,6 +1,8 @@
-from typing import Any, List, Optional, TypedDict
+from typing import Any, List, Optional, TypedDict, Union
 
-from eth_typing import AnyAddress, HexStr
+from eth_typing import Address, ChecksumAddress, HexAddress, HexStr
+
+AnyAddressType = Union[Address, HexAddress, ChecksumAddress]
 
 
 class ParameterDecoded(TypedDict):
@@ -22,22 +24,22 @@ class Erc20Info(TypedDict):
 
 
 class Balance(TypedDict):
-    token_address: Optional[AnyAddress]
+    token_address: Optional[AnyAddressType]
     token: Optional[Erc20Info]
     balance: int
 
 
 class DelegateUser(TypedDict):
-    safe: Optional[AnyAddress]
-    delegate: AnyAddress
-    delegator: AnyAddress
+    safe: Optional[AnyAddressType]
+    delegate: AnyAddressType
+    delegator: AnyAddressType
     label: str
 
 
 class MessageConfirmation(TypedDict):
     created: str
     modified: str
-    owner: AnyAddress
+    owner: AnyAddressType
     signature: HexStr
     signatureType: str
 
@@ -45,17 +47,17 @@ class MessageConfirmation(TypedDict):
 class Message(TypedDict):
     created: str
     modified: str
-    safe: AnyAddress
+    safe: AnyAddressType
     messageHash: HexStr
     message: Any
-    proposedBy: AnyAddress
+    proposedBy: AnyAddressType
     safeAppId: int
     confirmations: Optional[List[MessageConfirmation]]
     preparedSignature: Optional[HexStr]
 
 
 class TransactionConfirmation(TypedDict):
-    owner: AnyAddress
+    owner: AnyAddressType
     submissionDate: str
     transactionHash: HexStr
     signature: HexStr
@@ -63,16 +65,16 @@ class TransactionConfirmation(TypedDict):
 
 
 class Transaction(TypedDict):
-    safe: AnyAddress
-    to: AnyAddress
+    safe: AnyAddressType
+    to: AnyAddressType
     value: str
     data: Optional[HexStr]
     operation: int
-    gasToken: Optional[AnyAddress]
+    gasToken: Optional[AnyAddressType]
     safeTxGas: int
     baseGas: int
     gasPrice: str
-    refundReceiver: Optional[AnyAddress]
+    refundReceiver: Optional[AnyAddressType]
     nonce: int
     execution_date: str
     submission_date: str
@@ -80,8 +82,8 @@ class Transaction(TypedDict):
     blockNumber: Optional[int]
     transactionHash: HexStr
     safeTxHash: HexStr
-    proposer: AnyAddress
-    executor: Optional[AnyAddress]
+    proposer: AnyAddressType
+    executor: Optional[AnyAddressType]
     isExecuted: bool
     isSuccessful: Optional[bool]
     ethGasPrice: Optional[str]
diff --git a/safe_eth/safe/api/transaction_service_api/transaction_service_api.py b/safe_eth/safe/api/transaction_service_api/transaction_service_api.py
index 64e824546..ab865df45 100644
--- a/safe_eth/safe/api/transaction_service_api/transaction_service_api.py
+++ b/safe_eth/safe/api/transaction_service_api/transaction_service_api.py
@@ -4,6 +4,7 @@
 from urllib.parse import urlencode
 
 from eth_typing import ChecksumAddress, Hash32, HexStr
+from eth_utils import to_checksum_address
 from hexbytes import HexBytes
 
 from safe_eth.eth import EthereumClient, EthereumNetwork
@@ -74,10 +75,14 @@ def data_decoded_to_text(cls, data_decoded: Dict[str, Any]) -> Optional[str]:
                     + ":\n - "
                     + "\n - ".join(
                         [
-                            cls.data_decoded_to_text(
-                                decoded_value.get("decodedData", {})
-                            )
+                            decoded_text
                             for decoded_value in parameter.get("decodedValue", {})
+                            if (
+                                decoded_text := cls.data_decoded_to_text(
+                                    decoded_value.get("decodedData", {})
+                                )
+                            )
+                            is not None
                         ]
                     )
                     + "\n"
@@ -101,7 +106,7 @@ def parse_signatures(cls, raw_tx: Transaction) -> Optional[bytes]:
         """
         if raw_tx["signatures"]:
             # Tx was executed and signatures field is populated
-            return raw_tx["signatures"]
+            return HexBytes(raw_tx["signatures"])
         elif raw_tx["confirmations"]:
             # Parse offchain transactions
             return b"".join(
@@ -113,6 +118,7 @@ def parse_signatures(cls, raw_tx: Transaction) -> Optional[bytes]:
                     if confirmation["signatureType"] == "EOA"
                 ]
             )
+        return None
 
     def create_delegate_message_hash(self, delegate_address: ChecksumAddress) -> Hash32:
         return eip712_encode_hash(
@@ -124,7 +130,7 @@ def _build_transaction_service_tx(
     ) -> TransactionServiceTx:
         signatures = self.parse_signatures(tx_raw)
         safe_tx = TransactionServiceTx(
-            tx_raw["proposer"],
+            to_checksum_address(tx_raw["proposer"]),
             self.ethereum_client,
             tx_raw["safe"],
             tx_raw["to"],
@@ -146,7 +152,8 @@ def _build_transaction_service_tx(
 
         if safe_tx.safe_tx_hash != safe_tx_hash:
             raise ApiSafeTxHashNotMatchingException(
-                f"API safe-tx-hash: {safe_tx_hash.hex()} doesn't match the calculated safe-tx-hash: {safe_tx.safe_tx_hash.hex()}"
+                f"API safe-tx-hash: {safe_tx_hash.hex() if isinstance(safe_tx_hash, bytes) else safe_tx_hash} "
+                f"doesn't match the calculated safe-tx-hash: {safe_tx.safe_tx_hash.hex()}"
             )
 
         return safe_tx
@@ -159,7 +166,7 @@ def get_balances(self, safe_address: str) -> List[Balance]:
         """
         response = self._get_request(f"/api/v1/safes/{safe_address}/balances/")
         if not response.ok:
-            raise SafeAPIException(f"Cannot get balances: {response.content}")
+            raise SafeAPIException(f"Cannot get balances: {response.content!r}")
         return response.json()
 
     def get_safe_transaction(
@@ -175,7 +182,7 @@ def get_safe_transaction(
         )
         if not response.ok:
             raise SafeAPIException(
-                f"Cannot get transaction with safe-tx-hash={safe_tx_hash_str}: {response.content}"
+                f"Cannot get transaction with safe-tx-hash={safe_tx_hash_str}: {response.content!r}"
             )
 
         if not self.ethereum_client:
@@ -186,7 +193,7 @@ def get_safe_transaction(
         result = response.json()
         safe_tx = self._build_transaction_service_tx(safe_tx_hash, result)
 
-        return safe_tx, safe_tx.tx_hash
+        return safe_tx, HexBytes(safe_tx.tx_hash) if safe_tx.tx_hash else None
 
     def get_transactions(
         self, safe_address: ChecksumAddress, **kwargs: Dict[str, Union[str, int, bool]]
@@ -206,13 +213,13 @@ def get_transactions(
 
         response = self._get_request(url)
         if not response.ok:
-            raise SafeAPIException(f"Cannot get transactions: {response.content}")
+            raise SafeAPIException(f"Cannot get transactions: {response.content!r}")
 
         transactions = response.json().get("results", [])
 
         if safe_tx_hash_arg := kwargs.get("safe_tx_hash", None):
             # Validation that the calculated safe_tx_hash is the same as the safe_tx_hash provided for filter.
-            safe_tx_hash = HexBytes(safe_tx_hash_arg)
+            safe_tx_hash = HexBytes(str(safe_tx_hash_arg))
             [
                 self._build_transaction_service_tx(safe_tx_hash, tx)
                 for tx in transactions
@@ -228,7 +235,7 @@ def get_delegates(self, safe_address: ChecksumAddress) -> List[DelegateUser]:
         """
         response = self._get_request(f"/api/v2/delegates/?safe={safe_address}")
         if not response.ok:
-            raise SafeAPIException(f"Cannot get delegates: {response.content}")
+            raise SafeAPIException(f"Cannot get delegates: {response.content!r}")
         return response.json().get("results", [])
 
     def get_safes_for_owner(
@@ -241,7 +248,7 @@ def get_safes_for_owner(
         """
         response = self._get_request(f"/api/v1/owners/{owner_address}/safes/")
         if not response.ok:
-            raise SafeAPIException(f"Cannot get delegates: {response.content}")
+            raise SafeAPIException(f"Cannot get delegates: {response.content!r}")
         return response.json().get("safes", [])
 
     def post_signatures(self, safe_tx_hash: bytes, signatures: bytes) -> bool:
@@ -251,14 +258,14 @@ def post_signatures(self, safe_tx_hash: bytes, signatures: bytes) -> bool:
         :param signatures:
         :return: True if new confirmation was created
         """
-        safe_tx_hash = HexBytes(safe_tx_hash).hex()
+        safe_tx_hash_str = HexBytes(safe_tx_hash).hex()
         response = self._post_request(
-            f"/api/v1/multisig-transactions/{safe_tx_hash}/confirmations/",
+            f"/api/v1/multisig-transactions/{safe_tx_hash_str}/confirmations/",
             payload={"signature": HexBytes(signatures).hex()},
         )
         if not response.ok:
             raise SafeAPIException(
-                f"Cannot post signatures for tx with safe-tx-hash={safe_tx_hash}: {response.content}"
+                f"Cannot post signatures for tx with safe-tx-hash={safe_tx_hash_str}: {response.content!r}"
             )
         return True
 
@@ -280,7 +287,7 @@ def add_delegate(
             add_payload["safe"] = safe_address
         response = self._post_request("/api/v2/delegates/", add_payload)
         if not response.ok:
-            raise SafeAPIException(f"Cannot add delegate: {response.content}")
+            raise SafeAPIException(f"Cannot add delegate: {response.content!r}")
         return True
 
     def remove_delegate(
@@ -311,7 +318,7 @@ def remove_delegate(
             remove_payload,
         )
         if not response.ok:
-            raise SafeAPIException(f"Cannot remove delegate: {response.content}")
+            raise SafeAPIException(f"Cannot remove delegate: {response.content!r}")
         return True
 
     def post_transaction(self, safe_tx: SafeTx) -> bool:
@@ -337,7 +344,7 @@ def post_transaction(self, safe_tx: SafeTx) -> bool:
             f"/api/v1/safes/{safe_tx.safe_address}/multisig-transactions/", data
         )
         if not response.ok:
-            raise SafeAPIException(f"Error posting transaction: {response.content}")
+            raise SafeAPIException(f"Error posting transaction: {response.content!r}")
         return True
 
     def delete_transaction(self, safe_tx_hash: str, signature: str) -> bool:
@@ -352,7 +359,7 @@ def delete_transaction(self, safe_tx_hash: str, signature: str) -> bool:
             f"/api/v1/multisig-transactions/{safe_tx_hash}/", payload
         )
         if not response.ok:
-            raise SafeAPIException(f"Error deleting transaction: {response.content}")
+            raise SafeAPIException(f"Error deleting transaction: {response.content!r}")
         return True
 
     def post_message(
@@ -379,7 +386,7 @@ def post_message(
             f"/api/v1/safes/{safe_address}/messages/", payload
         )
         if not response.ok:
-            raise SafeAPIException(f"Error posting message: {response.content}")
+            raise SafeAPIException(f"Error posting message: {response.content!r}")
         return True
 
     def get_message(self, safe_message_hash: bytes) -> Message:
@@ -392,7 +399,7 @@ def get_message(self, safe_message_hash: bytes) -> Message:
             f"/api/v1/messages/{HexBytes(safe_message_hash).hex()}/"
         )
         if not response.ok:
-            raise SafeAPIException(f"Cannot get messages: {response.content}")
+            raise SafeAPIException(f"Cannot get messages: {response.content!r}")
         return response.json()
 
     def get_messages(self, safe_address: ChecksumAddress) -> List[Message]:
@@ -403,7 +410,7 @@ def get_messages(self, safe_address: ChecksumAddress) -> List[Message]:
         """
         response = self._get_request(f"/api/v1/safes/{safe_address}/messages/")
         if not response.ok:
-            raise SafeAPIException(f"Cannot get messages: {response.content}")
+            raise SafeAPIException(f"Cannot get messages: {response.content!r}")
         return response.json().get("results", [])
 
     def post_message_signature(
@@ -422,7 +429,7 @@ def post_message_signature(
         )
         if not response.ok:
             raise SafeAPIException(
-                f"Error posting message signature: {response.content}"
+                f"Error posting message signature: {response.content!r}"
             )
         return True
 
@@ -441,5 +448,5 @@ def decode_data(
             payload["to"] = to_address
         response = self._post_request("/api/v1/data-decoder/", payload)
         if not response.ok:
-            raise SafeAPIException(f"Cannot decode tx data: {response.content}")
+            raise SafeAPIException(f"Cannot decode tx data: {response.content!r}")
         return response.json()
diff --git a/safe_eth/safe/multi_send.py b/safe_eth/safe/multi_send.py
index 5d6438082..05ab91c70 100644
--- a/safe_eth/safe/multi_send.py
+++ b/safe_eth/safe/multi_send.py
@@ -3,7 +3,7 @@
 from typing import List, Optional, Union
 
 from eth_account.signers.local import LocalAccount
-from eth_typing import ChecksumAddress
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 from hexbytes import HexBytes
 from web3 import Web3
 
@@ -193,7 +193,7 @@ def __init__(
         self.address = address
         self.ethereum_client = ethereum_client
         self.call_only = call_only
-        addresses = (
+        multi_send_addresses = (
             self.MULTISEND_CALL_ONLY_ADDRESSES
             if call_only
             else self.MULTISEND_ADDRESSES
@@ -205,17 +205,21 @@ def __init__(
             )
         elif ethereum_client:
             # Try to detect MultiSend address if not provided
-            for address in addresses:
-                if ethereum_client.is_contract(address):
-                    self.address = address
+            for multi_send_address in multi_send_addresses:
+                multi_send_address_checksum = ChecksumAddress(
+                    HexAddress(HexStr(multi_send_address))
+                )
+                if ethereum_client.is_contract(multi_send_address_checksum):
+                    self.address = multi_send_address_checksum
                     break
         else:
-            self.address = addresses[0]
+            self.address = ChecksumAddress(HexAddress(HexStr(multi_send_addresses[0])))
 
         if not self.address:
-            raise ValueError(
-                f"Cannot find a MultiSend contract for chainId={self.ethereum_client.get_chain_id()}"
+            chain_id = (
+                self.ethereum_client.get_chain_id() if self.ethereum_client else "N/A"
             )
+            raise ValueError(f"Cannot find a MultiSend contract for chainId={chain_id}")
 
     @property
     def w3(self):
diff --git a/safe_eth/safe/proxy_factory.py b/safe_eth/safe/proxy_factory.py
index 71287bfcb..933f25195 100644
--- a/safe_eth/safe/proxy_factory.py
+++ b/safe_eth/safe/proxy_factory.py
@@ -43,7 +43,7 @@ def __new__(cls, *args, version: str = "1.4.1", **kwargs) -> "ProxyFactory":
             "1.4.1": ProxyFactoryV141,
         }
         instance_class = versions[version]
-        instance = super().__new__(instance_class)
+        instance = super().__new__(instance_class)  # type: ignore[type-abstract]
         return instance
 
     @classmethod
@@ -57,8 +57,8 @@ def deploy_contract(
         :param deployer_account: Ethereum Account
         :return: ``EthereumTxSent`` with the deployed contract address
         """
-        contract_fn = cls.get_contract_fn(cls)
-        contract = contract_fn(ethereum_client.w3)
+        contract_fn = cls.get_contract_fn(cls)  # type: ignore[arg-type]
+        contract = contract_fn(ethereum_client.w3, None)
         constructor_data = contract.constructor().build_transaction(
             get_empty_tx_params()
         )["data"]
@@ -197,7 +197,7 @@ def calculate_proxy_address(
         """
 
         if chain_specific:
-            salt_nonce = fast_keccak(
+            salt_nonce_encoded = fast_keccak(
                 encode_packed(
                     ["bytes32", "uint256", "uint256"],
                     [
@@ -208,7 +208,7 @@ def calculate_proxy_address(
                 )
             )
         else:
-            salt_nonce = fast_keccak(
+            salt_nonce_encoded = fast_keccak(
                 encode_packed(
                     ["bytes32", "uint256"], [fast_keccak(initializer), salt_nonce]
                 )
@@ -219,7 +219,7 @@ def calculate_proxy_address(
             ["bytes", "uint256"], [proxy_creation_code, int(master_copy, 0)]
         )
 
-        return mk_contract_address_2(self.address, salt_nonce, deployment_data)
+        return mk_contract_address_2(self.address, salt_nonce_encoded, deployment_data)
 
     def deploy_proxy_contract_with_nonce(
         self,
@@ -256,26 +256,26 @@ def deploy_proxy_contract_with_nonce(
 
 
 class ProxyFactoryV100(ProxyFactory):
-    def get_contract_fn(self) -> Callable[[Web3, ChecksumAddress], Contract]:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_proxy_factory_V1_0_0_contract
 
 
 class ProxyFactoryV111(ProxyFactory):
-    def get_contract_fn(self) -> Callable[[Web3, ChecksumAddress], Contract]:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_proxy_factory_V1_1_1_contract
 
 
 class ProxyFactoryV130(ProxyFactory):
-    def get_contract_fn(self) -> Callable[[Web3, ChecksumAddress], Contract]:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_proxy_factory_V1_3_0_contract
 
 
 class ProxyFactoryV141(ProxyFactory):
-    def get_contract_fn(self) -> Callable[[Web3, ChecksumAddress], Contract]:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_proxy_factory_V1_4_1_contract
 
     @cache
-    def get_proxy_runtime_code(self) -> Optional[bytes]:
+    def get_proxy_runtime_code(self) -> bytes:
         """
         :return: From v1.4.1 onwards the method is not available
         :raises: NotImplementedError
diff --git a/safe_eth/safe/safe.py b/safe_eth/safe/safe.py
index 5ee3abdbd..ce68eeb30 100644
--- a/safe_eth/safe/safe.py
+++ b/safe_eth/safe/safe.py
@@ -4,19 +4,19 @@
 from abc import ABCMeta, abstractmethod
 from functools import cached_property
 from logging import getLogger
-from typing import Callable, Dict, List, Optional, Union
+from typing import Any, Callable, Dict, List, Optional, Type, Union
 
 import eth_abi
 from eth_abi.exceptions import DecodingError
 from eth_abi.packed import encode_packed
 from eth_account import Account
 from eth_account.signers.local import LocalAccount
-from eth_typing import ChecksumAddress, Hash32, HexStr
+from eth_typing import ChecksumAddress, Hash32, HexAddress, HexStr
 from hexbytes import HexBytes
 from web3 import Web3
 from web3.contract import Contract
 from web3.exceptions import Web3Exception
-from web3.types import BlockIdentifier, TxParams
+from web3.types import BlockIdentifier, TxParams, Wei
 
 from safe_eth.eth import EthereumClient, EthereumTxSent
 from safe_eth.eth.constants import GAS_CALL_DATA_BYTE, NULL_ADDRESS, SENTINEL_ADDRESS
@@ -94,7 +94,7 @@ def __new__(
         if cls is not Safe:
             return super().__new__(cls, *args, **kwargs)
 
-        versions: Dict[str, Safe] = {
+        versions: Dict[str, Type[Safe]] = {
             "0.0.1": SafeV001,
             "1.0.0": SafeV100,
             "1.1.1": SafeV111,
@@ -111,7 +111,7 @@ def __new__(
         except (Web3Exception, ValueError):
             version = None  # Cannot detect the version
 
-        instance_class = versions.get(version, default_version)
+        instance_class = versions.get(version or "", default_version)
         instance = super().__new__(instance_class)
         return instance
 
@@ -142,8 +142,15 @@ def chain_id(self) -> int:
     def simulate_tx_accessor_address(self) -> ChecksumAddress:
         if self._simulate_tx_accessor_address:
             return self._simulate_tx_accessor_address
-        return os.environ.get(
-            "SAFE_SIMULATE_TX_ACCESSOR_ADDRESS", SAFE_SIMULATE_TX_ACCESSOR_ADDRESS
+        return ChecksumAddress(
+            HexAddress(
+                HexStr(
+                    os.environ.get(
+                        "SAFE_SIMULATE_TX_ACCESSOR_ADDRESS",
+                        SAFE_SIMULATE_TX_ACCESSOR_ADDRESS,
+                    )
+                )
+            )
         )
 
     @simulate_tx_accessor_address.setter
@@ -153,7 +160,9 @@ def simulate_tx_accessor_address(self, value: ChecksumAddress):
     def retrieve_version(
         self, block_identifier: Optional[BlockIdentifier] = "latest"
     ) -> str:
-        return self.contract.functions.VERSION().call(block_identifier=block_identifier)
+        return self.contract.functions.VERSION().call(
+            block_identifier=block_identifier or "latest"
+        )
 
     @cached_property
     def domain_separator(self) -> Optional[bytes]:
@@ -180,8 +189,8 @@ def deploy_contract(
         :param deployer_account: Ethereum account
         :return: ``EthereumTxSent`` with the deployed contract address
         """
-        contract_fn = cls.get_contract_fn(cls)
-        safe_contract = contract_fn(ethereum_client.w3)
+        contract_fn = cls.get_contract_fn(cls)  # type: ignore[arg-type]
+        safe_contract = contract_fn(ethereum_client.w3, None)
         constructor_data = safe_contract.constructor().build_transaction(
             get_empty_tx_params()
         )["data"]
@@ -193,9 +202,10 @@ def deploy_contract(
             .functions.VERSION()
             .call()
         )
-        assert deployed_version == cls.get_version(
-            cls
-        ), f"Deployed version {deployed_version} is not matching expected {cls.get_version(cls)} version"
+        safe_version = cls.get_version(cls)  # type: ignore[arg-type]
+        assert (
+            deployed_version == safe_version
+        ), f"Deployed version {deployed_version} is not matching expected {safe_version} version"
 
         logger.info(
             "Deployed and initialized Safe Master Contract version=%s on address %s by %s",
@@ -360,7 +370,7 @@ def parse_revert_data(result: bytes) -> int:
             {
                 "from": safe_address,
                 "gas": 0,  # Don't call estimate
-                "gasPrice": 0,  # Don't get gas price
+                "gasPrice": Wei(0),  # Don't get gas price
             }
         )
 
@@ -371,7 +381,7 @@ def parse_revert_data(result: bytes) -> int:
         }
 
         if gas_limit:
-            tx_params["gas"] = hex(gas_limit)
+            tx_params["gas"] = HexStr(hex(gas_limit))
 
         query = {
             "jsonrpc": "2.0",
@@ -398,7 +408,7 @@ def parse_revert_data(result: bytes) -> int:
                     )
 
         raise CannotEstimateGas(
-            f"Received {response.status_code} - {response.content} from ethereum node"
+            f"Received {response.status_code} - {response.content!r} from ethereum node"
         )
 
     def estimate_tx_gas_with_web3(
@@ -452,7 +462,7 @@ def estimate_tx_gas_by_trying(
                     value,
                     data,
                     operation,
-                    gas_limit=gas_estimated + base_gas + 32000,
+                    gas_limit=gas_estimated + (base_gas or 0) + 32000,
                 )
                 return gas_estimated
             except CannotEstimateGas:
@@ -517,10 +527,12 @@ def get_message_hash(self, message: Union[str, Hash32]) -> Hash32:
         :param message: Message that should be hashed. A ``Hash32`` must be provided for EIP191 or EIP712 messages
         :return: Message hash
         """
-
         if isinstance(message, str):
-            message = message.encode()
-        message_hash = fast_keccak(message)
+            message_to_hash = message.encode()  # Convertir str a bytes
+        else:
+            message_to_hash = message
+
+        message_hash = fast_keccak(message_to_hash)
 
         safe_message_hash = fast_keccak(
             eth_abi.encode(
@@ -613,9 +625,9 @@ def retrieve_all_info(
 
     def retrieve_domain_separator(
         self, block_identifier: Optional[BlockIdentifier] = "latest"
-    ) -> str:
+    ) -> Optional[bytes]:
         return self.contract.functions.domainSeparator().call(
-            block_identifier=block_identifier
+            block_identifier=block_identifier or "latest"
         )
 
     def retrieve_code(self) -> HexBytes:
@@ -670,6 +682,15 @@ def retrieve_modules(
         :param block_identifier:
         :return: List of module addresses
         """
+        if pagination is None:
+            pagination = 50
+
+        if max_modules_to_retrieve is None:
+            max_modules_to_retrieve = 500
+
+        if block_identifier is None:
+            block_identifier = "latest"
+
         if not hasattr(self.contract.functions, "getModulesPaginated"):
             # Custom code for Safes < v1.3.0
             # Safe V1_0_0 can get into an infinite loop if it's not initialized
@@ -709,7 +730,7 @@ def retrieve_is_hash_approved(
     ) -> bool:
         return (
             self.contract.functions.approvedHashes(owner, safe_hash).call(
-                block_identifier=block_identifier
+                block_identifier=block_identifier or "latest"
             )
             == 1
         )
@@ -720,33 +741,35 @@ def retrieve_is_message_signed(
         block_identifier: Optional[BlockIdentifier] = "latest",
     ) -> bool:
         return self.contract.functions.signedMessages(message_hash).call(
-            block_identifier=block_identifier
+            block_identifier=block_identifier or "latest"
         )
 
     def retrieve_is_owner(
         self, owner: str, block_identifier: Optional[BlockIdentifier] = "latest"
     ) -> bool:
         return self.contract.functions.isOwner(owner).call(
-            block_identifier=block_identifier
+            block_identifier=block_identifier or "latest"
         )
 
     def retrieve_nonce(
         self, block_identifier: Optional[BlockIdentifier] = "latest"
     ) -> int:
-        return self.contract.functions.nonce().call(block_identifier=block_identifier)
+        return self.contract.functions.nonce().call(
+            block_identifier=block_identifier or "latest"
+        )
 
     def retrieve_owners(
         self, block_identifier: Optional[BlockIdentifier] = "latest"
     ) -> List[str]:
         return self.contract.functions.getOwners().call(
-            block_identifier=block_identifier
+            block_identifier=block_identifier or "latest"
         )
 
     def retrieve_threshold(
         self, block_identifier: Optional[BlockIdentifier] = "latest"
     ) -> int:
         return self.contract.functions.getThreshold().call(
-            block_identifier=block_identifier
+            block_identifier=block_identifier or "latest"
         )
 
     def build_multisig_tx(
@@ -873,7 +896,7 @@ class SafeV001(Safe):
     def get_version(self):
         return "0.0.1"
 
-    def get_contract_fn(self) -> Callable[[Web3, ChecksumAddress], Contract]:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_safe_V0_0_1_contract
 
     @staticmethod
@@ -901,7 +924,7 @@ def deploy_contract(
             2,  # Threshold. Maximum security
             NULL_ADDRESS,  # Address for optional DELEGATE CALL
             b"",  # Data for optional DELEGATE CALL
-        ).build_transaction({"to": NULL_ADDRESS, "gas": 0, "gasPrice": 0})["data"]
+        ).build_transaction({"to": NULL_ADDRESS, "gas": 0, "gasPrice": Wei(0)})["data"]
 
         ethereum_tx_sent = ethereum_client.deploy_and_initialize_contract(
             deployer_account, constructor_data, HexBytes(initializer_data)
@@ -918,7 +941,7 @@ class SafeV100(Safe):
     def get_version(self):
         return "1.0.0"
 
-    def get_contract_fn(self) -> Contract:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_safe_V1_0_0_contract
 
     @staticmethod
@@ -949,7 +972,7 @@ def deploy_contract(
             NULL_ADDRESS,  # Payment token
             0,  # Payment
             NULL_ADDRESS,  # Refund receiver
-        ).build_transaction({"to": NULL_ADDRESS, "gas": 0, "gasPrice": 0})["data"]
+        ).build_transaction({"to": NULL_ADDRESS, "gas": 0, "gasPrice": Wei(0)})["data"]
 
         ethereum_tx_sent = ethereum_client.deploy_and_initialize_contract(
             deployer_account, constructor_data, HexBytes(initializer_data)
@@ -966,7 +989,7 @@ class SafeV111(Safe):
     def get_version(self):
         return "1.1.1"
 
-    def get_contract_fn(self) -> Contract:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_safe_V1_1_1_contract
 
 
@@ -974,7 +997,7 @@ class SafeV120(Safe):
     def get_version(self):
         return "1.2.0"
 
-    def get_contract_fn(self) -> Contract:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_safe_V1_1_1_contract
 
 
@@ -982,7 +1005,7 @@ class SafeV130(Safe):
     def get_version(self):
         return "1.3.0"
 
-    def get_contract_fn(self) -> Contract:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_safe_V1_3_0_contract
 
 
@@ -990,7 +1013,7 @@ class SafeV141(Safe):
     def get_version(self):
         return "1.4.1"
 
-    def get_contract_fn(self) -> Contract:
+    def get_contract_fn(self) -> Callable[[Web3, Optional[ChecksumAddress]], Contract]:
         return get_safe_V1_4_1_contract
 
     def estimate_tx_gas_with_safe(
@@ -1042,7 +1065,9 @@ def estimate_tx_gas_with_safe(
             return estimate
         except DecodingError as e:
             try:
-                decoded_revert = eth_abi.decode(["string"], accessible_data)
+                decoded_revert: Union[tuple[Any], str] = eth_abi.decode(
+                    ["string"], accessible_data
+                )
             except DecodingError:
                 decoded_revert = "No revert message"
             raise CannotEstimateGas(
diff --git a/safe_eth/safe/safe_create2_tx.py b/safe_eth/safe/safe_create2_tx.py
index 8e2518cb9..314f5cd66 100644
--- a/safe_eth/safe/safe_create2_tx.py
+++ b/safe_eth/safe/safe_create2_tx.py
@@ -127,13 +127,14 @@ def build(
         payment_token = payment_token or NULL_ADDRESS
         assert fast_is_checksum_address(payment_receiver)
         assert fast_is_checksum_address(payment_token)
+        owners_list = list(map(str, owners))
 
         # Get bytes for `setup(address[] calldata _owners, uint256 _threshold, address to, bytes calldata data,
         # address paymentToken, uint256 payment, address payable paymentReceiver)`
         # This initializer will be passed to the ProxyFactory to be called right after proxy is deployed
         # We use `payment=0` as safe has no ether yet and estimation will fail
         safe_setup_data: bytes = self._get_initial_setup_safe_data(
-            owners,
+            owners_list,
             threshold,
             fallback_handler=fallback_handler,
             payment_token=payment_token,
@@ -141,7 +142,7 @@ def build(
         )
 
         calculated_gas: int = self._calculate_gas(
-            owners, safe_setup_data, payment_token
+            owners_list, safe_setup_data, payment_token
         )
         estimated_gas: int = self._estimate_gas(
             safe_setup_data, salt_nonce, payment_token, payment_receiver
@@ -156,7 +157,7 @@ def build(
 
         # Now we have a estimate for `payment` so we get initialization data again
         final_safe_setup_data: bytes = self._get_initial_setup_safe_data(
-            owners,
+            owners_list,
             threshold,
             fallback_handler=fallback_handler,
             payment_token=payment_token,
@@ -171,7 +172,7 @@ def build(
 
         return SafeCreate2Tx(
             salt_nonce,
-            owners,
+            owners_list,
             threshold,
             fallback_handler,
             self.master_copy_address,
diff --git a/safe_eth/safe/safe_creator.py b/safe_eth/safe/safe_creator.py
index 0ea9e92e1..1c2020476 100644
--- a/safe_eth/safe/safe_creator.py
+++ b/safe_eth/safe/safe_creator.py
@@ -90,7 +90,7 @@ def create(
         )
 
         if proxy_factory_address:
-            proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client)
+            proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client)  # type: ignore[abstract]
             return proxy_factory.deploy_proxy_contract_with_nonce(
                 deployer_account, master_copy_address, initializer=HexBytes(initializer)
             )
diff --git a/safe_eth/safe/safe_signature.py b/safe_eth/safe/safe_signature.py
index 12f89adfc..61edadc8e 100644
--- a/safe_eth/safe/safe_signature.py
+++ b/safe_eth/safe/safe_signature.py
@@ -7,7 +7,7 @@
 from eth_abi import encode as encode_abi
 from eth_abi.exceptions import DecodingError
 from eth_account.messages import defunct_hash_message
-from eth_typing import ChecksumAddress
+from eth_typing import BlockIdentifier, ChecksumAddress, HexAddress, HexStr
 from hexbytes import HexBytes
 from web3.exceptions import Web3Exception
 
@@ -177,7 +177,7 @@ def export_signatures(cls, safe_signatures: Sequence["SafeSignature"]) -> HexByt
                 dynamic_offset += len(contract_signature)
             else:
                 signature += safe_signature.export_signature()
-        return signature + dynamic_part
+        return HexBytes(signature + dynamic_part)
 
     def export_signature(self) -> HexBytes:
         """
@@ -318,9 +318,13 @@ def build_for_owner(cls, owner: str, safe_hash: str) -> "SafeSignatureApprovedHa
         return cls(HexBytes(r + s + v), safe_hash)
 
     def is_valid(self, ethereum_client: EthereumClient, safe_address: str) -> bool:
-        safe_contract = get_safe_contract(ethereum_client.w3, safe_address)
+        safe_contract = get_safe_contract(
+            ethereum_client.w3, ChecksumAddress(HexAddress(HexStr(safe_address)))
+        )
         exception: Exception
-        for block_identifier in ("pending", "latest"):
+
+        block_identifiers: List[BlockIdentifier] = ["pending", "latest"]
+        for block_identifier in block_identifiers:
             try:
                 return (
                     safe_contract.functions.approvedHashes(
diff --git a/safe_eth/safe/safe_tx.py b/safe_eth/safe/safe_tx.py
index 1e36cbcec..25fc42f59 100644
--- a/safe_eth/safe/safe_tx.py
+++ b/safe_eth/safe/safe_tx.py
@@ -7,7 +7,7 @@
 from packaging.version import Version
 from web3.contract.contract import ContractFunction
 from web3.exceptions import Web3Exception
-from web3.types import BlockIdentifier, TxParams, Wei
+from web3.types import BlockIdentifier, Nonce, TxParams, Wei
 
 from safe_eth.eth import EthereumClient
 from safe_eth.eth.constants import NULL_ADDRESS
@@ -171,7 +171,7 @@ def eip712_structured_data(self) -> Dict[str, Any]:
             "nonce": self.safe_nonce,
         }
 
-        payload = {
+        payload: Dict[str, Any] = {
             "types": types,
             "primaryType": "SafeTx",
             "domain": {"verifyingContract": self.safe_address},
@@ -301,14 +301,19 @@ def call(
         :param block_identifier:
         :return: `1` if everything ok
         """
-        parameters: Dict[str, Any] = {
+        parameters: TxParams = {
             "from": tx_sender_address if tx_sender_address else self.safe_address
         }
 
         if tx_gas:
             parameters["gas"] = tx_gas
         try:
-            success = self.w3_tx.call(parameters, block_identifier=block_identifier)
+            success = self.w3_tx.call(
+                parameters,
+                block_identifier=block_identifier
+                if block_identifier is not None
+                else "latest",
+            )
 
             if not success:
                 raise InvalidInternalTx(
@@ -376,15 +381,17 @@ def execute(
         else:
             tx_parameters = {
                 "from": sender_account.address,
-                "gasPrice": tx_gas_price or self.w3.eth.gas_price,
+                "gasPrice": Wei(tx_gas_price)
+                if tx_gas_price
+                else self.w3.eth.gas_price,
             }
 
         if tx_gas:
             tx_parameters["gas"] = tx_gas
         if tx_nonce is not None:
-            tx_parameters["nonce"] = tx_nonce
+            tx_parameters["nonce"] = Nonce(tx_nonce)
 
-        self.tx: TxParams = self.w3_tx.build_transaction(tx_parameters)
+        self.tx = self.w3_tx.build_transaction(tx_parameters)
         self.tx["gas"] = Wei(
             tx_gas or (max(self.tx["gas"] + 75000, self.recommended_gas()))
         )
diff --git a/safe_eth/safe/signatures.py b/safe_eth/safe/signatures.py
index 80e0f4f45..0ca042e60 100644
--- a/safe_eth/safe/signatures.py
+++ b/safe_eth/safe/signatures.py
@@ -51,7 +51,7 @@ def signatures_to_bytes(signatures: List[Tuple[int, int, int]]) -> bytes:
     return b"".join([signature_to_bytes(v, r, s) for v, r, s in signatures])
 
 
-def get_signing_address(signed_hash: Union[bytes, str], v: int, r: int, s: int) -> str:
+def get_signing_address(signed_hash: bytes, v: int, r: int, s: int) -> str:
     """
     :return: checksummed ethereum address, for example `0x568c93675A8dEb121700A6FAdDdfE7DFAb66Ae4A`
     :rtype: str or `NULL_ADDRESS` if signature is not valid
diff --git a/safe_eth/util/http.py b/safe_eth/util/http.py
index 59b6ee35f..875923bb7 100644
--- a/safe_eth/util/http.py
+++ b/safe_eth/util/http.py
@@ -1,3 +1,5 @@
+from typing import Union
+
 import requests
 
 
@@ -13,7 +15,7 @@ def prepare_http_session(
     https://docs.python-requests.org/en/latest/api/#requests.adapters.HTTPAdapter
     """
     session = requests.Session()
-    retry_conf = (
+    retry_conf: Union[requests.adapters.Retry, int] = (
         requests.adapters.Retry(
             total=retry_count, backoff_factor=0.3, respect_retry_after_header=False
         )
diff --git a/scripts/cowswap/cow_swap_cli.py b/scripts/cowswap/cow_swap_cli.py
index 3600f914c..b26475ba9 100644
--- a/scripts/cowswap/cow_swap_cli.py
+++ b/scripts/cowswap/cow_swap_cli.py
@@ -1,3 +1,11 @@
+from typing import Union
+
+from eth_typing import HexStr
+
+from safe_eth.eth.clients.cowswap import CowSwapAPI, Order, OrderKind
+from safe_eth.eth.clients.cowswap.cow_swap_api import ErrorResponse
+
+
 def confirm_prompt(question: str) -> bool:
     reply = None
     while reply not in ("y", "n"):
@@ -11,7 +19,6 @@ def confirm_prompt(question: str) -> bool:
     import sys
     import time
 
-    from safe_eth.cowsap import CowSwapAPI, Order, OrderKind
     from safe_eth.eth import EthereumNetwork
     from safe_eth.eth.constants import NULL_ADDRESS
 
@@ -40,7 +47,7 @@ def confirm_prompt(question: str) -> bool:
         from_token, to_token, OrderKind.SELL, amount_wei
     )
 
-    buy_amount = int(buy_amount_response.get("amount", "0"))
+    buy_amount = int(str(buy_amount_response.get("amount", "0")))
     if not buy_amount:
         print("Cannot calculate amount to receive, maybe token is not supported")
         sys.exit(1)
@@ -60,12 +67,14 @@ def confirm_prompt(question: str) -> bool:
         buyTokenBalance="erc20",  # `erc20` or `internal`
     )
 
-    order.feeAmount = cow_swap_api.get_fee(order)
+    fee: Union[int, ErrorResponse] = cow_swap_api.get_fee(order, NULL_ADDRESS)
+    if isinstance(fee, int):
+        order.feeAmount = fee
 
     if confirm_prompt(
         f"Exchanging {amount_wei} {from_token} to {buy_amount} {to_token}?"
     ):
-        result = cow_swap_api.place_order(order, PRIVATE_KEY)
+        result = cow_swap_api.place_order(order, HexStr(PRIVATE_KEY))
         if isinstance(result, dict):
             print(f"Cannot place order {result}")
         else:


Test patch:
diff --git a/requirements-test.txt b/requirements-test.txt
index 743d2fe9e..1698139ec 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,8 +1,10 @@
 -r requirements.txt
 coverage==7.6.1
 faker==28.4.1
+mypy==1.11.2
 pytest==8.3.2
 pytest-django==4.8.0
 pytest-env==1.1.3
 pytest-rerunfailures==14.0
 pytest-sugar==1.0.0
+types-requests==2.32.0.20240712
diff --git a/safe_eth/eth/tests/account_abstraction/test_bundler_client.py b/safe_eth/eth/tests/account_abstraction/test_bundler_client.py
index 7bcfbe3aa..988d2c77f 100644
--- a/safe_eth/eth/tests/account_abstraction/test_bundler_client.py
+++ b/safe_eth/eth/tests/account_abstraction/test_bundler_client.py
@@ -5,6 +5,7 @@
 from django.test import TestCase
 
 import requests
+from eth_typing import HexStr
 
 from ...account_abstraction import (
     BundlerClient,
@@ -37,7 +38,7 @@ def test_get_user_operation_by_hash(self, mock_session: MagicMock):
         mock_session.return_value.json = MagicMock(return_value=user_operation_mock)
         self.bundler.get_user_operation_by_hash.cache_clear()
         expected_user_operation = UserOperation.from_bundler_response(
-            user_operation_hash, user_operation_mock["result"]
+            HexStr(user_operation_hash), user_operation_mock["result"]
         )
         self.assertEqual(
             self.bundler.get_user_operation_by_hash(user_operation_hash),
@@ -127,7 +128,7 @@ def test_get_user_operation_and_receipt(self, mock_session: MagicMock):
         )
         self.bundler.get_user_operation_and_receipt.cache_clear()
         expected_user_operation = UserOperation.from_bundler_response(
-            user_operation_hash, user_operation_mock["result"]
+            HexStr(user_operation_hash), user_operation_mock["result"]
         )
         expected_user_operation_receipt = UserOperationReceipt.from_bundler_response(
             user_operation_receipt_mock["result"]
diff --git a/safe_eth/eth/tests/clients/test_sourcify_client.py b/safe_eth/eth/tests/clients/test_sourcify_client.py
index 9dc92c928..ed2c565e3 100644
--- a/safe_eth/eth/tests/clients/test_sourcify_client.py
+++ b/safe_eth/eth/tests/clients/test_sourcify_client.py
@@ -40,6 +40,7 @@ def test_get_contract_metadata(self, is_chain_supported_mock: MagicMock):
             )
         except IOError:
             self.skipTest("Cannot connect to Sourcify")
+        assert safe_contract_metadata_mainnet is not None
         self.assertEqual(safe_contract_metadata_mainnet.name, "Safe")
         self.assertIsInstance(safe_contract_metadata_mainnet.abi, List)
         self.assertTrue(safe_contract_metadata_mainnet.abi)
@@ -55,6 +56,7 @@ def test_get_contract_metadata(self, is_chain_supported_mock: MagicMock):
         token_contract_metadata_mainnet = sourcify_client_mainnet.get_contract_metadata(
             partial_match_contract_address
         )
+        assert token_contract_metadata_mainnet is not None
         self.assertEqual(token_contract_metadata_mainnet.name, "LiquidGasToken")
         self.assertIsInstance(token_contract_metadata_mainnet.abi, List)
         self.assertTrue(token_contract_metadata_mainnet.abi)
diff --git a/safe_eth/eth/tests/ethereum_test_case.py b/safe_eth/eth/tests/ethereum_test_case.py
index d55bb4999..a702a683a 100644
--- a/safe_eth/eth/tests/ethereum_test_case.py
+++ b/safe_eth/eth/tests/ethereum_test_case.py
@@ -3,9 +3,10 @@
 
 from eth_account import Account
 from eth_account.signers.local import LocalAccount
+from eth_typing import ChecksumAddress, HexAddress, HexStr
 from web3 import Web3
 from web3.contract import Contract
-from web3.types import TxParams
+from web3.types import TxParams, Wei
 
 from ..ethereum_client import EthereumClient, get_auto_ethereum_client
 from ..multicall import Multicall
@@ -66,7 +67,9 @@ def send_tx(self, tx: TxParams, account: LocalAccount) -> bytes:
         return send_tx(self.w3, tx, account)
 
     def send_ether(self, to: str, value: int) -> bytes:
-        return send_tx(self.w3, {"to": to, "value": value}, self.ethereum_test_account)
+        return send_tx(
+            self.w3, {"to": to, "value": Wei(value)}, self.ethereum_test_account
+        )
 
     def create_and_fund_account(
         self, initial_ether: float = 0, initial_wei: int = 0
@@ -76,7 +79,9 @@ def create_and_fund_account(
             self.send_tx(
                 {
                     "to": account.address,
-                    "value": self.w3.to_wei(initial_ether, "ether") + initial_wei,
+                    "value": Wei(
+                        self.w3.to_wei(initial_ether, "ether") + Wei(initial_wei)
+                    ),
                 },
                 self.ethereum_test_account,
             )
@@ -95,12 +100,17 @@ def deploy_erc20(
             self.ethereum_test_account,
             name,
             symbol,
-            owner,
+            ChecksumAddress(HexAddress(HexStr(owner))),
             amount,
             decimals=decimals,
         )
 
     def deploy_example_erc20(self, amount: int, owner: str) -> Contract:
         return deploy_erc20(
-            self.w3, self.ethereum_test_account, "Uxio", "UXI", owner, amount
+            self.w3,
+            self.ethereum_test_account,
+            "Uxio",
+            "UXI",
+            ChecksumAddress(HexAddress(HexStr(owner))),
+            amount,
         )
diff --git a/safe_eth/eth/tests/mocks/mock_bundler.py b/safe_eth/eth/tests/mocks/mock_bundler.py
index c9100502f..75b8fbe8e 100644
--- a/safe_eth/eth/tests/mocks/mock_bundler.py
+++ b/safe_eth/eth/tests/mocks/mock_bundler.py
@@ -1,9 +1,11 @@
+from typing import Any, Dict
+
 from eth_typing import ChecksumAddress, HexAddress, HexStr
 from hexbytes import HexBytes
 
 # Module address and chainId used for the following mocks
 safe_4337_address = ChecksumAddress(
-    HexStr(HexAddress("0xB0B5c0578Aa134b0496a6C0e51A7aae47C522861"))
+    HexAddress(HexStr("0xB0B5c0578Aa134b0496a6C0e51A7aae47C522861"))
 )
 safe_4337_user_operation_hash_mock = HexBytes(
     "0x39b3e2171c04539d9b3f848d04364dfaa42cc0b412ff65ce2a85c566cf8bf281"
@@ -17,7 +19,7 @@
 )
 safe_4337_chain_id_mock = 11155111
 
-user_operation_mock = {
+user_operation_mock: Dict[str, Any] = {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
@@ -41,7 +43,7 @@
     },
 }
 
-user_operation_receipt_mock = {
+user_operation_receipt_mock: Dict[str, Any] = {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
@@ -384,7 +386,7 @@
     },
 }
 
-supported_entrypoint_mock = {
+supported_entrypoint_mock: Dict[str, Any] = {
     "jsonrpc": "2.0",
     "id": 1,
     "result": [
diff --git a/safe_eth/eth/tests/oracles/test_cowswap.py b/safe_eth/eth/tests/oracles/test_cowswap.py
index 8d9e542be..75e7e9e39 100644
--- a/safe_eth/eth/tests/oracles/test_cowswap.py
+++ b/safe_eth/eth/tests/oracles/test_cowswap.py
@@ -18,7 +18,7 @@
 from ..utils import just_test_if_mainnet_node
 
 
-@pytest.skip("Having issues often", allow_module_level=True)
+@pytest.mark.skip("Having issues often")
 class TestCowswapOracle(EthereumTestCaseMixin, TestCase):
     def test_get_price(self):
         mainnet_node = just_test_if_mainnet_node()
diff --git a/safe_eth/eth/tests/test_ethereum_client.py b/safe_eth/eth/tests/test_ethereum_client.py
index 1a2755212..1dc9628db 100644
--- a/safe_eth/eth/tests/test_ethereum_client.py
+++ b/safe_eth/eth/tests/test_ethereum_client.py
@@ -7,6 +7,7 @@
 import pytest
 import requests
 from eth_account import Account
+from eth_typing import URI, HexStr
 from hexbytes import HexBytes
 from web3.eth import Eth
 from web3.types import TxParams
@@ -575,35 +576,40 @@ def test_filter_out_errored_traces(self):
         TracingManager, "trace_transaction", return_value=internal_txs_errored
     )
     def test_get_previous_trace(self, trace_transaction_mock: MagicMock):
-        self.assertEqual(
-            self.ethereum_client.tracing.get_previous_trace("0x12", [0, 0])[
-                "traceAddress"
-            ],
-            [0],
-        )
-        self.assertEqual(
-            self.ethereum_client.tracing.get_previous_trace(
-                "0x12", [0, 0], number_traces=2
-            )["traceAddress"],
-            [],
+        trace_result = self.ethereum_client.tracing.get_previous_trace(
+            HexStr("0x12"), [0, 0]
         )
-        self.assertEqual(
-            self.ethereum_client.tracing.get_previous_trace(
-                "0x12", [0, 0], skip_delegate_calls=True
-            )["traceAddress"],
-            [],
+        assert trace_result is not None
+        assert trace_result.get("traceAddress") is not None
+        self.assertEqual(trace_result.get("traceAddress"), [0])
+
+        trace_result_2_traces = self.ethereum_client.tracing.get_previous_trace(
+            HexStr("0x12"), [0, 0], number_traces=2
         )
-        self.assertIsNone(
+        assert trace_result_2_traces is not None
+        assert trace_result_2_traces.get("traceAddress") is not None
+        self.assertEqual(trace_result_2_traces.get("traceAddress"), [])
+
+        trace_result_skip_delegate_calls = (
             self.ethereum_client.tracing.get_previous_trace(
-                "0x12", [0, 0], number_traces=3
+                HexStr("0x12"), [0, 0], skip_delegate_calls=True
             )
         )
-        self.assertEqual(
-            self.ethereum_client.tracing.get_previous_trace(
-                "0x12", [0, 0, 0], skip_delegate_calls=True
-            )["traceAddress"],
-            [0, 0],
+        assert trace_result_skip_delegate_calls is not None
+        assert trace_result_skip_delegate_calls.get("traceAddress") is not None
+        self.assertEqual(trace_result_skip_delegate_calls.get("traceAddress"), [])
+
+        trace_result_3_traces = self.ethereum_client.tracing.get_previous_trace(
+            HexStr("0x12"), [0, 0], number_traces=3
         )
+        self.assertIsNone(trace_result_3_traces)
+
+        trace_result_00_trace_address = self.ethereum_client.tracing.get_previous_trace(
+            HexStr("0x12"), [0, 0, 0], skip_delegate_calls=True
+        )
+        assert trace_result_00_trace_address is not None
+        assert trace_result_00_trace_address.get("traceAddress") is not None
+        self.assertEqual(trace_result_00_trace_address.get("traceAddress"), [0, 0])
 
     @mock.patch.object(
         TracingManager, "trace_transaction", return_value=creation_internal_txs
@@ -613,13 +619,15 @@ def trace_addresses(traces: Sequence[Dict[str, Any]]) -> List[List[int]]:
             return [trace["traceAddress"] for trace in traces]
 
         self.assertEqual(
-            trace_addresses(self.ethereum_client.tracing.get_next_traces("0x12", [])),
+            trace_addresses(
+                self.ethereum_client.tracing.get_next_traces(HexStr("0x12"), [])
+            ),
             [[0], [1]],
         )
         self.assertEqual(
             trace_addresses(
                 self.ethereum_client.tracing.get_next_traces(
-                    "0x12", [], remove_delegate_calls=True
+                    HexStr("0x12"), [], remove_delegate_calls=True
                 )
             ),
             [[1]],
@@ -627,7 +635,7 @@ def trace_addresses(traces: Sequence[Dict[str, Any]]) -> List[List[int]]:
         self.assertEqual(
             trace_addresses(
                 self.ethereum_client.tracing.get_next_traces(
-                    "0x12", [], remove_calls=True
+                    HexStr("0x12"), [], remove_calls=True
                 )
             ),
             [[0]],
@@ -635,14 +643,18 @@ def trace_addresses(traces: Sequence[Dict[str, Any]]) -> List[List[int]]:
         self.assertEqual(
             trace_addresses(
                 self.ethereum_client.tracing.get_next_traces(
-                    "0x12", [], remove_delegate_calls=True, remove_calls=True
+                    HexStr("0x12"), [], remove_delegate_calls=True, remove_calls=True
                 )
             ),
             [],
         )
-        self.assertEqual(self.ethereum_client.tracing.get_next_traces("0x12", [0]), [])
         self.assertEqual(
-            trace_addresses(self.ethereum_client.tracing.get_next_traces("0x12", [1])),
+            self.ethereum_client.tracing.get_next_traces(HexStr("0x12"), [0]), []
+        )
+        self.assertEqual(
+            trace_addresses(
+                self.ethereum_client.tracing.get_next_traces(HexStr("0x12"), [1])
+            ),
             [[1, 0]],
         )
 
@@ -1213,7 +1225,7 @@ class TestEthereumClientWithMainnetNode(EthereumTestCaseMixin, TestCase):
     def setUpClass(cls) -> None:
         super().setUpClass()
         mainnet_node = just_test_if_mainnet_node()
-        cls.ethereum_client = EthereumClient(mainnet_node)
+        cls.ethereum_client = EthereumClient(URI(mainnet_node))
 
     def test_is_eip1559_supported(self):
         self.assertTrue(self.ethereum_client.is_eip1559_supported())
diff --git a/safe_eth/eth/tests/test_multicall.py b/safe_eth/eth/tests/test_multicall.py
index 09f5eb151..a4179b1b0 100644
--- a/safe_eth/eth/tests/test_multicall.py
+++ b/safe_eth/eth/tests/test_multicall.py
@@ -3,6 +3,8 @@
 
 from django.test import TestCase
 
+from eth_typing import URI, ChecksumAddress, HexAddress, HexStr
+
 from .. import EthereumClient, EthereumNetwork, EthereumNetworkNotSupported
 from ..constants import NULL_ADDRESS
 from ..contracts import get_erc20_contract
@@ -79,9 +81,12 @@ class TestMulticallNode(EthereumTestCaseMixin, TestCase):
     def setUpClass(cls) -> None:
         super().setUpClass()
         mainnet_node = just_test_if_mainnet_node()
-        cls.ethereum_client = EthereumClient(mainnet_node)
+        cls.ethereum_client = EthereumClient(URI(mainnet_node))
         cls.gno_contract = get_erc20_contract(
-            cls.ethereum_client.w3, "0x6810e776880C02933D47DB1b9fc05908e5386b96"
+            cls.ethereum_client.w3,
+            ChecksumAddress(
+                HexAddress(HexStr("0x6810e776880C02933D47DB1b9fc05908e5386b96"))
+            ),
         )
         cls.not_existing_contract = get_erc20_contract(
             cls.ethereum_client.w3, NULL_ADDRESS
diff --git a/safe_eth/eth/tests/utils.py b/safe_eth/eth/tests/utils.py
index d462d27d2..dadc20843 100644
--- a/safe_eth/eth/tests/utils.py
+++ b/safe_eth/eth/tests/utils.py
@@ -48,7 +48,7 @@ def just_test_if_node_available(node_url_variable_name: str) -> str:
             )
             if not response.ok:
                 pytest.fail(
-                    f"Problem connecting to node {node_url}: {response.status_code} - {response.content}"
+                    f"Problem connecting to node {node_url}: {response.status_code} - {response.content!r}"
                 )
         except IOError:
             pytest.fail(f"Problem connecting to {node_url}")
@@ -150,10 +150,12 @@ def bytes_to_str(o: Any) -> Any:
         o = dict(o)  # Remove AttributeDict
         for k in o.keys():
             o[k] = bytes_to_str(o[k])
-    elif isinstance(o, (list, tuple)):
+    elif isinstance(o, list):
         o = deepcopy(o)
         for i, v in enumerate(o):
             o[i] = bytes_to_str(o[i])
+    elif isinstance(o, tuple):
+        o = tuple(bytes_to_str(v) for v in o)
     elif isinstance(o, set):
         o = {bytes_to_str(element) for element in o}
     return o
diff --git a/safe_eth/safe/tests/safe_test_case.py b/safe_eth/safe/tests/safe_test_case.py
index 686c74838..beb0f7139 100644
--- a/safe_eth/safe/tests/safe_test_case.py
+++ b/safe_eth/safe/tests/safe_test_case.py
@@ -1,6 +1,7 @@
 import logging
 import os
 from typing import Dict, List, Optional
+from unittest import TestCase
 
 from eth_account import Account
 from eth_typing import ChecksumAddress
@@ -33,7 +34,7 @@
 logger = logging.getLogger(__name__)
 
 
-class SafeTestCaseMixin(EthereumTestCaseMixin):
+class SafeTestCaseMixin(EthereumTestCaseMixin, TestCase):
     compatibility_fallback_handler: Contract
     multi_send: MultiSend
     multi_send_contract: Contract
@@ -202,11 +203,15 @@ def deploy_safe_singleton_factory(cls) -> bool:
         )
         send_tx(
             cls.w3,
-            {"to": SAFE_SINGLETON_FACTORY_DEPLOYER_ADDRESS, "value": 10000000000000000},
+            {
+                "to": SAFE_SINGLETON_FACTORY_DEPLOYER_ADDRESS,
+                "value": Wei(10000000000000000),
+            },
             cls.ethereum_test_account,
         )
         tx_hash = cls.ethereum_client.send_raw_transaction(raw_tx)
         tx_receipt = cls.ethereum_client.get_transaction_receipt(tx_hash, timeout=30)
+        assert tx_receipt is not None
         assert tx_receipt["status"] == 1
 
         # Clear cached empty singleton factory
@@ -241,7 +246,8 @@ def _deploy_test_safe(
             master_copy_address,
             initializer=initializer,
         )
-        safe = Safe(
+        assert ethereum_tx_sent.contract_address is not None
+        safe = Safe(  # type: ignore[abstract]
             ethereum_tx_sent.contract_address,
             self.ethereum_client,
             simulate_tx_accessor_address=self.simulate_tx_accessor_V1_4_1.address,
@@ -304,7 +310,9 @@ def _deploy_new_test_safe(
         )
 
         safe = self._deploy_test_safe(
-            initializer, master_copy_address, initial_funding_wei=initial_funding_wei
+            initializer,
+            master_copy_address,
+            initial_funding_wei=Wei(initial_funding_wei),
         )
 
         self.assertEqual(safe.retrieve_version(), master_copy_version)
@@ -414,7 +422,7 @@ def deploy_test_safe_v1_0_0(
         safe = self._deploy_test_safe(
             initializer,
             self.safe_contract_V1_0_0.address,
-            initial_funding_wei=initial_funding_wei,
+            initial_funding_wei=Wei(initial_funding_wei),
         )
 
         self.assertEqual(safe.retrieve_version(), "1.0.0")
@@ -423,7 +431,7 @@ def deploy_test_safe_v1_0_0(
 
         return safe
 
-    def deploy_example_guard(self) -> ChecksumAddress:
+    def deploy_example_guard(self) -> Optional[ChecksumAddress]:
         """
         :return: An example DebugTransactionGuard (from safe contracts v1.4.1) supporting IERC165
         """
@@ -437,7 +445,7 @@ def deploy_example_guard(self) -> ChecksumAddress:
         assert tx_receipt["status"] == 1, "Problem deploying example guard"
         return tx_receipt["contractAddress"]
 
-    def deploy_sign_message_lib(self) -> ChecksumAddress:
+    def deploy_sign_message_lib(self) -> Optional[ChecksumAddress]:
         """
         Deploy sign message lib