diff --git a/.gitignore b/.gitignore
index 900f464..77b0191 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,6 @@ src/hedera_sdk_python/hapi
.idea
# Pytest
-.pytest_cache
\ No newline at end of file
+.pytest_cache
+
+uv.lock
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/GitLink.xml b/.idea/GitLink.xml
deleted file mode 100644
index 009597c..0000000
--- a/.idea/GitLink.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/hedera_sdk_python.iml b/.idea/hedera_sdk_python.iml
deleted file mode 100644
index b9527bf..0000000
--- a/.idea/hedera_sdk_python.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 52cb8d5..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index def6f37..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/hedera_sdk_python/consensus/topic_delete_transaction.py b/src/hedera_sdk_python/consensus/topic_delete_transaction.py
index 2dd23e7..014c6ef 100644
--- a/src/hedera_sdk_python/consensus/topic_delete_transaction.py
+++ b/src/hedera_sdk_python/consensus/topic_delete_transaction.py
@@ -18,6 +18,9 @@ def build_transaction_body(self):
Raises:
ValueError: If required fields are missing.
"""
+ if self.topic_id is None:
+ raise ValueError("Missing required fields: topic_id")
+
transaction_body = self.build_base_transaction_body()
transaction_body.consensusDeleteTopic.CopyFrom(consensus_delete_topic_pb2.ConsensusDeleteTopicTransactionBody(
topicID=self.topic_id.to_proto()
diff --git a/src/hedera_sdk_python/consensus/topic_update_transaction.py b/src/hedera_sdk_python/consensus/topic_update_transaction.py
index ae0bd5a..6d21552 100644
--- a/src/hedera_sdk_python/consensus/topic_update_transaction.py
+++ b/src/hedera_sdk_python/consensus/topic_update_transaction.py
@@ -25,6 +25,9 @@ def build_transaction_body(self):
Raises:
ValueError: If required fields are missing.
"""
+ if self.topic_id is None:
+ raise ValueError("Missing required fields: topic_id")
+
transaction_body = self.build_base_transaction_body()
transaction_body.consensusUpdateTopic.CopyFrom(consensus_update_topic_pb2.ConsensusUpdateTopicTransactionBody(
topicID=self.topic_id.to_proto(),
diff --git a/src/hedera_sdk_python/query/transaction_get_receipt_query.py b/src/hedera_sdk_python/query/transaction_get_receipt_query.py
index 861924e..dce5525 100644
--- a/src/hedera_sdk_python/query/transaction_get_receipt_query.py
+++ b/src/hedera_sdk_python/query/transaction_get_receipt_query.py
@@ -1,5 +1,5 @@
from hedera_sdk_python.query.query import Query
-from hedera_sdk_python.hapi import transaction_get_receipt_pb2, query_pb2
+from hedera_sdk_python.hapi import transaction_get_receipt_pb2, query_pb2, query_header_pb2
from hedera_sdk_python.response_code import ResponseCode
from hedera_sdk_python.transaction.transaction_id import TransactionId
from hedera_sdk_python.transaction.transaction_receipt import TransactionReceipt
@@ -31,6 +31,13 @@ def set_transaction_id(self, transaction_id: TransactionId):
self.transaction_id = transaction_id
return self
+ def _is_payment_required(self):
+ """
+ Override the default in the base Query class:
+ This particular query does NOT require a payment.
+ """
+ return False
+
def _make_request(self):
"""
Constructs the protobuf request for the transaction receipt query.
@@ -46,7 +53,9 @@ def _make_request(self):
if not self.transaction_id:
raise ValueError("Transaction ID must be set before making the request.")
- query_header = self._make_request_header()
+ query_header = query_header_pb2.QueryHeader()
+ query_header.responseType = query_header_pb2.ResponseType.ANSWER_ONLY
+
transaction_get_receipt = transaction_get_receipt_pb2.TransactionGetReceiptQuery()
transaction_get_receipt.header.CopyFrom(query_header)
transaction_get_receipt.transactionID.CopyFrom(self.transaction_id.to_proto())
diff --git a/src/hedera_sdk_python/transaction/transaction.py b/src/hedera_sdk_python/transaction/transaction.py
index 5d382f2..621dce9 100644
--- a/src/hedera_sdk_python/transaction/transaction.py
+++ b/src/hedera_sdk_python/transaction/transaction.py
@@ -185,23 +185,24 @@ def build_base_transaction_body(self):
ValueError: If required IDs are not set.
"""
if self.transaction_id is None:
- if self.operator_account_id is None:
- raise ValueError("Operator account ID is not set.")
- self.transaction_id = TransactionId.generate(self.operator_account_id)
+ if self.operator_account_id is None:
+ raise ValueError("Operator account ID is not set.")
+ self.transaction_id = TransactionId.generate(self.operator_account_id)
transaction_id_proto = self.transaction_id.to_proto()
if self.node_account_id is None:
raise ValueError("Node account ID is not set.")
- transaction_body = transaction_body_pb2.TransactionBody(
- transactionID=transaction_id_proto,
- nodeAccountID=self.node_account_id.to_proto(),
- transactionFee=self.transaction_fee or self._default_transaction_fee,
- transactionValidDuration=duration_pb2.Duration(seconds=self.transaction_valid_duration),
- generateRecord=self.generate_record,
- memo=self.memo
- )
+ transaction_body = transaction_body_pb2.TransactionBody()
+ transaction_body.transactionID.CopyFrom(transaction_id_proto)
+ transaction_body.nodeAccountID.CopyFrom(self.node_account_id.to_proto())
+
+ transaction_body.transactionFee = self.transaction_fee or self._default_transaction_fee
+
+ transaction_body.transactionValidDuration.seconds = self.transaction_valid_duration
+ transaction_body.generateRecord = self.generate_record
+ transaction_body.memo = self.memo
return transaction_body
diff --git a/tests/test_topic_create_transaction.py b/tests/test_topic_create_transaction.py
new file mode 100644
index 0000000..71f49e9
--- /dev/null
+++ b/tests/test_topic_create_transaction.py
@@ -0,0 +1,106 @@
+import pytest
+from unittest.mock import MagicMock
+from hedera_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction
+from hedera_sdk_python.account.account_id import AccountId
+from hedera_sdk_python.crypto.private_key import PrivateKey
+from hedera_sdk_python.client.client import Client
+from hedera_sdk_python.response_code import ResponseCode
+from hedera_sdk_python.hapi import (
+ transaction_receipt_pb2
+)
+from hedera_sdk_python.transaction.transaction_receipt import TransactionReceipt
+from hedera_sdk_python.transaction.transaction_id import TransactionId
+from hedera_sdk_python.hapi import timestamp_pb2 as hapi_timestamp_pb2
+
+@pytest.mark.usefixtures("mock_account_ids")
+def test_build_topic_create_transaction_body(mock_account_ids):
+ """
+ Test building a TopicCreateTransaction body with valid memo, admin key.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+
+ tx = TopicCreateTransaction(memo="Hello Topic", admin_key=PrivateKey.generate().public_key())
+
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ transaction_body = tx.build_transaction_body()
+
+ assert transaction_body.consensusCreateTopic.memo == "Hello Topic"
+ assert transaction_body.consensusCreateTopic.adminKey.ed25519
+
+def test_missing_operator_in_topic_create(mock_account_ids):
+ """
+ Test that building the body fails if no operator ID is set.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+
+ tx = TopicCreateTransaction(memo="No Operator")
+ tx.node_account_id = node_account_id
+
+ with pytest.raises(ValueError, match="Operator account ID is not set."):
+ tx.build_transaction_body()
+
+def test_missing_node_in_topic_create(mock_account_ids):
+ """
+ Test that building the body fails if no node account ID is set.
+ """
+ tx = TopicCreateTransaction(memo="No Node")
+ tx.operator_account_id = AccountId(0, 0, 2)
+
+ with pytest.raises(ValueError, match="Node account ID is not set."):
+ tx.build_transaction_body()
+
+def test_sign_topic_create_transaction(mock_account_ids):
+ """
+ Test signing the TopicCreateTransaction with a private key.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ tx = TopicCreateTransaction(memo="Signing test")
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ private_key = PrivateKey.generate()
+
+ body_bytes = tx.build_transaction_body().SerializeToString()
+ tx.transaction_body_bytes = body_bytes
+
+ tx.sign(private_key)
+ assert len(tx.signature_map.sigPair) == 1
+
+def test_execute_topic_create_transaction(mock_account_ids):
+ """
+ Test executing the TopicCreateTransaction with a mock Client.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+
+ tx = TopicCreateTransaction(memo="Execute test")
+ tx.operator_account_id = AccountId(0, 0, 2)
+
+ client = MagicMock(spec=Client)
+ client.operator_private_key = PrivateKey.generate()
+ client.operator_account_id = AccountId(0, 0, 2)
+ client.node_account_id = node_account_id
+
+ real_tx_id = TransactionId(
+ account_id=AccountId(0, 0, 2),
+ valid_start=hapi_timestamp_pb2.Timestamp(seconds=11111, nanos=222)
+ )
+ client.generate_transaction_id.return_value = real_tx_id
+
+ client.topic_stub = MagicMock()
+
+ mock_response = MagicMock()
+ mock_response.nodeTransactionPrecheckCode = ResponseCode.OK
+ client.topic_stub.createTopic.return_value = mock_response
+
+ proto_receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.OK)
+ real_receipt = TransactionReceipt.from_proto(proto_receipt)
+ client.get_transaction_receipt.return_value = real_receipt
+
+ receipt = tx.execute(client)
+
+ client.topic_stub.createTopic.assert_called_once()
+ assert receipt is not None
+ assert receipt.status == ResponseCode.OK
+ print("Test passed: TopicCreateTransaction executed successfully.")
diff --git a/tests/test_topic_delete_transaction.py b/tests/test_topic_delete_transaction.py
new file mode 100644
index 0000000..a4e5c41
--- /dev/null
+++ b/tests/test_topic_delete_transaction.py
@@ -0,0 +1,94 @@
+import pytest
+from unittest.mock import MagicMock
+from hedera_sdk_python.consensus.topic_delete_transaction import TopicDeleteTransaction
+from hedera_sdk_python.consensus.topic_id import TopicId
+from hedera_sdk_python.account.account_id import AccountId
+from hedera_sdk_python.crypto.private_key import PrivateKey
+from hedera_sdk_python.client.client import Client
+from hedera_sdk_python.response_code import ResponseCode
+from hedera_sdk_python.hapi import transaction_receipt_pb2
+from hedera_sdk_python.transaction.transaction_receipt import TransactionReceipt
+from hedera_sdk_python.transaction.transaction_id import TransactionId
+from hedera_sdk_python.hapi import timestamp_pb2 as hapi_timestamp_pb2
+
+@pytest.mark.usefixtures("mock_account_ids")
+def test_build_topic_delete_transaction_body(mock_account_ids):
+ """
+ Test building a TopicDeleteTransaction body with a valid topic ID.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ topic_id = TopicId(0,0,1234)
+ tx = TopicDeleteTransaction(topic_id=topic_id)
+
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ transaction_body = tx.build_transaction_body()
+ assert transaction_body.consensusDeleteTopic.topicID.topicNum == 1234
+
+def test_missing_topic_id_in_delete(mock_account_ids):
+ """
+ Test that building fails if no topic ID is provided.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ tx = TopicDeleteTransaction(topic_id=None)
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ with pytest.raises(ValueError, match="Missing required fields"):
+ tx.build_transaction_body()
+
+def test_sign_topic_delete_transaction(mock_account_ids):
+ """
+ Test signing the TopicDeleteTransaction with a private key.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ tx = TopicDeleteTransaction(topic_id=TopicId(0,0,9876))
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ private_key = PrivateKey.generate()
+
+ body_bytes = tx.build_transaction_body().SerializeToString()
+ tx.transaction_body_bytes = body_bytes
+
+ tx.sign(private_key)
+ assert len(tx.signature_map.sigPair) == 1
+
+def test_execute_topic_delete_transaction(mock_account_ids):
+ """
+ Test executing the TopicDeleteTransaction with a mock Client.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ topic_id = TopicId(0,0,9876)
+ tx = TopicDeleteTransaction(topic_id=topic_id)
+ tx.operator_account_id = AccountId(0, 0, 2)
+
+ client = MagicMock(spec=Client)
+ client.operator_private_key = PrivateKey.generate()
+ client.operator_account_id = AccountId(0, 0, 2)
+ client.node_account_id = node_account_id
+
+ real_tx_id = TransactionId(
+ account_id=AccountId(0, 0, 2),
+ valid_start=hapi_timestamp_pb2.Timestamp(seconds=20000, nanos=3333)
+ )
+ client.generate_transaction_id.return_value = real_tx_id
+
+ client.topic_stub = MagicMock()
+ mock_response = MagicMock()
+ mock_response.nodeTransactionPrecheckCode = ResponseCode.OK
+ client.topic_stub.deleteTopic.return_value = mock_response
+
+ proto_receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.OK)
+ real_receipt = TransactionReceipt.from_proto(proto_receipt)
+ client.get_transaction_receipt.return_value = real_receipt
+
+ # Act
+ receipt = tx.execute(client)
+
+ # Assert
+ client.topic_stub.deleteTopic.assert_called_once()
+ assert receipt is not None
+ assert receipt.status == ResponseCode.OK
+ print("Test passed: TopicDeleteTransaction executed successfully.")
diff --git a/tests/test_topic_message_submit_transaction.py b/tests/test_topic_message_submit_transaction.py
new file mode 100644
index 0000000..567e2a6
--- /dev/null
+++ b/tests/test_topic_message_submit_transaction.py
@@ -0,0 +1,60 @@
+import pytest
+from unittest.mock import MagicMock
+from hedera_sdk_python.consensus.topic_id import TopicId
+from hedera_sdk_python.consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction
+from hedera_sdk_python.transaction.transaction_id import TransactionId
+from hedera_sdk_python.transaction.transaction_receipt import TransactionReceipt
+from hedera_sdk_python.account.account_id import AccountId
+from hedera_sdk_python.crypto.private_key import PrivateKey
+from hedera_sdk_python.client.client import Client
+from hedera_sdk_python.response_code import ResponseCode
+from hedera_sdk_python.hapi import (
+ response_pb2,
+ transaction_receipt_pb2,
+ timestamp_pb2 as hapi_timestamp_pb2
+)
+
+def test_execute_topic_submit_message():
+ """
+ Test executing the TopicMessageSubmitTransaction with a mock Client.
+ When calling tx.execute(client), freeze_with() checks client.node_account_id if tx.node_account_id is None.
+ """
+ topic_id = TopicId(0, 0, 1234)
+ message = "Hello from topic submit!"
+ tx = TopicMessageSubmitTransaction(topic_id, message)
+
+ tx.operator_account_id = AccountId(0, 0, 2)
+
+ client = MagicMock(spec=Client)
+ client.operator_private_key = PrivateKey.generate()
+ client.operator_account_id = AccountId(0, 0, 2)
+ client.node_account_id = AccountId(0, 0, 3)
+
+ real_tx_id = TransactionId(
+ account_id=AccountId(0, 0, 2),
+ valid_start=hapi_timestamp_pb2.Timestamp(seconds=12345, nanos=6789)
+ )
+ client.generate_transaction_id.return_value = real_tx_id
+
+ client.topic_stub = MagicMock()
+
+ mock_response = MagicMock()
+ mock_response.nodeTransactionPrecheckCode = ResponseCode.OK
+ client.topic_stub.submitMessage.return_value = mock_response
+
+ real_receipt_proto = transaction_receipt_pb2.TransactionReceipt(
+ status=ResponseCode.OK
+ )
+ real_receipt = TransactionReceipt.from_proto(real_receipt_proto)
+
+ client.get_transaction_receipt.return_value = real_receipt
+
+ try:
+ receipt = tx.execute(client)
+ except Exception as e:
+ pytest.fail(f"TopicMessageSubmitTransaction execution failed with: {e}")
+
+ client.topic_stub.submitMessage.assert_called_once()
+ assert receipt is not None
+ assert receipt.status == ResponseCode.OK
+ print("Test passed: TopicMessageSubmitTransaction executed successfully.")
diff --git a/tests/test_topic_update_transaction.py b/tests/test_topic_update_transaction.py
new file mode 100644
index 0000000..5fab690
--- /dev/null
+++ b/tests/test_topic_update_transaction.py
@@ -0,0 +1,95 @@
+import pytest
+from unittest.mock import MagicMock
+from hedera_sdk_python.consensus.topic_update_transaction import TopicUpdateTransaction
+from hedera_sdk_python.consensus.topic_id import TopicId
+from hedera_sdk_python.account.account_id import AccountId
+from hedera_sdk_python.crypto.private_key import PrivateKey
+from hedera_sdk_python.client.client import Client
+from hedera_sdk_python.response_code import ResponseCode
+from hedera_sdk_python.hapi import transaction_receipt_pb2
+from hedera_sdk_python.transaction.transaction_receipt import TransactionReceipt
+from hedera_sdk_python.transaction.transaction_id import TransactionId
+from hedera_sdk_python.hapi import timestamp_pb2 as hapi_timestamp_pb2
+
+@pytest.mark.usefixtures("mock_account_ids")
+def test_build_topic_update_transaction_body(mock_account_ids):
+ """
+ Test building a TopicUpdateTransaction body with valid topic ID and memo.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ topic_id = TopicId(0,0,1234)
+ tx = TopicUpdateTransaction(topic_id=topic_id, memo="Updated Memo")
+
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ transaction_body = tx.build_transaction_body()
+ assert transaction_body.consensusUpdateTopic.topicID.topicNum == 1234
+ assert transaction_body.consensusUpdateTopic.memo.value == "Updated Memo"
+
+def test_missing_topic_id_in_update(mock_account_ids):
+ """
+ Test that building fails if no topic ID is provided.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+
+ tx = TopicUpdateTransaction(topic_id=None, memo="No ID")
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ with pytest.raises(ValueError, match="Missing required fields"):
+ tx.build_transaction_body()
+
+def test_sign_topic_update_transaction(mock_account_ids):
+ """
+ Test signing the TopicUpdateTransaction with a private key.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ topic_id = TopicId(0,0,9999)
+ tx = TopicUpdateTransaction(topic_id=topic_id, memo="Signature test")
+ tx.operator_account_id = AccountId(0, 0, 2)
+ tx.node_account_id = node_account_id
+
+ private_key = PrivateKey.generate()
+
+ body_bytes = tx.build_transaction_body().SerializeToString()
+ tx.transaction_body_bytes = body_bytes
+
+ tx.sign(private_key)
+ assert len(tx.signature_map.sigPair) == 1
+
+def test_execute_topic_update_transaction(mock_account_ids):
+ """
+ Test executing the TopicUpdateTransaction with a mock Client.
+ """
+ _, _, node_account_id, _, _ = mock_account_ids
+ topic_id = TopicId(0,0,9999)
+ tx = TopicUpdateTransaction(topic_id=topic_id, memo="Exec update")
+ tx.operator_account_id = AccountId(0, 0, 2)
+
+ client = MagicMock(spec=Client)
+ client.operator_private_key = PrivateKey.generate()
+ client.operator_account_id = AccountId(0, 0, 2)
+ client.node_account_id = node_account_id
+
+ real_tx_id = TransactionId(
+ account_id=AccountId(0, 0, 2),
+ valid_start=hapi_timestamp_pb2.Timestamp(seconds=12345, nanos=6789)
+ )
+ client.generate_transaction_id.return_value = real_tx_id
+
+ client.topic_stub = MagicMock()
+ mock_response = MagicMock()
+ mock_response.nodeTransactionPrecheckCode = ResponseCode.OK
+ client.topic_stub.updateTopic.return_value = mock_response
+
+ proto_receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.OK)
+ real_receipt = TransactionReceipt.from_proto(proto_receipt)
+ client.get_transaction_receipt.return_value = real_receipt
+
+ receipt = tx.execute(client)
+
+ client.topic_stub.updateTopic.assert_called_once()
+ assert receipt is not None
+ assert receipt.status == ResponseCode.OK
+ print("Test passed: TopicUpdateTransaction executed successfully.")