diff --git a/src/safe_cli/operators/safe_operator.py b/src/safe_cli/operators/safe_operator.py index 0c94779b..f88c5b1c 100644 --- a/src/safe_cli/operators/safe_operator.py +++ b/src/safe_cli/operators/safe_operator.py @@ -460,10 +460,15 @@ def approve_hash(self, hash_to_approve: HexBytes, sender: str) -> bool: if call_result: # There's revert message return False else: - signed_transaction = sender_account.sign_transaction(transaction_to_send) - tx_hash = self.ethereum_client.send_raw_transaction( - signed_transaction["raw_transaction"] - ) + if hasattr(sender_account, "get_signed_raw_transaction"): # Ledger, Trezor + raw_transaction = sender_account.get_signed_raw_transaction( + transaction_to_send, self.ethereum_client.get_chain_id() + ) + else: + raw_transaction = sender_account.sign_transaction(transaction_to_send)[ + "raw_transaction" + ] + tx_hash = self.ethereum_client.send_raw_transaction(raw_transaction) print_formatted_text( HTML( f"Sent tx with tx-hash {to_0x_hex_str(tx_hash)} from owner " diff --git a/tests/test_safe_operator.py b/tests/test_safe_operator.py index 2cb47e0e..ea8c75a0 100644 --- a/tests/test_safe_operator.py +++ b/tests/test_safe_operator.py @@ -184,6 +184,24 @@ def test_approve_hash(self): with self.assertRaises(HashAlreadyApproved): safe_operator.approve_hash(safe_tx_hash, self.ethereum_test_account.address) + def test_approve_hash_with_hardware_wallet(self): + owner = self.ethereum_test_account + safe_address = self.deploy_test_safe(owners=[owner.address]).address + safe_operator = SafeOperator(safe_address, self.ethereum_node_url) + safe_tx_hash = Web3.keccak(text="hardware-wallet") + # A hardware wallet has no sign_transaction + hw_wallet = mock.Mock(spec=["address", "get_signed_raw_transaction"]) + hw_wallet.address = owner.address + hw_wallet.get_signed_raw_transaction.side_effect = lambda tx, chain_id: ( + self.w3.eth.account.sign_transaction(tx, owner.key).raw_transaction + ) + with mock.patch.object(safe_operator, "search_account", return_value=hw_wallet): + self.assertTrue(safe_operator.approve_hash(safe_tx_hash, owner.address)) + hw_wallet.get_signed_raw_transaction.assert_called_once() + self.assertTrue( + safe_operator.safe.retrieve_is_hash_approved(owner.address, safe_tx_hash) + ) + @mock.patch("safe_cli.safe_addresses._get_valid_contract") def test_sign_message(self, mock_sign_message_lib_address): safe_address = self.deploy_test_safe(