From 49ac4f41a976a11cd4c5ca3c9a080ecf9675822b Mon Sep 17 00:00:00 2001 From: moisses89 <7888669+moisses89@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:24:09 +0100 Subject: [PATCH 1/6] Add SafeApiServiceTx class --- gnosis/safe/api/__init__.py | 3 ++- .../safe/api/transaction_service_api/SafeApiServiceTx.py | 9 +++++++++ gnosis/safe/api/transaction_service_api/__init__.py | 7 +++++++ .../transaction_service_api.py | 7 ++++--- gnosis/safe/tests/api/test_transaction_service_api.py | 3 +-- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py create mode 100644 gnosis/safe/api/transaction_service_api/__init__.py rename gnosis/safe/api/{ => transaction_service_api}/transaction_service_api.py (98%) diff --git a/gnosis/safe/api/__init__.py b/gnosis/safe/api/__init__.py index 82e5967bf..78f607819 100644 --- a/gnosis/safe/api/__init__.py +++ b/gnosis/safe/api/__init__.py @@ -1,7 +1,8 @@ # flake8: noqa F401 +from gnosis.safe.api.transaction_service_api import TransactionServiceApi + from .base_api import SafeAPIException from .relay_service_api import RelayEstimation, RelaySentTransaction, RelayServiceApi -from .transaction_service_api import TransactionServiceApi __all__ = [ "SafeAPIException", diff --git a/gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py b/gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py new file mode 100644 index 000000000..0dcaa3f57 --- /dev/null +++ b/gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py @@ -0,0 +1,9 @@ +from eth_typing import ChecksumAddress + +from gnosis.safe import SafeTx + + +class SafeApiServiceTx(SafeTx): + def __init__(self, proposer: ChecksumAddress, *args, **kwargs): + super().__init__(*args, **kwargs) + self.proposer = proposer diff --git a/gnosis/safe/api/transaction_service_api/__init__.py b/gnosis/safe/api/transaction_service_api/__init__.py new file mode 100644 index 000000000..7518a27f9 --- /dev/null +++ b/gnosis/safe/api/transaction_service_api/__init__.py @@ -0,0 +1,7 @@ +from gnosis.safe.api.transaction_service_api.transaction_service_api import ( + TransactionServiceApi, +) + +__all__ = [ + "TransactionServiceApi", +] diff --git a/gnosis/safe/api/transaction_service_api.py b/gnosis/safe/api/transaction_service_api/transaction_service_api.py similarity index 98% rename from gnosis/safe/api/transaction_service_api.py rename to gnosis/safe/api/transaction_service_api/transaction_service_api.py index ba5caedc9..ed4ee870c 100644 --- a/gnosis/safe/api/transaction_service_api.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_api.py @@ -9,8 +9,8 @@ from gnosis.eth import EthereumNetwork from gnosis.safe import SafeTx - -from .base_api import SafeAPIException, SafeBaseAPI +from gnosis.safe.api.base_api import SafeAPIException, SafeBaseAPI +from gnosis.safe.api.transaction_service_api.SafeApiServiceTx import SafeApiServiceTx logger = logging.getLogger(__name__) @@ -133,7 +133,8 @@ def get_safe_transaction( logger.warning( "EthereumClient should be defined to get a executable SafeTx" ) - safe_tx = SafeTx( + safe_tx = SafeApiServiceTx( + result["proposer"], self.ethereum_client, result["safe"], result["to"], diff --git a/gnosis/safe/tests/api/test_transaction_service_api.py b/gnosis/safe/tests/api/test_transaction_service_api.py index 7eed04be8..abe487f20 100644 --- a/gnosis/safe/tests/api/test_transaction_service_api.py +++ b/gnosis/safe/tests/api/test_transaction_service_api.py @@ -4,8 +4,7 @@ from gnosis.eth import EthereumClient, EthereumNetwork, EthereumNetworkNotSupported from gnosis.eth.tests.ethereum_test_case import EthereumTestCaseMixin - -from ...api.transaction_service_api import TransactionServiceApi +from gnosis.safe.api.transaction_service_api import TransactionServiceApi class TestTransactionServiceAPI(EthereumTestCaseMixin, TestCase): From 77405f80349bfc9f3e676087edde5ffb977d8899 Mon Sep 17 00:00:00 2001 From: moisses89 <7888669+moisses89@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:20:16 +0100 Subject: [PATCH 2/6] Add remove transaction request --- gnosis/safe/api/message_api_helper.py | 43 ++++++++++++++++++++++ gnosis/safe/api/transaction_service_api.py | 21 +++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 gnosis/safe/api/message_api_helper.py diff --git a/gnosis/safe/api/message_api_helper.py b/gnosis/safe/api/message_api_helper.py new file mode 100644 index 000000000..e9b4574d3 --- /dev/null +++ b/gnosis/safe/api/message_api_helper.py @@ -0,0 +1,43 @@ +import time + +from eth_typing import ChecksumAddress + + +def get_totp(): + return int(time.time()) // 3600 + + +def generate_delegate_message(cls, delegate_address: ChecksumAddress) -> str: + totp = get_totp() + return delegate_address + str(totp) + + +def generate_remove_transaction_message( + safe_address: ChecksumAddress, safe_tx_hash: bytes, chain_id: int +): + remove_transaction_message = { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"}, + ], + "DeleteRequest": [ + {"name": "safeTxHash", "type": "bytes32"}, + {"name": "totp", "type": "uint256"}, + ], + }, + "primaryType": "DeleteRequest", + "domain": { + "name": "Safe Transaction Service", + "version": "1.0", + "chainId": chain_id, + "verifyingContract": safe_address, + }, + "message": { + "safeTxHash": safe_tx_hash, + "totp": get_totp(), + }, + } + return remove_transaction_message diff --git a/gnosis/safe/api/transaction_service_api.py b/gnosis/safe/api/transaction_service_api.py index ba5caedc9..0fe86f9ec 100644 --- a/gnosis/safe/api/transaction_service_api.py +++ b/gnosis/safe/api/transaction_service_api.py @@ -1,5 +1,4 @@ import logging -import time from typing import Any, Dict, List, Optional, Tuple, Union from eth_account.signers.local import LocalAccount @@ -11,6 +10,7 @@ from gnosis.safe import SafeTx from .base_api import SafeAPIException, SafeBaseAPI +from .message_api_helper import generate_delegate_message logger = logging.getLogger(__name__) @@ -36,9 +36,7 @@ class TransactionServiceApi(SafeBaseAPI): @classmethod def create_delegate_message_hash(cls, delegate_address: ChecksumAddress) -> str: - totp = int(time.time()) // 3600 - hash_to_sign = Web3.keccak(text=delegate_address + str(totp)) - return hash_to_sign + return Web3.keccak(text=generate_delegate_message(delegate_address)) @classmethod def data_decoded_to_text(cls, data_decoded: Dict[str, Any]) -> Optional[str]: @@ -276,6 +274,21 @@ def post_transaction(self, safe_tx: SafeTx) -> bool: raise SafeAPIException(f"Error posting transaction: {response.content}") return True + def delete_transaction(self, safe_tx_hash: str, signature: str) -> bool: + """ + + :param safe_tx_hash: hash of eip712 see in message_api_helper.py generate_remove_transaction_message function + :param signature: signature of safe_tx_hash by transaction proposer + :return: + """ + payload = {"safeTxHash": safe_tx_hash, "signature": signature} + response = self._delete_request( + f"/api/v1/multisig-transactions/{safe_tx_hash}/", payload + ) + if not response.ok: + raise SafeAPIException(f"Error deleting transaction: {response.content}") + return True + def post_message( self, safe_address: ChecksumAddress, From f40edec53b80fc390c71901b120c9cabbf10c210 Mon Sep 17 00:00:00 2001 From: moisses89 <7888669+moisses89@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:53:34 +0100 Subject: [PATCH 3/6] Refactor class names --- .../transaction_service_api.py | 12 +++++++----- ...api_helper.py => transaction_service_messages.py} | 4 ++-- ...SafeApiServiceTx.py => transaction_service_tx.py} | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) rename gnosis/safe/api/transaction_service_api/{message_api_helper.py => transaction_service_messages.py} (90%) rename gnosis/safe/api/transaction_service_api/{SafeApiServiceTx.py => transaction_service_tx.py} (85%) diff --git a/gnosis/safe/api/transaction_service_api/transaction_service_api.py b/gnosis/safe/api/transaction_service_api/transaction_service_api.py index 2425477bd..9de615339 100644 --- a/gnosis/safe/api/transaction_service_api/transaction_service_api.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_api.py @@ -8,10 +8,12 @@ from gnosis.eth import EthereumNetwork from gnosis.safe import SafeTx -from gnosis.safe.api.transaction_service_api.SafeApiServiceTx import SafeApiServiceTx +from gnosis.safe.api.transaction_service_api.transaction_service_tx import ( + TransactionServiceTx, +) from ..base_api import SafeAPIException, SafeBaseAPI -from .message_api_helper import generate_delegate_message +from .transaction_service_messages import get_delegate_message logger = logging.getLogger(__name__) @@ -37,7 +39,7 @@ class TransactionServiceApi(SafeBaseAPI): @classmethod def create_delegate_message_hash(cls, delegate_address: ChecksumAddress) -> str: - return Web3.keccak(text=generate_delegate_message(delegate_address)) + return Web3.keccak(text=get_delegate_message(delegate_address)) @classmethod def data_decoded_to_text(cls, data_decoded: Dict[str, Any]) -> Optional[str]: @@ -132,7 +134,7 @@ def get_safe_transaction( logger.warning( "EthereumClient should be defined to get a executable SafeTx" ) - safe_tx = SafeApiServiceTx( + safe_tx = TransactionServiceTx( result["proposer"], self.ethereum_client, result["safe"], @@ -279,7 +281,7 @@ def post_transaction(self, safe_tx: SafeTx) -> bool: def delete_transaction(self, safe_tx_hash: str, signature: str) -> bool: """ - :param safe_tx_hash: hash of eip712 see in message_api_helper.py generate_remove_transaction_message function + :param safe_tx_hash: hash of eip712 see in transaction_service_messages.py generate_remove_transaction_message function :param signature: signature of safe_tx_hash by transaction proposer :return: """ diff --git a/gnosis/safe/api/transaction_service_api/message_api_helper.py b/gnosis/safe/api/transaction_service_api/transaction_service_messages.py similarity index 90% rename from gnosis/safe/api/transaction_service_api/message_api_helper.py rename to gnosis/safe/api/transaction_service_api/transaction_service_messages.py index e9b4574d3..529675660 100644 --- a/gnosis/safe/api/transaction_service_api/message_api_helper.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_messages.py @@ -7,12 +7,12 @@ def get_totp(): return int(time.time()) // 3600 -def generate_delegate_message(cls, delegate_address: ChecksumAddress) -> str: +def get_delegate_message(cls, delegate_address: ChecksumAddress) -> str: totp = get_totp() return delegate_address + str(totp) -def generate_remove_transaction_message( +def get_remove_transaction_message( safe_address: ChecksumAddress, safe_tx_hash: bytes, chain_id: int ): remove_transaction_message = { diff --git a/gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py b/gnosis/safe/api/transaction_service_api/transaction_service_tx.py similarity index 85% rename from gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py rename to gnosis/safe/api/transaction_service_api/transaction_service_tx.py index 0dcaa3f57..c2320b32e 100644 --- a/gnosis/safe/api/transaction_service_api/SafeApiServiceTx.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_tx.py @@ -3,7 +3,7 @@ from gnosis.safe import SafeTx -class SafeApiServiceTx(SafeTx): +class TransactionServiceTx(SafeTx): def __init__(self, proposer: ChecksumAddress, *args, **kwargs): super().__init__(*args, **kwargs) self.proposer = proposer From a4d2a28166fbc02745fc9dc01b987ea228db4516 Mon Sep 17 00:00:00 2001 From: moisses89 <7888669+moisses89@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:53:34 +0100 Subject: [PATCH 4/6] Refactor class names --- .../safe/api/transaction_service_api/transaction_service_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnosis/safe/api/transaction_service_api/transaction_service_api.py b/gnosis/safe/api/transaction_service_api/transaction_service_api.py index 9de615339..8a07d6215 100644 --- a/gnosis/safe/api/transaction_service_api/transaction_service_api.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_api.py @@ -116,7 +116,7 @@ def get_balances(self, safe_address: str) -> List[Dict[str, Any]]: def get_safe_transaction( self, safe_tx_hash: Union[bytes, HexStr] - ) -> Tuple[SafeTx, Optional[HexBytes]]: + ) -> Tuple[TransactionServiceTx, Optional[HexBytes]]: """ :param safe_tx_hash: :return: SafeTx and `tx-hash` if transaction was executed From 8e1b41c2abce6eb17bffaa413e0c519eafe817a9 Mon Sep 17 00:00:00 2001 From: moisses89 <7888669+moisses89@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:42:01 +0100 Subject: [PATCH 5/6] Add documentation --- gnosis/safe/api/__init__.py | 2 +- .../api/transaction_service_api/__init__.py | 4 +--- .../transaction_service_api.py | 4 +--- .../transaction_service_messages.py | 24 +++++++++++++++++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/gnosis/safe/api/__init__.py b/gnosis/safe/api/__init__.py index 78f607819..a4f1eb16d 100644 --- a/gnosis/safe/api/__init__.py +++ b/gnosis/safe/api/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa F401 -from gnosis.safe.api.transaction_service_api import TransactionServiceApi +from transaction_service_api import TransactionServiceApi from .base_api import SafeAPIException from .relay_service_api import RelayEstimation, RelaySentTransaction, RelayServiceApi diff --git a/gnosis/safe/api/transaction_service_api/__init__.py b/gnosis/safe/api/transaction_service_api/__init__.py index 7518a27f9..1b2f5e4a4 100644 --- a/gnosis/safe/api/transaction_service_api/__init__.py +++ b/gnosis/safe/api/transaction_service_api/__init__.py @@ -1,6 +1,4 @@ -from gnosis.safe.api.transaction_service_api.transaction_service_api import ( - TransactionServiceApi, -) +from transaction_service_api import TransactionServiceApi __all__ = [ "TransactionServiceApi", diff --git a/gnosis/safe/api/transaction_service_api/transaction_service_api.py b/gnosis/safe/api/transaction_service_api/transaction_service_api.py index 8a07d6215..9d07fe5c5 100644 --- a/gnosis/safe/api/transaction_service_api/transaction_service_api.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_api.py @@ -4,13 +4,11 @@ from eth_account.signers.local import LocalAccount from eth_typing import ChecksumAddress, HexStr from hexbytes import HexBytes +from transaction_service_tx import TransactionServiceTx from web3 import Web3 from gnosis.eth import EthereumNetwork from gnosis.safe import SafeTx -from gnosis.safe.api.transaction_service_api.transaction_service_tx import ( - TransactionServiceTx, -) from ..base_api import SafeAPIException, SafeBaseAPI from .transaction_service_messages import get_delegate_message diff --git a/gnosis/safe/api/transaction_service_api/transaction_service_messages.py b/gnosis/safe/api/transaction_service_api/transaction_service_messages.py index 529675660..d7682045f 100644 --- a/gnosis/safe/api/transaction_service_api/transaction_service_messages.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_messages.py @@ -1,20 +1,40 @@ import time +from typing import Dict from eth_typing import ChecksumAddress -def get_totp(): +def get_totp() -> int: + """ + + :return: time-based one-time password + """ return int(time.time()) // 3600 def get_delegate_message(cls, delegate_address: ChecksumAddress) -> str: + """ + Retrieves the required message for creating or removing a delegate on Safe Transaction Service. + + :param cls: + :param delegate_address: + :return: generated str message + """ totp = get_totp() return delegate_address + str(totp) def get_remove_transaction_message( safe_address: ChecksumAddress, safe_tx_hash: bytes, chain_id: int -): +) -> Dict: + """ + Retrieves the required message for removing a not executed transaction on Safe Transaction Service. + + :param safe_address: + :param safe_tx_hash: + :param chain_id: + :return: generated EIP712 message + """ remove_transaction_message = { "types": { "EIP712Domain": [ From 51bc7668a8ba066127c741ea828197a85e12de05 Mon Sep 17 00:00:00 2001 From: moisses89 <7888669+moisses89@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:10:25 +0100 Subject: [PATCH 6/6] Fix import issue --- gnosis/safe/api/__init__.py | 3 +-- gnosis/safe/api/transaction_service_api/__init__.py | 2 +- .../api/transaction_service_api/transaction_service_api.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gnosis/safe/api/__init__.py b/gnosis/safe/api/__init__.py index a4f1eb16d..82e5967bf 100644 --- a/gnosis/safe/api/__init__.py +++ b/gnosis/safe/api/__init__.py @@ -1,8 +1,7 @@ # flake8: noqa F401 -from transaction_service_api import TransactionServiceApi - from .base_api import SafeAPIException from .relay_service_api import RelayEstimation, RelaySentTransaction, RelayServiceApi +from .transaction_service_api import TransactionServiceApi __all__ = [ "SafeAPIException", diff --git a/gnosis/safe/api/transaction_service_api/__init__.py b/gnosis/safe/api/transaction_service_api/__init__.py index 1b2f5e4a4..c94ede637 100644 --- a/gnosis/safe/api/transaction_service_api/__init__.py +++ b/gnosis/safe/api/transaction_service_api/__init__.py @@ -1,4 +1,4 @@ -from transaction_service_api import TransactionServiceApi +from .transaction_service_api import TransactionServiceApi __all__ = [ "TransactionServiceApi", diff --git a/gnosis/safe/api/transaction_service_api/transaction_service_api.py b/gnosis/safe/api/transaction_service_api/transaction_service_api.py index 9d07fe5c5..58f5873ec 100644 --- a/gnosis/safe/api/transaction_service_api/transaction_service_api.py +++ b/gnosis/safe/api/transaction_service_api/transaction_service_api.py @@ -4,7 +4,6 @@ from eth_account.signers.local import LocalAccount from eth_typing import ChecksumAddress, HexStr from hexbytes import HexBytes -from transaction_service_tx import TransactionServiceTx from web3 import Web3 from gnosis.eth import EthereumNetwork @@ -12,6 +11,7 @@ from ..base_api import SafeAPIException, SafeBaseAPI from .transaction_service_messages import get_delegate_message +from .transaction_service_tx import TransactionServiceTx logger = logging.getLogger(__name__)