diff --git a/src/hiero_sdk_python/account/account_allowance_approve_transaction.py b/src/hiero_sdk_python/account/account_allowance_approve_transaction.py index 056893792..e3436e1e6 100644 --- a/src/hiero_sdk_python/account/account_allowance_approve_transaction.py +++ b/src/hiero_sdk_python/account/account_allowance_approve_transaction.py @@ -372,3 +372,13 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to approve allowances. """ return _Method(transaction_func=channel.crypto.approveAllowances, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("cryptoApproveAllowance"): + body = transaction_body.cryptoApproveAllowance + transaction.hbar_allowances = [HbarAllowance._from_proto(a) for a in body.cryptoAllowances] + transaction.token_allowances = [TokenAllowance._from_proto(a) for a in body.tokenAllowances] + transaction.nft_allowances = [TokenNftAllowance._from_proto(a) for a in body.nftAllowances] + return transaction diff --git a/src/hiero_sdk_python/account/account_allowance_delete_transaction.py b/src/hiero_sdk_python/account/account_allowance_delete_transaction.py index 6603cac6f..535ec5aeb 100644 --- a/src/hiero_sdk_python/account/account_allowance_delete_transaction.py +++ b/src/hiero_sdk_python/account/account_allowance_delete_transaction.py @@ -131,3 +131,11 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to delete allowances. """ return _Method(transaction_func=channel.crypto.deleteAllowances, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("cryptoDeleteAllowance"): + body = transaction_body.cryptoDeleteAllowance + transaction.nft_wipe = [TokenNftAllowance._from_wipe_proto(a) for a in body.nftAllowances] + return transaction diff --git a/src/hiero_sdk_python/account/account_create_transaction.py b/src/hiero_sdk_python/account/account_create_transaction.py index 0aeac467c..c7a3b8025 100644 --- a/src/hiero_sdk_python/account/account_create_transaction.py +++ b/src/hiero_sdk_python/account/account_create_transaction.py @@ -319,7 +319,7 @@ def _build_proto_body(self) -> crypto_create_pb2.CryptoCreateTransactionBody: # triggers an INVALID_INITIAL_BALANCE pre-check error instead of a local error. initialBalance=ctypes.c_uint64(initial_balance_tinybars).value, receiverSigRequired=self.receiver_signature_required, - autoRenewPeriod=duration_pb2.Duration(seconds=self.auto_renew_period.seconds), + autoRenewPeriod=(duration_pb2.Duration(seconds=self.auto_renew_period.seconds) if self.auto_renew_period else None), memo=self.account_memo, max_automatic_token_associations=self.max_automatic_token_associations, alias=self.alias.address_bytes if self.alias else None, @@ -371,3 +371,27 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An instance of _Method containing the transaction and query functions. """ return _Method(transaction_func=channel.crypto.createAccount, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("cryptoCreateAccount"): + body = transaction_body.cryptoCreateAccount + if body.HasField("key"): + transaction.key = Key.from_proto_key(body.key) + transaction.initial_balance = body.initialBalance + transaction.receiver_signature_required = body.receiverSigRequired + transaction.auto_renew_period = None + if body.HasField("autoRenewPeriod"): + transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + transaction.account_memo = body.memo if body.memo else None + transaction.max_automatic_token_associations = body.max_automatic_token_associations + transaction.alias = EvmAddress.from_bytes(body.alias) if body.alias else None + transaction.decline_staking_reward = body.decline_reward + staked_id = body.WhichOneof("staked_id") + if staked_id == "staked_account_id": + transaction.staked_account_id = AccountId._from_proto(body.staked_account_id) + elif staked_id == "staked_node_id": + transaction.staked_node_id = body.staked_node_id + return transaction diff --git a/src/hiero_sdk_python/account/account_delete_transaction.py b/src/hiero_sdk_python/account/account_delete_transaction.py index cb84097a3..f4db8a182 100644 --- a/src/hiero_sdk_python/account/account_delete_transaction.py +++ b/src/hiero_sdk_python/account/account_delete_transaction.py @@ -138,3 +138,14 @@ def _get_method(self, channel: _Channel) -> _Method: delete accounts. """ return _Method(transaction_func=channel.crypto.cryptoDelete, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("cryptoDelete"): + body = transaction_body.cryptoDelete + if body.HasField("deleteAccountID"): + transaction.account_id = AccountId._from_proto(body.deleteAccountID) + if body.HasField("transferAccountID"): + transaction.transfer_account_id = AccountId._from_proto(body.transferAccountID) + return transaction diff --git a/src/hiero_sdk_python/account/account_update_transaction.py b/src/hiero_sdk_python/account/account_update_transaction.py index 7fd7cb9ef..4723e9b4a 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -358,3 +358,31 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to update an account. """ return _Method(transaction_func=channel.crypto.updateAccount, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("cryptoUpdateAccount"): + body = transaction_body.cryptoUpdateAccount + if body.HasField("accountIDToUpdate"): + transaction.account_id = AccountId._from_proto(body.accountIDToUpdate) + if body.HasField("key"): + transaction.key = Key.from_proto_key(body.key) + if body.HasField("autoRenewPeriod"): + transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + if body.HasField("memo"): + transaction.account_memo = body.memo.value + if body.HasField("expirationTime"): + transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime) + if body.HasField("receiverSigRequiredWrapper"): + transaction.receiver_signature_required = body.receiverSigRequiredWrapper.value + if body.HasField("max_automatic_token_associations"): + transaction.max_automatic_token_associations = body.max_automatic_token_associations.value + if body.HasField("decline_reward"): + transaction.decline_staking_reward = body.decline_reward.value + staked_id = body.WhichOneof("staked_id") + if staked_id == "staked_account_id": + transaction.staked_account_id = AccountId._from_proto(body.staked_account_id) + elif staked_id == "staked_node_id": + transaction.staked_node_id = body.staked_node_id + return transaction diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index 45083c8be..df9d7ede1 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -11,7 +11,6 @@ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import consensus_create_topic_pb2, transaction_pb2 @@ -20,6 +19,7 @@ ) from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.utils.key_utils import key_to_proto @@ -251,3 +251,23 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: The method for executing the transaction. """ return _Method(transaction_func=channel.topic.createTopic, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("consensusCreateTopic"): + body = transaction_body.consensusCreateTopic + transaction.memo = body.memo if body.memo else "" + if body.HasField("adminKey"): + transaction.admin_key = Key.from_proto_key(body.adminKey) + if body.HasField("submitKey"): + transaction.submit_key = Key.from_proto_key(body.submitKey) + if body.HasField("autoRenewPeriod"): + transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + if body.HasField("autoRenewAccount"): + transaction.auto_renew_account = AccountId._from_proto(body.autoRenewAccount) + if body.HasField("fee_schedule_key"): + transaction.fee_schedule_key = Key.from_proto_key(body.fee_schedule_key) + transaction.custom_fees = [CustomFixedFee._from_topic_fee_proto(f) for f in body.custom_fees] + transaction.fee_exempt_keys = [Key.from_proto_key(k) for k in body.fee_exempt_key_list] + return transaction diff --git a/src/hiero_sdk_python/consensus/topic_delete_transaction.py b/src/hiero_sdk_python/consensus/topic_delete_transaction.py index fb789a9d6..069238262 100644 --- a/src/hiero_sdk_python/consensus/topic_delete_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_delete_transaction.py @@ -94,3 +94,12 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: The method to execute the transaction. """ return _Method(transaction_func=channel.topic.deleteTopic, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("consensusDeleteTopic"): + body = transaction_body.consensusDeleteTopic + if body.HasField("topicID"): + transaction.topic_id = TopicId._from_proto(body.topicID) + return transaction diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index 0cc9529d5..5b41cac28 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -249,6 +249,17 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.topic.submitMessage, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("consensusSubmitMessage"): + body = transaction_body.consensusSubmitMessage + if body.HasField("topicID"): + transaction.topic_id = TopicId._from_proto(body.topicID) + transaction.message = body.message.decode("utf-8") if body.message else None + transaction._total_chunks = transaction.get_required_chunks() + return transaction + def freeze_with(self, client: Client) -> TopicMessageSubmitTransaction: if self._transaction_body_bytes: return self diff --git a/src/hiero_sdk_python/consensus/topic_update_transaction.py b/src/hiero_sdk_python/consensus/topic_update_transaction.py index ef283f9b8..d28be948e 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -312,3 +312,32 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: The method to execute the transaction. """ return _Method(transaction_func=channel.topic.updateTopic, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("consensusUpdateTopic"): + body = transaction_body.consensusUpdateTopic + transaction.memo = None + transaction.auto_renew_period = None + if body.HasField("topicID"): + transaction.topic_id = TopicId._from_proto(body.topicID) + if body.HasField("memo"): + transaction.memo = body.memo.value + if body.HasField("adminKey"): + transaction.admin_key = Key.from_proto_key(body.adminKey) + if body.HasField("submitKey"): + transaction.submit_key = Key.from_proto_key(body.submitKey) + if body.HasField("autoRenewPeriod"): + transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + if body.HasField("autoRenewAccount"): + transaction.auto_renew_account = AccountId._from_proto(body.autoRenewAccount) + if body.HasField("expirationTime"): + transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime) + if body.HasField("fee_schedule_key"): + transaction.fee_schedule_key = Key.from_proto_key(body.fee_schedule_key) + if body.HasField("custom_fees"): + transaction.custom_fees = [CustomFixedFee._from_topic_fee_proto(f) for f in body.custom_fees.fees] + if body.HasField("fee_exempt_key_list"): + transaction.fee_exempt_keys = [Key.from_proto_key(k) for k in body.fee_exempt_key_list.keys] + return transaction diff --git a/src/hiero_sdk_python/contract/contract_create_transaction.py b/src/hiero_sdk_python/contract/contract_create_transaction.py index 795c64a70..8b1bb9a8b 100644 --- a/src/hiero_sdk_python/contract/contract_create_transaction.py +++ b/src/hiero_sdk_python/contract/contract_create_transaction.py @@ -10,6 +10,7 @@ from hiero_sdk_python.contract.contract_function_parameters import ( ContractFunctionParameters, ) +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method @@ -387,6 +388,38 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body.contractCreateInstance.CopyFrom(contract_create_body) return schedulable_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("contractCreateInstance"): + body = transaction_body.contractCreateInstance + if body.HasField("adminKey"): + transaction.admin_key = Key.from_proto_key(body.adminKey) + transaction.gas = body.gas + transaction.initial_balance = body.initialBalance if body.initialBalance else None + if body.HasField("proxyAccountID"): + transaction.proxy_account_id = AccountId._from_proto(body.proxyAccountID) + transaction.auto_renew_period = ( + Duration._from_proto(body.autoRenewPeriod) if body.HasField("autoRenewPeriod") else Duration(DEFAULT_AUTO_RENEW_PERIOD) + ) + transaction.parameters = body.constructorParameters if body.constructorParameters else None + transaction.contract_memo = body.memo if body.memo else None + transaction.max_automatic_token_associations = body.max_automatic_token_associations + if body.HasField("auto_renew_account_id"): + transaction.auto_renew_account_id = AccountId._from_proto(body.auto_renew_account_id) + transaction.decline_reward = body.decline_reward if body.decline_reward else None + initcode_source = body.WhichOneof("initcodeSource") + if initcode_source == "fileID": + transaction.bytecode_file_id = FileId._from_proto(body.fileID) + elif initcode_source == "initcode": + transaction.bytecode = body.initcode + staked_id = body.WhichOneof("staked_id") + if staked_id == "staked_account_id": + transaction.staked_account_id = AccountId._from_proto(body.staked_account_id) + elif staked_id == "staked_node_id": + transaction.staked_node_id = body.staked_node_id + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the contract create transaction. diff --git a/src/hiero_sdk_python/contract/contract_delete_transaction.py b/src/hiero_sdk_python/contract/contract_delete_transaction.py index 2ae52ef8e..f9005c983 100644 --- a/src/hiero_sdk_python/contract/contract_delete_transaction.py +++ b/src/hiero_sdk_python/contract/contract_delete_transaction.py @@ -153,6 +153,21 @@ def build_transaction_body(self): transaction_body.contractDeleteInstance.CopyFrom(contract_delete_body) return transaction_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("contractDeleteInstance"): + body = transaction_body.contractDeleteInstance + if body.HasField("contractID"): + transaction.contract_id = ContractId._from_proto(body.contractID) + transaction.permanent_removal = body.permanent_removal if body.permanent_removal else None + obtainers = body.WhichOneof("obtainers") + if obtainers == "transferAccountID": + transaction.transfer_account_id = AccountId._from_proto(body.transferAccountID) + elif obtainers == "transferContractID": + transaction.transfer_contract_id = ContractId._from_proto(body.transferContractID) + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the contract delete transaction. diff --git a/src/hiero_sdk_python/contract/contract_execute_transaction.py b/src/hiero_sdk_python/contract/contract_execute_transaction.py index 14229fd89..c4edcf883 100644 --- a/src/hiero_sdk_python/contract/contract_execute_transaction.py +++ b/src/hiero_sdk_python/contract/contract_execute_transaction.py @@ -163,6 +163,18 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body.contractCall.CopyFrom(contract_execute_body) return schedulable_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("contractCall"): + body = transaction_body.contractCall + if body.HasField("contractID"): + transaction.contract_id = ContractId._from_proto(body.contractID) + transaction.gas = body.gas if body.gas else None + transaction.amount = body.amount if body.amount else None + transaction.function_parameters = body.functionParameters if body.functionParameters else None + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the contract execute transaction. diff --git a/src/hiero_sdk_python/contract/contract_update_transaction.py b/src/hiero_sdk_python/contract/contract_update_transaction.py index 8909f618f..d2336c2d5 100644 --- a/src/hiero_sdk_python/contract/contract_update_transaction.py +++ b/src/hiero_sdk_python/contract/contract_update_transaction.py @@ -11,6 +11,7 @@ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.contract.contract_id import ContractId +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method @@ -302,6 +303,37 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body.contractUpdateInstance.CopyFrom(contract_update_body) return schedulable_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("contractUpdateInstance"): + body = transaction_body.contractUpdateInstance + if body.HasField("contractID"): + transaction.contract_id = ContractId._from_proto(body.contractID) + if body.HasField("expirationTime"): + transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime) + if body.HasField("adminKey"): + transaction.admin_key = Key.from_proto_key(body.adminKey) + if body.HasField("autoRenewPeriod"): + transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + memo_field = body.WhichOneof("memoField") + if memo_field == "memoWrapper": + transaction.contract_memo = body.memoWrapper.value + elif memo_field == "memo": + transaction.contract_memo = body.memo if body.memo else None + if body.HasField("max_automatic_token_associations"): + transaction.max_automatic_token_associations = body.max_automatic_token_associations.value + if body.HasField("auto_renew_account_id"): + transaction.auto_renew_account_id = AccountId._from_proto(body.auto_renew_account_id) + if body.HasField("decline_reward"): + transaction.decline_reward = body.decline_reward.value + staked_id = body.WhichOneof("staked_id") + if staked_id == "staked_account_id": + transaction.staked_account_id = AccountId._from_proto(body.staked_account_id) + elif staked_id == "staked_node_id": + transaction.staked_node_id = body.staked_node_id + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the contract update transaction. diff --git a/src/hiero_sdk_python/contract/ethereum_transaction.py b/src/hiero_sdk_python/contract/ethereum_transaction.py index fdcda5220..5858a3ad7 100644 --- a/src/hiero_sdk_python/contract/ethereum_transaction.py +++ b/src/hiero_sdk_python/contract/ethereum_transaction.py @@ -122,6 +122,17 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: """ raise ValueError("Cannot schedule an EthereumTransaction") + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("ethereumTransaction"): + body = transaction_body.ethereumTransaction + transaction.ethereum_data = body.ethereum_data if body.ethereum_data else None + if body.HasField("call_data"): + transaction.call_data = FileId._from_proto(body.call_data) + transaction.max_gas_allowed = body.max_gas_allowance if body.max_gas_allowance else None + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the ethereum transaction. diff --git a/src/hiero_sdk_python/file/file_append_transaction.py b/src/hiero_sdk_python/file/file_append_transaction.py index 3019296a5..08f4816ea 100644 --- a/src/hiero_sdk_python/file/file_append_transaction.py +++ b/src/hiero_sdk_python/file/file_append_transaction.py @@ -242,6 +242,13 @@ def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.file.appendContent, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("fileAppend"): + transaction._from_proto(transaction_body.fileAppend) + return transaction + def _from_proto(self, proto: file_append_pb2.FileAppendTransactionBody) -> FileAppendTransaction: """ Initializes a new FileAppendTransaction instance from a protobuf object. @@ -252,8 +259,8 @@ def _from_proto(self, proto: file_append_pb2.FileAppendTransactionBody) -> FileA Returns: FileAppendTransaction: This transaction instance. """ - self.file_id = FileId._from_proto(proto.fileID) if proto.fileID else None - self.contents = proto.contents + self.file_id = FileId._from_proto(proto.fileID) if proto.HasField("fileID") else None + self.contents = proto.contents if proto.contents else None self._total_chunks = self._calculate_total_chunks() return self diff --git a/src/hiero_sdk_python/file/file_create_transaction.py b/src/hiero_sdk_python/file/file_create_transaction.py index 0275469ea..b9b9d756b 100644 --- a/src/hiero_sdk_python/file/file_create_transaction.py +++ b/src/hiero_sdk_python/file/file_create_transaction.py @@ -182,6 +182,13 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.file.createFile, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("fileCreate"): + transaction._from_proto(transaction_body.fileCreate) + return transaction + def _from_proto(self, proto: file_create_pb2.FileCreateTransactionBody) -> FileCreateTransaction: """ Initializes a new FileCreateTransaction instance from a protobuf object. diff --git a/src/hiero_sdk_python/file/file_delete_transaction.py b/src/hiero_sdk_python/file/file_delete_transaction.py index b4ab5c7d8..58ae21657 100644 --- a/src/hiero_sdk_python/file/file_delete_transaction.py +++ b/src/hiero_sdk_python/file/file_delete_transaction.py @@ -104,3 +104,12 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to delete a file. """ return _Method(transaction_func=channel.file.deleteFile, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("fileDelete"): + body = transaction_body.fileDelete + if body.HasField("fileID"): + transaction.file_id = FileId._from_proto(body.fileID) + return transaction diff --git a/src/hiero_sdk_python/file/file_update_transaction.py b/src/hiero_sdk_python/file/file_update_transaction.py index f0daa894a..73194b625 100644 --- a/src/hiero_sdk_python/file/file_update_transaction.py +++ b/src/hiero_sdk_python/file/file_update_transaction.py @@ -6,6 +6,7 @@ from google.protobuf.wrappers_pb2 import StringValue from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.executable import _Method from hiero_sdk_python.file.file_id import FileId @@ -214,3 +215,18 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to update a file. """ return _Method(transaction_func=channel.file.updateFile, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("fileUpdate"): + body = transaction_body.fileUpdate + if body.HasField("fileID"): + transaction.file_id = FileId._from_proto(body.fileID) + transaction.keys = [Key.from_proto_key(k) for k in body.keys.keys] if body.keys.keys else None + transaction.contents = body.contents if body.contents else None + if body.HasField("expirationTime"): + transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime) + if body.HasField("memo"): + transaction.file_memo = body.memo.value + return transaction diff --git a/src/hiero_sdk_python/nodes/node_create_transaction.py b/src/hiero_sdk_python/nodes/node_create_transaction.py index 0431a1f8f..effbbe2e3 100644 --- a/src/hiero_sdk_python/nodes/node_create_transaction.py +++ b/src/hiero_sdk_python/nodes/node_create_transaction.py @@ -7,6 +7,7 @@ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.address_book.endpoint import Endpoint from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services.node_create_pb2 import NodeCreateTransactionBody @@ -254,6 +255,25 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: scheduled_body.nodeCreate.CopyFrom(node_create_body) return scheduled_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("nodeCreate"): + body = transaction_body.nodeCreate + if body.HasField("account_id"): + transaction.account_id = AccountId._from_proto(body.account_id) + transaction.description = body.description if body.description else None + transaction.gossip_endpoints = [Endpoint._from_proto(ep) for ep in body.gossip_endpoint] + transaction.service_endpoints = [Endpoint._from_proto(ep) for ep in body.service_endpoint] + transaction.gossip_ca_certificate = body.gossip_ca_certificate if body.gossip_ca_certificate else None + transaction.grpc_certificate_hash = body.grpc_certificate_hash if body.grpc_certificate_hash else None + if body.HasField("admin_key"): + transaction.admin_key = Key.from_proto_key(body.admin_key) + transaction.decline_reward = body.decline_reward if body.decline_reward else None + if body.HasField("grpc_proxy_endpoint"): + transaction.grpc_web_proxy_endpoint = Endpoint._from_proto(body.grpc_proxy_endpoint) + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the node create transaction. diff --git a/src/hiero_sdk_python/nodes/node_delete_transaction.py b/src/hiero_sdk_python/nodes/node_delete_transaction.py index ab8d5231a..a9bf4e871 100644 --- a/src/hiero_sdk_python/nodes/node_delete_transaction.py +++ b/src/hiero_sdk_python/nodes/node_delete_transaction.py @@ -92,6 +92,14 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: scheduled_body.nodeDelete.CopyFrom(node_delete_body) return scheduled_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("nodeDelete"): + body = transaction_body.nodeDelete + transaction.node_id = body.node_id if body.node_id else None + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the node delete transaction. diff --git a/src/hiero_sdk_python/nodes/node_update_transaction.py b/src/hiero_sdk_python/nodes/node_update_transaction.py index da9e169ae..e36ec051d 100644 --- a/src/hiero_sdk_python/nodes/node_update_transaction.py +++ b/src/hiero_sdk_python/nodes/node_update_transaction.py @@ -10,6 +10,7 @@ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.address_book.endpoint import Endpoint from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services.node_update_pb2 import NodeUpdateTransactionBody @@ -288,6 +289,30 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: scheduled_body.nodeUpdate.CopyFrom(node_update_body) return scheduled_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("nodeUpdate"): + body = transaction_body.nodeUpdate + transaction.node_id = body.node_id if body.node_id else None + if body.HasField("account_id"): + transaction.account_id = AccountId._from_proto(body.account_id) + if body.HasField("description"): + transaction.description = body.description.value + transaction.gossip_endpoints = [Endpoint._from_proto(ep) for ep in body.gossip_endpoint] + transaction.service_endpoints = [Endpoint._from_proto(ep) for ep in body.service_endpoint] + if body.HasField("gossip_ca_certificate"): + transaction.gossip_ca_certificate = body.gossip_ca_certificate.value + if body.HasField("grpc_certificate_hash"): + transaction.grpc_certificate_hash = body.grpc_certificate_hash.value + if body.HasField("admin_key"): + transaction.admin_key = Key.from_proto_key(body.admin_key) + if body.HasField("decline_reward"): + transaction.decline_reward = body.decline_reward.value + if body.HasField("grpc_proxy_endpoint"): + transaction.grpc_web_proxy_endpoint = Endpoint._from_proto(body.grpc_proxy_endpoint) + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the node update transaction. diff --git a/src/hiero_sdk_python/prng_transaction.py b/src/hiero_sdk_python/prng_transaction.py index 26c142e78..4a9791b8c 100644 --- a/src/hiero_sdk_python/prng_transaction.py +++ b/src/hiero_sdk_python/prng_transaction.py @@ -74,6 +74,14 @@ def build_transaction_body(self): transaction_body.util_prng.CopyFrom(prng_body) return transaction_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("util_prng"): + body = transaction_body.util_prng + transaction.range = body.range if body.range else None + return transaction + def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the prng transaction. diff --git a/src/hiero_sdk_python/schedule/schedule_create_transaction.py b/src/hiero_sdk_python/schedule/schedule_create_transaction.py index e9b7d8b9d..991048d7b 100644 --- a/src/hiero_sdk_python/schedule/schedule_create_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_create_transaction.py @@ -229,3 +229,20 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to create a schedule. """ return _Method(transaction_func=channel.schedule.createSchedule, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("scheduleCreate"): + body = transaction_body.scheduleCreate + if body.HasField("payerAccountID"): + transaction.payer_account_id = AccountId._from_proto(body.payerAccountID) + if body.HasField("adminKey"): + transaction.admin_key = PublicKey._from_proto(body.adminKey) + if body.HasField("scheduledTransactionBody"): + transaction.schedulable_body = body.scheduledTransactionBody + transaction.schedule_memo = body.memo + if body.HasField("expiration_time"): + transaction.expiration_time = Timestamp._from_protobuf(body.expiration_time) + transaction.wait_for_expiry = body.wait_for_expiry + return transaction diff --git a/src/hiero_sdk_python/schedule/schedule_delete_transaction.py b/src/hiero_sdk_python/schedule/schedule_delete_transaction.py index b5e5c9048..20794fcbc 100644 --- a/src/hiero_sdk_python/schedule/schedule_delete_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_delete_transaction.py @@ -103,3 +103,12 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to delete a schedule. """ return _Method(transaction_func=channel.schedule.deleteSchedule, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("scheduleDelete"): + body = transaction_body.scheduleDelete + if body.HasField("scheduleID"): + transaction.schedule_id = ScheduleId._from_proto(body.scheduleID) + return transaction diff --git a/src/hiero_sdk_python/schedule/schedule_sign_transaction.py b/src/hiero_sdk_python/schedule/schedule_sign_transaction.py index c2b677953..e1f617d52 100644 --- a/src/hiero_sdk_python/schedule/schedule_sign_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_sign_transaction.py @@ -109,3 +109,12 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to sign a schedule. """ return _Method(transaction_func=channel.schedule.signSchedule, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("scheduleSign"): + body = transaction_body.scheduleSign + if body.HasField("scheduleID"): + transaction.schedule_id = ScheduleId._from_proto(body.scheduleID) + return transaction diff --git a/src/hiero_sdk_python/system/freeze_transaction.py b/src/hiero_sdk_python/system/freeze_transaction.py index 16467aaf2..3c4d5e6f2 100644 --- a/src/hiero_sdk_python/system/freeze_transaction.py +++ b/src/hiero_sdk_python/system/freeze_transaction.py @@ -156,3 +156,16 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to freeze the network. """ return _Method(transaction_func=channel.freeze.freeze, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("freeze"): + body = transaction_body.freeze + if body.HasField("start_time"): + transaction.start_time = Timestamp._from_protobuf(body.start_time) + if body.HasField("update_file"): + transaction.file_id = FileId._from_proto(body.update_file) + transaction.file_hash = body.file_hash if body.file_hash else None + transaction.freeze_type = FreezeType._from_proto(body.freeze_type) + return transaction diff --git a/src/hiero_sdk_python/tokens/custom_fixed_fee.py b/src/hiero_sdk_python/tokens/custom_fixed_fee.py index cd3daa03b..2fa6e9fc9 100644 --- a/src/hiero_sdk_python/tokens/custom_fixed_fee.py +++ b/src/hiero_sdk_python/tokens/custom_fixed_fee.py @@ -264,6 +264,8 @@ def _from_proto(cls, proto_fee: custom_fees_pb2.CustomFee) -> CustomFixedFee: Raises: ValueError: If the `fixed_fee` field is not set in the protobuf message. """ + if not proto_fee.HasField("fixed_fee"): + raise ValueError("fixed_fee is required in CustomFee proto") fixed_fee_proto = proto_fee.fixed_fee denominating_token_id = None @@ -283,6 +285,29 @@ def _from_proto(cls, proto_fee: custom_fees_pb2.CustomFee) -> CustomFixedFee: all_collectors_are_exempt=collectors_are_exempt, ) + @classmethod + def _from_topic_fee_proto(cls, proto_fee: custom_fees_pb2.FixedCustomFee) -> CustomFixedFee: + """Creates a CustomFixedFee from a FixedCustomFee protobuf (used for topic fees).""" + from hiero_sdk_python.tokens.token_id import TokenId + + if not proto_fee.HasField("fixed_fee"): + raise ValueError("fixed_fee is required in FixedCustomFee proto") + fixed_fee_proto = proto_fee.fixed_fee + + denominating_token_id = None + if fixed_fee_proto.HasField("denominating_token_id"): + denominating_token_id = TokenId._from_proto(fixed_fee_proto.denominating_token_id) + + fee_collector_account_id = None + if proto_fee.HasField("fee_collector_account_id"): + fee_collector_account_id = AccountId._from_proto(proto_fee.fee_collector_account_id) + + return cls( + amount=fixed_fee_proto.amount, + denominating_token_id=denominating_token_id, + fee_collector_account_id=fee_collector_account_id, + ) + def __eq__(self, other: CustomFixedFee) -> bool: """Compares this CustomFixedFee instance with another object for equality. diff --git a/src/hiero_sdk_python/tokens/token_airdrop_claim.py b/src/hiero_sdk_python/tokens/token_airdrop_claim.py index cbfd93c61..91b5b3d02 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_claim.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_claim.py @@ -115,6 +115,17 @@ def _pending_airdrop_ids_to_proto(self) -> list[Any]: for airdrop in self._pending_airdrop_ids ] + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenClaimAirdrop"): + body = transaction_body.tokenClaimAirdrop + transaction._pending_airdrop_ids = [ + PendingAirdropId._from_proto(a) for a in body.pending_airdrops + ] + transaction._validate_all(transaction._pending_airdrop_ids) + return transaction + @classmethod def _from_proto(cls, proto: TokenClaimAirdropTransactionBody) -> TokenClaimAirdropTransaction: """Construct a TokenClaimAirdropTransaction from a TokenClaimAirdropTransactionBody. diff --git a/src/hiero_sdk_python/tokens/token_airdrop_transaction.py b/src/hiero_sdk_python/tokens/token_airdrop_transaction.py index da1e74360..f04aa18f2 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_transaction.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_transaction.py @@ -9,6 +9,7 @@ from __future__ import annotations +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_airdrop_pb2, transaction_pb2 @@ -16,6 +17,7 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.abstract_token_transfer_transaction import AbstractTokenTransferTransaction +from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer from hiero_sdk_python.tokens.token_transfer import TokenTransfer @@ -62,6 +64,32 @@ def _build_proto_body(self) -> token_airdrop_pb2.TokenAirdropTransactionBody: return token_airdrop_pb2.TokenAirdropTransactionBody(token_transfers=token_transfers) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenAirdrop"): + for transfer in transaction_body.tokenAirdrop.token_transfers: + if not transfer.HasField("token"): + continue + token_id = TokenId._from_proto(transfer.token) + for t in transfer.transfers: + if not t.HasField("accountID"): + continue + account_id = AccountId._from_proto(t.accountID) + expected_decimals = transfer.expected_decimals.value if transfer.HasField("expected_decimals") else None + transaction.token_transfers[token_id].append( + TokenTransfer(token_id, account_id, t.amount, expected_decimals, t.is_approval) + ) + for n in transfer.nftTransfers: + if not n.HasField("senderAccountID") or not n.HasField("receiverAccountID"): + continue + sender_id = AccountId._from_proto(n.senderAccountID) + receiver_id = AccountId._from_proto(n.receiverAccountID) + transaction.nft_transfers[token_id].append( + TokenNftTransfer(token_id, sender_id, receiver_id, n.serialNumber, n.is_approval) + ) + return transaction + @classmethod def _from_proto(cls, proto: token_airdrop_pb2.TokenAirdropTransactionBody) -> TokenAirdropTransaction: """ diff --git a/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py b/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py index 1e8e95429..7cc8cd30c 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py @@ -100,5 +100,15 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body.tokenCancelAirdrop.CopyFrom(token_airdrop_cancel_body) return schedulable_body + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenCancelAirdrop"): + body = transaction_body.tokenCancelAirdrop + transaction.pending_airdrops = [ + PendingAirdropId._from_proto(a) for a in body.pending_airdrops + ] + return transaction + def _get_method(self, channel): return _Method(transaction_func=channel.token.cancelAirdrop, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_associate_transaction.py b/src/hiero_sdk_python/tokens/token_associate_transaction.py index 13a14d328..ca447f820 100644 --- a/src/hiero_sdk_python/tokens/token_associate_transaction.py +++ b/src/hiero_sdk_python/tokens/token_associate_transaction.py @@ -107,6 +107,16 @@ def _build_proto_body(self) -> token_associate_pb2.TokenAssociateTransactionBody account=self.account_id._to_proto(), tokens=[token_id._to_proto() for token_id in self.token_ids] ) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenAssociate"): + body = transaction_body.tokenAssociate + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + transaction.token_ids = [TokenId._from_proto(t) for t in body.tokens] + return transaction + @classmethod def _from_proto(cls, body: token_associate_pb2.TokenAssociateTransactionBody) -> TokenAssociateTransaction: """Construct a TokenAssociateTransaction from its protobuf.""" diff --git a/src/hiero_sdk_python/tokens/token_burn_transaction.py b/src/hiero_sdk_python/tokens/token_burn_transaction.py index 362d6e14d..52999c866 100644 --- a/src/hiero_sdk_python/tokens/token_burn_transaction.py +++ b/src/hiero_sdk_python/tokens/token_burn_transaction.py @@ -160,6 +160,17 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.token.burnToken, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenBurn"): + body = transaction_body.tokenBurn + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + transaction.amount = body.amount if body.amount else None + transaction.serials = list(body.serialNumbers) + return transaction + def _from_proto(self, proto: TokenBurnTransactionBody) -> TokenBurnTransaction: """ Deserializes a TokenBurnTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_create_transaction.py b/src/hiero_sdk_python/tokens/token_create_transaction.py index bd0dc8790..035ee43ab 100644 --- a/src/hiero_sdk_python/tokens/token_create_transaction.py +++ b/src/hiero_sdk_python/tokens/token_create_transaction.py @@ -30,6 +30,7 @@ from hiero_sdk_python.tokens.supply_type import SupplyType from hiero_sdk_python.tokens.token_type import TokenType from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.utils.key_utils import key_to_proto @@ -560,3 +561,46 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.createToken, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenCreation"): + body = transaction_body.tokenCreation + transaction._token_params.token_name = body.name + transaction._token_params.token_symbol = body.symbol + transaction._token_params.decimals = body.decimals + transaction._token_params.initial_supply = body.initialSupply + transaction._token_params.token_type = TokenType(body.tokenType) + transaction._token_params.supply_type = SupplyType(body.supplyType) + transaction._token_params.max_supply = body.maxSupply + transaction._token_params.freeze_default = body.freezeDefault + if body.HasField("treasury"): + transaction._token_params.treasury_account_id = AccountId._from_proto(body.treasury) + if body.HasField("expiry"): + transaction._token_params.expiration_time = Timestamp._from_protobuf(body.expiry) + transaction._token_params.auto_renew_period = None + if body.HasField("autoRenewAccount"): + transaction._token_params.auto_renew_account_id = AccountId._from_proto(body.autoRenewAccount) + if body.HasField("autoRenewPeriod"): + transaction._token_params.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + transaction._token_params.memo = body.memo if body.memo else None + transaction._token_params.metadata = body.metadata if body.metadata else None + transaction._token_params.custom_fees = [CustomFee._from_proto(f) for f in body.custom_fees] + if body.HasField("adminKey"): + transaction._keys.admin_key = PublicKey._from_proto(body.adminKey) + if body.HasField("kycKey"): + transaction._keys.kyc_key = PublicKey._from_proto(body.kycKey) + if body.HasField("freezeKey"): + transaction._keys.freeze_key = PublicKey._from_proto(body.freezeKey) + if body.HasField("wipeKey"): + transaction._keys.wipe_key = PublicKey._from_proto(body.wipeKey) + if body.HasField("supplyKey"): + transaction._keys.supply_key = PublicKey._from_proto(body.supplyKey) + if body.HasField("fee_schedule_key"): + transaction._keys.fee_schedule_key = PublicKey._from_proto(body.fee_schedule_key) + if body.HasField("pause_key"): + transaction._keys.pause_key = PublicKey._from_proto(body.pause_key) + if body.HasField("metadata_key"): + transaction._keys.metadata_key = PublicKey._from_proto(body.metadata_key) + return transaction diff --git a/src/hiero_sdk_python/tokens/token_delete_transaction.py b/src/hiero_sdk_python/tokens/token_delete_transaction.py index b03169c36..dc0b202b1 100644 --- a/src/hiero_sdk_python/tokens/token_delete_transaction.py +++ b/src/hiero_sdk_python/tokens/token_delete_transaction.py @@ -94,3 +94,12 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.deleteToken, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenDeletion"): + body = transaction_body.tokenDeletion + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + return transaction diff --git a/src/hiero_sdk_python/tokens/token_dissociate_transaction.py b/src/hiero_sdk_python/tokens/token_dissociate_transaction.py index 1957de2c2..053c9ce22 100644 --- a/src/hiero_sdk_python/tokens/token_dissociate_transaction.py +++ b/src/hiero_sdk_python/tokens/token_dissociate_transaction.py @@ -77,6 +77,16 @@ def _validate_check_sum(self, client) -> None: if token_id is not None: token_id.validate_checksum(client) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenDissociate"): + body = transaction_body.tokenDissociate + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + transaction.token_ids = [TokenId._from_proto(t) for t in body.tokens] + return transaction + @classmethod def _from_proto(cls, proto: token_dissociate_pb2.TokenDissociateTransactionBody) -> TokenDissociateTransaction: """ diff --git a/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py b/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py index 3cab108ef..255c8679f 100644 --- a/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py @@ -90,6 +90,16 @@ def _get_method(self, channel: _Channel) -> _Method: """Gets the gRPC method for this transaction.""" return _Method(transaction_func=channel.token.updateTokenFeeSchedule) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("token_fee_schedule_update"): + body = transaction_body.token_fee_schedule_update + if body.HasField("token_id"): + transaction.token_id = TokenId._from_proto(body.token_id) + transaction.custom_fees = [CustomFee._from_proto(f) for f in body.custom_fees] + return transaction + def __repr__(self): """Readable representation for debugging.""" return f"" diff --git a/src/hiero_sdk_python/tokens/token_freeze_transaction.py b/src/hiero_sdk_python/tokens/token_freeze_transaction.py index 06b3e0fe0..d010a2a4c 100644 --- a/src/hiero_sdk_python/tokens/token_freeze_transaction.py +++ b/src/hiero_sdk_python/tokens/token_freeze_transaction.py @@ -116,3 +116,14 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.freezeTokenAccount, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenFreeze"): + body = transaction_body.tokenFreeze + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + return transaction diff --git a/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py b/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py index 4e71bfa87..ea29362ec 100644 --- a/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py +++ b/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py @@ -127,6 +127,17 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.token.grantKycToTokenAccount, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenGrantKyc"): + body = transaction_body.tokenGrantKyc + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + return transaction + def _from_proto(self, proto: token_grant_kyc_pb2.TokenGrantKycTransactionBody) -> TokenGrantKycTransaction: """ Initializes a new TokenGrantKycTransaction instance from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_mint_transaction.py b/src/hiero_sdk_python/tokens/token_mint_transaction.py index e265c66a4..58d5b84f5 100644 --- a/src/hiero_sdk_python/tokens/token_mint_transaction.py +++ b/src/hiero_sdk_python/tokens/token_mint_transaction.py @@ -160,3 +160,14 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.mintToken, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenMint"): + body = transaction_body.tokenMint + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + transaction.amount = body.amount if body.amount else None + transaction.metadata = list(body.metadata) if body.metadata else None + return transaction diff --git a/src/hiero_sdk_python/tokens/token_pause_transaction.py b/src/hiero_sdk_python/tokens/token_pause_transaction.py index a3b96b351..bd26836d4 100644 --- a/src/hiero_sdk_python/tokens/token_pause_transaction.py +++ b/src/hiero_sdk_python/tokens/token_pause_transaction.py @@ -99,6 +99,15 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.pauseToken, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("token_pause"): + body = transaction_body.token_pause + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + return transaction + def _from_proto(self, proto: token_pause_pb2.TokenPauseTransactionBody) -> TokenPauseTransaction: """ Deserializes a TokenPauseTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_reject_transaction.py b/src/hiero_sdk_python/tokens/token_reject_transaction.py index 863477947..358d0fca7 100644 --- a/src/hiero_sdk_python/tokens/token_reject_transaction.py +++ b/src/hiero_sdk_python/tokens/token_reject_transaction.py @@ -128,6 +128,21 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.token.rejectToken, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenReject"): + body = transaction_body.tokenReject + if body.HasField("owner"): + transaction.owner_id = AccountId._from_proto(body.owner) + transaction.token_ids = [ + TokenId._from_proto(e.fungible_token) for e in body.rejections if e.HasField("fungible_token") + ] + transaction.nft_ids = [ + NftId._from_proto(e.nft) for e in body.rejections if e.HasField("nft") + ] + return transaction + def _from_proto(self, proto: TokenRejectTransactionBody) -> TokenRejectTransaction: """ Deserializes a TokenRejectTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py b/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py index 13b4f0e3c..92b5d5688 100644 --- a/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py +++ b/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py @@ -128,6 +128,17 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.token.revokeKycFromTokenAccount, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenRevokeKyc"): + body = transaction_body.tokenRevokeKyc + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + return transaction + def _from_proto(self, proto: token_revoke_kyc_pb2.TokenRevokeKycTransactionBody) -> TokenRevokeKycTransaction: """ Initializes a new TokenRevokeKycTransaction instance from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py b/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py index e9bf2d624..4a7195d46 100644 --- a/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py +++ b/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py @@ -116,3 +116,14 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.unfreezeTokenAccount, query_func=None) + + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenUnfreeze"): + body = transaction_body.tokenUnfreeze + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + return transaction diff --git a/src/hiero_sdk_python/tokens/token_unpause_transaction.py b/src/hiero_sdk_python/tokens/token_unpause_transaction.py index a30d1eea9..39d03ac08 100644 --- a/src/hiero_sdk_python/tokens/token_unpause_transaction.py +++ b/src/hiero_sdk_python/tokens/token_unpause_transaction.py @@ -71,6 +71,15 @@ def _validate_checksum(self, client: Client) -> None: if self.token_id is not None: self.token_id.validate_checksum(client) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("token_unpause"): + body = transaction_body.token_unpause + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + return transaction + @classmethod def _from_proto(cls, proto: TokenUnpauseTransactionBody) -> TokenUnpauseTransaction: """ diff --git a/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py b/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py index f5a90f6c6..fa77ce6be 100644 --- a/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py @@ -155,6 +155,17 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.token.updateNfts, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("token_update_nfts"): + body = transaction_body.token_update_nfts + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + transaction.serial_numbers = list(body.serial_numbers) + transaction.metadata = body.metadata.value if body.HasField("metadata") else None + return transaction + def _from_proto(self, proto: token_update_nfts_pb2.TokenUpdateNftsTransactionBody) -> TokenUpdateNftsTransaction: """ Deserializes a TokenUpdateNftsTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index 3361858dc..2dbc38389 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -471,6 +471,44 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=channel.token.updateToken, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenUpdate"): + body = transaction_body.tokenUpdate + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + if body.HasField("treasury"): + transaction.treasury_account_id = AccountId._from_proto(body.treasury) + transaction.token_name = body.name if body.name else None + transaction.token_symbol = body.symbol if body.symbol else None + transaction.token_memo = body.memo.value if body.HasField("memo") else None + transaction.metadata = body.metadata.value if body.HasField("metadata") else None + if body.HasField("expiry"): + transaction.expiration_time = Timestamp._from_protobuf(body.expiry) + if body.HasField("autoRenewAccount"): + transaction.auto_renew_account_id = AccountId._from_proto(body.autoRenewAccount) + if body.HasField("autoRenewPeriod"): + transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) + transaction.token_key_verification_mode = TokenKeyValidation._from_proto(body.key_verification_mode) + if body.HasField("adminKey"): + transaction.admin_key = Key.from_proto_key(body.adminKey) + if body.HasField("freezeKey"): + transaction.freeze_key = Key.from_proto_key(body.freezeKey) + if body.HasField("wipeKey"): + transaction.wipe_key = Key.from_proto_key(body.wipeKey) + if body.HasField("supplyKey"): + transaction.supply_key = Key.from_proto_key(body.supplyKey) + if body.HasField("metadata_key"): + transaction.metadata_key = Key.from_proto_key(body.metadata_key) + if body.HasField("pause_key"): + transaction.pause_key = Key.from_proto_key(body.pause_key) + if body.HasField("kycKey"): + transaction.kyc_key = Key.from_proto_key(body.kycKey) + if body.HasField("fee_schedule_key"): + transaction.fee_schedule_key = Key.from_proto_key(body.fee_schedule_key) + return transaction + def _set_keys_to_proto(self, token_update_body: token_update_pb2.TokenUpdateTransactionBody) -> None: """Sets the keys to the protobuf transaction body.""" if self.admin_key: diff --git a/src/hiero_sdk_python/tokens/token_wipe_transaction.py b/src/hiero_sdk_python/tokens/token_wipe_transaction.py index 86899bfc2..377c4727e 100644 --- a/src/hiero_sdk_python/tokens/token_wipe_transaction.py +++ b/src/hiero_sdk_python/tokens/token_wipe_transaction.py @@ -149,6 +149,19 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.token.wipeTokenAccount, query_func=None) + @classmethod + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("tokenWipe"): + body = transaction_body.tokenWipe + if body.HasField("token"): + transaction.token_id = TokenId._from_proto(body.token) + if body.HasField("account"): + transaction.account_id = AccountId._from_proto(body.account) + transaction.amount = body.amount + transaction.serial = list(body.serialNumbers) + return transaction + def _from_proto(self, proto: TokenWipeAccountTransactionBody) -> TokenWipeTransaction: """ Deserializes a TokenWipeAccountTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/transaction/transaction.py b/src/hiero_sdk_python/transaction/transaction.py index 9d2f1ebff..d8ba10055 100644 --- a/src/hiero_sdk_python/transaction/transaction.py +++ b/src/hiero_sdk_python/transaction/transaction.py @@ -474,7 +474,7 @@ def build_base_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body.transactionValidDuration.seconds = self.transaction_valid_duration transaction_body.generateRecord = self.generate_record - transaction_body.memo = self.memo + transaction_body.memo = self.memo if self.memo is not None else "" custom_fee_limits = [custom_fee._to_proto() for custom_fee in self.custom_fee_limits] transaction_body.max_custom_fees.extend(custom_fee_limits) @@ -499,7 +499,7 @@ def build_base_scheduled_body(self) -> SchedulableTransactionBody: else: schedulable_body.transactionFee = int(fee) - schedulable_body.memo = self.memo + schedulable_body.memo = self.memo if self.memo is not None else "" custom_fee_limits = [custom_fee._to_proto() for custom_fee in self.custom_fee_limits] schedulable_body.max_custom_fees.extend(custom_fee_limits) @@ -777,7 +777,7 @@ def _get_transaction_class(transaction_type: str): "fileUpdate": "hiero_sdk_python.file.file_update_transaction.FileUpdateTransaction", "systemDelete": None, # Admin transaction "systemUndelete": None, # Admin transaction - "freeze": None, # Admin transaction + "freeze": "hiero_sdk_python.system.freeze_transaction.FreezeTransaction", "consensusCreateTopic": "hiero_sdk_python.consensus.topic_create_transaction.TopicCreateTransaction", "consensusUpdateTopic": "hiero_sdk_python.consensus.topic_update_transaction.TopicUpdateTransaction", "consensusDeleteTopic": "hiero_sdk_python.consensus.topic_delete_transaction.TopicDeleteTransaction", @@ -794,20 +794,21 @@ def _get_transaction_class(transaction_type: str): "tokenWipe": "hiero_sdk_python.tokens.token_wipe_transaction.TokenWipeTransaction", "tokenAssociate": "hiero_sdk_python.tokens.token_associate_transaction.TokenAssociateTransaction", "tokenDissociate": "hiero_sdk_python.tokens.token_dissociate_transaction.TokenDissociateTransaction", - "tokenPause": "hiero_sdk_python.tokens.token_pause_transaction.TokenPauseTransaction", - "tokenUnpause": "hiero_sdk_python.tokens.token_pause_transaction.TokenUnpauseTransaction", + "token_pause": "hiero_sdk_python.tokens.token_pause_transaction.TokenPauseTransaction", + "token_unpause": "hiero_sdk_python.tokens.token_unpause_transaction.TokenUnpauseTransaction", "scheduleCreate": "hiero_sdk_python.schedule.schedule_create_transaction.ScheduleCreateTransaction", "scheduleDelete": "hiero_sdk_python.schedule.schedule_delete_transaction.ScheduleDeleteTransaction", "scheduleSign": "hiero_sdk_python.schedule.schedule_sign_transaction.ScheduleSignTransaction", - "tokenFeeScheduleUpdate": None, # Not commonly used - "tokenUpdateNfts": "hiero_sdk_python.tokens.token_update_nfts_transaction.TokenUpdateNftsTransaction", + "token_fee_schedule_update": "hiero_sdk_python.tokens.token_fee_schedule_update_transaction.TokenFeeScheduleUpdateTransaction", + "token_update_nfts": "hiero_sdk_python.tokens.token_update_nfts_transaction.TokenUpdateNftsTransaction", "nodeCreate": "hiero_sdk_python.nodes.node_create_transaction.NodeCreateTransaction", "nodeUpdate": "hiero_sdk_python.nodes.node_update_transaction.NodeUpdateTransaction", "nodeDelete": "hiero_sdk_python.nodes.node_delete_transaction.NodeDeleteTransaction", - "utilPrng": "hiero_sdk_python.prng_transaction.PrngTransaction", + "util_prng": "hiero_sdk_python.prng_transaction.PrngTransaction", "tokenReject": "hiero_sdk_python.tokens.token_reject_transaction.TokenRejectTransaction", "tokenAirdrop": "hiero_sdk_python.tokens.token_airdrop_transaction.TokenAirdropTransaction", - "tokenCancelAirdrop": "hiero_sdk_python.tokens.token_cancel_airdrop_transaction.TokenCancelAirdropTransaction", + "tokenCancelAirdrop": "hiero_sdk_python.tokens.token_airdrop_transaction_cancel.TokenCancelAirdropTransaction", + "tokenClaimAirdrop": "hiero_sdk_python.tokens.token_airdrop_claim.TokenClaimAirdropTransaction", "atomic_batch": "hiero_sdk_python.transaction.batch_transaction.BatchTransaction", } diff --git a/tests/unit/account_allowance_approve_transaction_test.py b/tests/unit/account_allowance_approve_transaction_test.py index 1655f2fdb..b71f7f07c 100644 --- a/tests/unit/account_allowance_approve_transaction_test.py +++ b/tests/unit/account_allowance_approve_transaction_test.py @@ -16,6 +16,8 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -349,3 +351,38 @@ def test_zero_amount_allowances(account_allowance_transaction, sample_accounts, assert len(account_allowance_transaction.token_allowances) == 1 assert account_allowance_transaction.hbar_allowances[0].amount == 0 assert account_allowance_transaction.token_allowances[0].amount == 0 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for AccountAllowanceApproveTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + owner = AccountId(0, 0, 200) + spender = AccountId(0, 0, 300) + token_id = TokenId(0, 0, 500) + nft_id = NftId(token_id, 1) + + tx = AccountAllowanceApproveTransaction() + tx.approve_hbar_allowance(owner, spender, Hbar(10)) + tx.approve_token_allowance(token_id, owner, spender, 100) + tx.approve_token_nft_allowance(nft_id, owner, spender) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, AccountAllowanceApproveTransaction) + assert len(reconstructed.hbar_allowances) == 1 + assert reconstructed.hbar_allowances[0].owner_account_id == owner + assert reconstructed.hbar_allowances[0].spender_account_id == spender + assert reconstructed.hbar_allowances[0].amount == Hbar(10).to_tinybars() + assert len(reconstructed.token_allowances) == 1 + assert reconstructed.token_allowances[0].token_id == token_id + assert reconstructed.token_allowances[0].owner_account_id == owner + assert reconstructed.token_allowances[0].spender_account_id == spender + assert reconstructed.token_allowances[0].amount == 100 + assert len(reconstructed.nft_allowances) == 1 + assert reconstructed.nft_allowances[0].token_id == token_id + assert reconstructed.nft_allowances[0].owner_account_id == owner + assert reconstructed.nft_allowances[0].spender_account_id == spender + assert nft_id.serial_number in reconstructed.nft_allowances[0].serial_numbers diff --git a/tests/unit/account_allowance_delete_transaction_test.py b/tests/unit/account_allowance_delete_transaction_test.py index fb619e0bc..f34b68477 100644 --- a/tests/unit/account_allowance_delete_transaction_test.py +++ b/tests/unit/account_allowance_delete_transaction_test.py @@ -17,6 +17,8 @@ from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_allowance import TokenNftAllowance +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -259,3 +261,25 @@ def test_empty_nft_wipe_list(account_allowance_delete_transaction): proto_body = account_allowance_delete_transaction._build_proto_body() assert len(proto_body.nftAllowances) == 0 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for AccountAllowanceDeleteTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + owner = AccountId(0, 0, 200) + token_id = TokenId(0, 0, 100) + nft_id = NftId(token_id, 1) + + tx = AccountAllowanceDeleteTransaction() + tx.delete_all_token_nft_allowances(nft_id, owner) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, AccountAllowanceDeleteTransaction) + assert len(reconstructed.nft_wipe) == 1 + assert reconstructed.nft_wipe[0].token_id == token_id + assert reconstructed.nft_wipe[0].owner_account_id == owner + assert reconstructed.nft_wipe[0].serial_numbers == [1] diff --git a/tests/unit/account_create_transaction_test.py b/tests/unit/account_create_transaction_test.py index d5c2ac0d9..0181bad97 100644 --- a/tests/unit/account_create_transaction_test.py +++ b/tests/unit/account_create_transaction_test.py @@ -25,6 +25,7 @@ TransactionResponse as TransactionResponseProto, ) from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -539,3 +540,23 @@ def test_set_stake_account_id_reset_stake_node_id(): tx.set_staked_account_id(AccountId(0, 0, 1)) assert tx.staked_account_id == AccountId(0, 0, 1) assert tx.staked_node_id is None + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for AccountCreateTransaction.""" + operator_id, node_account_id = mock_account_ids + + from hiero_sdk_python.hbar import Hbar + + tx = AccountCreateTransaction() + tx.set_initial_balance(Hbar(5).to_tinybars()) + tx.set_receiver_signature_required(True) + tx.transaction_id = generate_transaction_id(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, AccountCreateTransaction) + assert reconstructed.initial_balance == Hbar(5).to_tinybars() + assert reconstructed.receiver_signature_required is True diff --git a/tests/unit/account_delete_transaction_test.py b/tests/unit/account_delete_transaction_test.py index f0cc087df..a3ea4e2c8 100644 --- a/tests/unit/account_delete_transaction_test.py +++ b/tests/unit/account_delete_transaction_test.py @@ -13,6 +13,8 @@ from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -247,6 +249,27 @@ def test_build_scheduled_body(delete_params): assert schedulable_body.cryptoDelete.transferAccountID == delete_params["transfer_account_id"]._to_proto() +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for AccountDeleteTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + account_id_sender = AccountId(0, 0, 1) + account_id_recipient = AccountId(0, 0, 2) + + tx = AccountDeleteTransaction( + account_id=account_id_sender, + transfer_account_id=account_id_recipient, + ) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, AccountDeleteTransaction) + assert reconstructed.account_id == account_id_sender + assert reconstructed.transfer_account_id == account_id_recipient + + def test_parameter_validation_none_values(): """Test that parameters can be set to None.""" delete_tx = AccountDeleteTransaction( diff --git a/tests/unit/account_update_transaction_test.py b/tests/unit/account_update_transaction_test.py index b503c6dde..820224c81 100644 --- a/tests/unit/account_update_transaction_test.py +++ b/tests/unit/account_update_transaction_test.py @@ -35,6 +35,8 @@ TransactionResponse as TransactionResponseProto, ) from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -813,3 +815,30 @@ def test_set_keylist_threshold_key(): assert update_body.key.HasField("thresholdKey") assert update_body.key.thresholdKey.threshold == 2 assert len(update_body.key.thresholdKey.keys.keys) == 3 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for AccountUpdateTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + account_id_sender = AccountId(0, 0, 1) + + tx = AccountUpdateTransaction() + tx.set_account_id(account_id_sender) + tx.set_account_memo("updated") + tx.set_staked_node_id(5) + tx.set_decline_staking_reward(False) + tx.set_receiver_signature_required(False) + tx.set_max_automatic_token_associations(10) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, AccountUpdateTransaction) + assert reconstructed.account_id == account_id_sender + assert reconstructed.account_memo == "updated" + assert reconstructed.staked_node_id == 5 + assert reconstructed.decline_staking_reward == False + assert reconstructed.receiver_signature_required == False + assert reconstructed.max_automatic_token_associations == 10 diff --git a/tests/unit/file_append_transaction_test.py b/tests/unit/file_append_transaction_test.py index 4b832c43e..629d5d239 100644 --- a/tests/unit/file_append_transaction_test.py +++ b/tests/unit/file_append_transaction_test.py @@ -376,6 +376,26 @@ def test_file_append_execute_all_returns_receipt_without_validation(file_id): assert receipts[0].status == ResponseCode.INVALID_FILE_ID +@pytest.mark.unit +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for FileAppendTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + test_file_id = FileId(0, 0, 5) + + tx = FileAppendTransaction() + tx.set_file_id(test_file_id) + tx.set_contents(b"appended") + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, FileAppendTransaction) + assert reconstructed.file_id == test_file_id + assert reconstructed.contents == b"appended" + + def test_chunk_transaction_id_nanosecond_overflow(file_id): """Test that multi-chunk transaction IDs handle nanosecond overflow correctly.""" base_seconds = 1770911831 diff --git a/tests/unit/file_create_transaction_test.py b/tests/unit/file_create_transaction_test.py index dbdc1a5e4..db6237a16 100644 --- a/tests/unit/file_create_transaction_test.py +++ b/tests/unit/file_create_transaction_test.py @@ -26,6 +26,8 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -260,3 +262,29 @@ def test_file_create_transaction_from_proto(): assert from_proto.contents == b"" assert from_proto.file_memo == "" assert from_proto.keys == [] + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for FileCreateTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + + key = PrivateKey.generate().public_key() + expiration = Timestamp(seconds=9999999999, nanos=0) + + tx = FileCreateTransaction() + tx.set_contents(b"file contents") + tx.set_file_memo("my file") + tx.set_expiration_time(expiration) + tx.set_keys([key]) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, FileCreateTransaction) + assert reconstructed.contents == b"file contents" + assert reconstructed.file_memo == "my file" + assert reconstructed.expiration_time.seconds == expiration.seconds + assert len(reconstructed.keys) == 1 + assert reconstructed.keys[0].to_bytes_raw() == key.to_bytes_raw() diff --git a/tests/unit/file_delete_transaction_test.py b/tests/unit/file_delete_transaction_test.py index 2aa95840f..201f20b15 100644 --- a/tests/unit/file_delete_transaction_test.py +++ b/tests/unit/file_delete_transaction_test.py @@ -12,6 +12,8 @@ from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -131,3 +133,18 @@ def test_get_method(): assert method.query is None assert method.transaction == mock_file_stub.deleteFile + + +def test_from_bytes(mock_account_ids, file_id): + """Test round-trip via Transaction.from_bytes() for FileDeleteTransaction.""" + account_id, _, node_account_id, _, _ = mock_account_ids + + tx = FileDeleteTransaction(file_id=file_id) + tx.transaction_id = TransactionId.generate(account_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, FileDeleteTransaction) + assert reconstructed.file_id == file_id diff --git a/tests/unit/file_update_transaction_test.py b/tests/unit/file_update_transaction_test.py index cf0a71840..766ab2f2a 100644 --- a/tests/unit/file_update_transaction_test.py +++ b/tests/unit/file_update_transaction_test.py @@ -32,6 +32,8 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -358,3 +360,31 @@ def test_encode_contents_string(): # Test None handling encoded = file_tx._encode_contents(None) assert encoded is None + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for FileUpdateTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + test_file_id = FileId(0, 0, 5) + key = PrivateKey.generate().public_key() + expiration = Timestamp(seconds=9999999999, nanos=0) + + tx = FileUpdateTransaction() + tx.set_file_id(test_file_id) + tx.set_contents(b"updated") + tx.set_file_memo("updated memo") + tx.set_expiration_time(expiration) + tx.set_keys([key]) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, FileUpdateTransaction) + assert reconstructed.file_id == test_file_id + assert reconstructed.contents == b"updated" + assert reconstructed.file_memo == "updated memo" + assert reconstructed.expiration_time.seconds == expiration.seconds + assert len(reconstructed.keys) == 1 + assert reconstructed.keys[0].to_bytes_raw() == key.to_bytes_raw() diff --git a/tests/unit/freeze_transaction_test.py b/tests/unit/freeze_transaction_test.py index 026545b9f..89a719986 100644 --- a/tests/unit/freeze_transaction_test.py +++ b/tests/unit/freeze_transaction_test.py @@ -18,6 +18,8 @@ from hiero_sdk_python.system.freeze_transaction import FreezeTransaction from hiero_sdk_python.system.freeze_type import FreezeType from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -289,3 +291,26 @@ def test_build_proto_body_with_none_fields(): assert not proto_body.HasField("update_file") assert proto_body.file_hash == b"" assert proto_body.freeze_type == proto_FreezeType.UNKNOWN_FREEZE_TYPE + + +def test_from_bytes(mock_account_ids, freeze_params): + """Test round-trip via _from_protobuf for FreezeTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + + tx = FreezeTransaction( + start_time=freeze_params["start_time"], + file_id=freeze_params["file_id"], + file_hash=freeze_params["file_hash"], + freeze_type=freeze_params["freeze_type"], + ) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, FreezeTransaction) + assert reconstructed.freeze_type == freeze_params["freeze_type"] + assert reconstructed.start_time == freeze_params["start_time"] + assert reconstructed.file_id == freeze_params["file_id"] + assert reconstructed.file_hash == freeze_params["file_hash"] diff --git a/tests/unit/schedule_create_transaction_test.py b/tests/unit/schedule_create_transaction_test.py index 2354acd49..d94da76a8 100644 --- a/tests/unit/schedule_create_transaction_test.py +++ b/tests/unit/schedule_create_transaction_test.py @@ -15,6 +15,9 @@ ScheduleCreateTransaction, ) from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction @@ -265,3 +268,29 @@ def test_to_proto(mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for ScheduleCreateTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + inner_tx = TokenGrantKycTransaction() + inner_tx.set_token_id(token_id_1) + inner_tx.set_account_id(account_id_sender) + schedulable_body = inner_tx.build_scheduled_body() + + tx = ScheduleCreateTransaction() + tx.set_schedule_memo("test memo") + tx.set_payer_account_id(account_id_sender) + tx._set_schedulable_body(schedulable_body) + tx.transaction_id = TransactionId.generate(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, ScheduleCreateTransaction) + assert reconstructed.schedule_memo == "test memo" + assert reconstructed.payer_account_id == account_id_sender + assert reconstructed.schedulable_body == schedulable_body + assert reconstructed.schedulable_body.HasField("tokenGrantKyc") diff --git a/tests/unit/schedule_delete_transaction_test.py b/tests/unit/schedule_delete_transaction_test.py index 65c4e9383..1689170aa 100644 --- a/tests/unit/schedule_delete_transaction_test.py +++ b/tests/unit/schedule_delete_transaction_test.py @@ -24,6 +24,8 @@ ScheduleDeleteTransaction, ) from hiero_sdk_python.schedule.schedule_id import ScheduleId +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -249,3 +251,19 @@ def test_schedule_delete_transaction_can_execute(): receipt = transaction.execute(client) assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for ScheduleDeleteTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + schedule_id = ScheduleId(0, 0, 99) + + tx = ScheduleDeleteTransaction(schedule_id=schedule_id) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, ScheduleDeleteTransaction) + assert reconstructed.schedule_id == schedule_id diff --git a/tests/unit/schedule_sign_transaction_test.py b/tests/unit/schedule_sign_transaction_test.py index ce3a67fd5..177887cc4 100644 --- a/tests/unit/schedule_sign_transaction_test.py +++ b/tests/unit/schedule_sign_transaction_test.py @@ -10,6 +10,8 @@ from hiero_sdk_python.schedule.schedule_id import ScheduleId from hiero_sdk_python.schedule.schedule_sign_transaction import ScheduleSignTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -192,3 +194,19 @@ def test_schedule_id_property_access(): # Test getting via property retrieved_schedule_id = schedule_sign_tx.schedule_id assert retrieved_schedule_id == schedule_id + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for ScheduleSignTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + schedule_id = ScheduleId(0, 0, 42) + + tx = ScheduleSignTransaction(schedule_id=schedule_id) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, ScheduleSignTransaction) + assert reconstructed.schedule_id == schedule_id diff --git a/tests/unit/token_airdrop_transaction_test.py b/tests/unit/token_airdrop_transaction_test.py index db2ce86f4..73c77b82b 100644 --- a/tests/unit/token_airdrop_transaction_test.py +++ b/tests/unit/token_airdrop_transaction_test.py @@ -11,6 +11,8 @@ from hiero_sdk_python.hapi.services.token_airdrop_pb2 import TokenAirdropTransactionBody from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -402,3 +404,24 @@ def test_from_proto_without_token_transfer(mock_account_ids): assert nft_transfer[0].is_approved == True assert not airdrop_tx.token_transfers + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for TokenAirdropTransaction.""" + sender, receiver, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenAirdropTransaction() + tx.add_token_transfer(token_id=token_id_1, account_id=sender, amount=-1) + tx.add_token_transfer(token_id=token_id_1, account_id=receiver, amount=1) + tx.transaction_id = TransactionId.generate(sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenAirdropTransaction) + assert len(reconstructed.token_transfers[token_id_1]) == 2 + assert reconstructed.token_transfers[token_id_1][0].account_id == sender + assert reconstructed.token_transfers[token_id_1][0].amount == -1 + assert reconstructed.token_transfers[token_id_1][1].account_id == receiver + assert reconstructed.token_transfers[token_id_1][1].amount == 1 diff --git a/tests/unit/token_associate_transaction_test.py b/tests/unit/token_associate_transaction_test.py index 9c6bd5f3e..8a8cdd491 100644 --- a/tests/unit/token_associate_transaction_test.py +++ b/tests/unit/token_associate_transaction_test.py @@ -10,6 +10,7 @@ ) from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -217,3 +218,24 @@ def test_from_proto_builds_transaction(mock_account_ids): assert len(tx.token_ids) == 2 assert tx.token_ids[0] == token_id_1 assert tx.token_ids[1] == token_id_2 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for TokenAssociateTransaction.""" + account_id_sender, _, node_account_id, token_id_1, token_id_2 = mock_account_ids + + tx = TokenAssociateTransaction() + tx.set_account_id(account_id_sender) + tx.add_token_id(token_id_1) + tx.add_token_id(token_id_2) + tx.transaction_id = generate_transaction_id(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenAssociateTransaction) + assert reconstructed.account_id == account_id_sender + assert len(reconstructed.token_ids) == 2 + assert reconstructed.token_ids[0] == token_id_1 + assert reconstructed.token_ids[1] == token_id_2 diff --git a/tests/unit/token_burn_transaction_test.py b/tests/unit/token_burn_transaction_test.py index 97b58be9e..504214e50 100644 --- a/tests/unit/token_burn_transaction_test.py +++ b/tests/unit/token_burn_transaction_test.py @@ -12,6 +12,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_burn_transaction import TokenBurnTransaction from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -182,3 +184,38 @@ def test_build_scheduled_body_nft(mock_account_ids): assert schedulable_body.tokenBurn.token == token_id._to_proto() assert schedulable_body.tokenBurn.amount == 0 assert schedulable_body.tokenBurn.serialNumbers == serials + + +def test_from_bytes_fungible(mock_account_ids): + """Test round-trip via _from_protobuf for fungible TokenBurnTransaction.""" + operator_id, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenBurnTransaction(token_id=token_id_1, amount=100) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenBurnTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.amount == 100 + assert reconstructed.serials == [] + + +def test_from_bytes_nft(mock_account_ids): + """Test round-trip via _from_protobuf for NFT TokenBurnTransaction.""" + operator_id, _, node_account_id, token_id_1, _ = mock_account_ids + serials = [1, 2, 3] + + tx = TokenBurnTransaction(token_id=token_id_1, serials=serials) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenBurnTransaction) + assert reconstructed.token_id == token_id_1 + assert list(reconstructed.serials) == serials + assert reconstructed.amount is None diff --git a/tests/unit/token_delete_transaction_test.py b/tests/unit/token_delete_transaction_test.py index bb961eb4b..4addf24a8 100644 --- a/tests/unit/token_delete_transaction_test.py +++ b/tests/unit/token_delete_transaction_test.py @@ -9,6 +9,7 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -113,3 +114,19 @@ def test_build_scheduled_body(mock_account_ids): assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenDeletion") assert schedulable_body.tokenDeletion.token == token_id._to_proto() + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for TokenDeleteTransaction.""" + account_id, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenDeleteTransaction() + tx.set_token_id(token_id_1) + tx.transaction_id = generate_transaction_id(account_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenDeleteTransaction) + assert reconstructed.token_id == token_id_1 diff --git a/tests/unit/token_dissociate_transaction_test.py b/tests/unit/token_dissociate_transaction_test.py index 615c7d503..8732a18c4 100644 --- a/tests/unit/token_dissociate_transaction_test.py +++ b/tests/unit/token_dissociate_transaction_test.py @@ -9,6 +9,7 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -180,6 +181,27 @@ def test_from_proto(mock_account_ids): assert reconstructed_tx.token_ids[1] == token_id_2 +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenDissociateTransaction.""" + account_id_sender, _, node_account_id, token_id_1, token_id_2 = mock_account_ids + + tx = TokenDissociateTransaction() + tx.set_account_id(account_id_sender) + tx.add_token_id(token_id_1) + tx.add_token_id(token_id_2) + tx.transaction_id = generate_transaction_id(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenDissociateTransaction) + assert reconstructed.account_id == account_id_sender + assert len(reconstructed.token_ids) == 2 + assert reconstructed.token_ids[0] == token_id_1 + assert reconstructed.token_ids[1] == token_id_2 + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token dissociate transaction.""" account_id, _, _, token_id_1, token_id_2 = mock_account_ids diff --git a/tests/unit/token_fee_schedule_update_transaction_test.py b/tests/unit/token_fee_schedule_update_transaction_test.py index 8a62285c6..7c3373317 100644 --- a/tests/unit/token_fee_schedule_update_transaction_test.py +++ b/tests/unit/token_fee_schedule_update_transaction_test.py @@ -7,6 +7,8 @@ from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import ( TokenFeeScheduleUpdateTransaction, ) +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId pytestmark = pytest.mark.unit @@ -102,3 +104,30 @@ def test_build_transaction_body_with_empty_custom_fees(mock_account_ids): assert transaction_body.HasField("token_fee_schedule_update") assert transaction_body.token_fee_schedule_update.token_id == token_id._to_proto() assert len(transaction_body.token_fee_schedule_update.custom_fees) == 0 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenFeeScheduleUpdateTransaction.""" + account_id_sender, _, node_account_id, token_id_1, token_id_2 = mock_account_ids + + fee = CustomFixedFee( + amount=100, + denominating_token_id=token_id_2, + fee_collector_account_id=account_id_sender, + ) + + tx = TokenFeeScheduleUpdateTransaction(token_id=token_id_1, custom_fees=[fee]) + tx.transaction_id = TransactionId.generate(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenFeeScheduleUpdateTransaction) + assert reconstructed.token_id == token_id_1 + assert len(reconstructed.custom_fees) == 1 + restored_fee = reconstructed.custom_fees[0] + assert isinstance(restored_fee, CustomFixedFee) + assert restored_fee.amount == 100 + assert restored_fee.denominating_token_id == token_id_2 + assert restored_fee.fee_collector_account_id == account_id_sender diff --git a/tests/unit/token_freeze_transaction_test.py b/tests/unit/token_freeze_transaction_test.py index e19f15dd8..cd1ebd796 100644 --- a/tests/unit/token_freeze_transaction_test.py +++ b/tests/unit/token_freeze_transaction_test.py @@ -9,6 +9,7 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -136,3 +137,21 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.HasField("tokenFreeze") assert schedulable_body.tokenFreeze.token == token_id._to_proto() assert schedulable_body.tokenFreeze.account == freeze_id._to_proto() + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenFreezeTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenFreezeTransaction() + tx.set_token_id(token_id_1) + tx.set_account_id(account_id_sender) + tx.transaction_id = generate_transaction_id(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenFreezeTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender diff --git a/tests/unit/token_grant_kyc_transaction_test.py b/tests/unit/token_grant_kyc_transaction_test.py index 46d5cd67e..ef645865c 100644 --- a/tests/unit/token_grant_kyc_transaction_test.py +++ b/tests/unit/token_grant_kyc_transaction_test.py @@ -13,6 +13,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -145,6 +147,24 @@ def test_grant_kyc_transaction_from_proto(mock_account_ids): assert from_proto.account_id == AccountId() +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenGrantKycTransaction.""" + account_id, _, node_account_id, token_id, _ = mock_account_ids + + tx = TokenGrantKycTransaction() + tx.set_token_id(token_id) + tx.set_account_id(account_id) + tx.transaction_id = TransactionId.generate(account_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenGrantKycTransaction) + assert reconstructed.token_id == token_id + assert reconstructed.account_id == account_id + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token grant KYC transaction.""" account_id, _, _, token_id, _ = mock_account_ids diff --git a/tests/unit/token_mint_transaction_test.py b/tests/unit/token_mint_transaction_test.py index b617dcfd7..7a778eef4 100644 --- a/tests/unit/token_mint_transaction_test.py +++ b/tests/unit/token_mint_transaction_test.py @@ -11,6 +11,7 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -299,3 +300,40 @@ def test_build_scheduled_body_nft(mock_account_ids, metadata): assert schedulable_body.tokenMint.token == token_id._to_proto() assert schedulable_body.tokenMint.amount == 0 assert schedulable_body.tokenMint.metadata == metadata + + +def test_from_bytes_fungible(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for fungible TokenMintTransaction.""" + payer_account, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenMintTransaction() + tx.set_token_id(token_id_1) + tx.set_amount(500) + tx.transaction_id = generate_transaction_id(payer_account) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenMintTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.amount == 500 + + +def test_from_bytes_nft(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for NFT TokenMintTransaction.""" + payer_account, _, node_account_id, token_id_1, _ = mock_account_ids + nft_metadata = [b"meta1", b"meta2"] + + tx = TokenMintTransaction() + tx.set_token_id(token_id_1) + tx.set_metadata(nft_metadata) + tx.transaction_id = generate_transaction_id(payer_account) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenMintTransaction) + assert reconstructed.token_id == token_id_1 + assert list(reconstructed.metadata) == nft_metadata diff --git a/tests/unit/token_pause_transaction_test.py b/tests/unit/token_pause_transaction_test.py index 2b804c9d8..120ad63e8 100644 --- a/tests/unit/token_pause_transaction_test.py +++ b/tests/unit/token_pause_transaction_test.py @@ -18,6 +18,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -161,3 +163,19 @@ def test_build_scheduled_body(token_id): assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("token_pause") assert schedulable_body.token_pause.token == token_id._to_proto() + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenPauseTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenPauseTransaction() + tx.set_token_id(token_id_1) + tx.transaction_id = TransactionId.generate(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenPauseTransaction) + assert reconstructed.token_id == token_id_1 diff --git a/tests/unit/token_revoke_kyc_transaction_test.py b/tests/unit/token_revoke_kyc_transaction_test.py index b82b768a2..750ba7c2b 100644 --- a/tests/unit/token_revoke_kyc_transaction_test.py +++ b/tests/unit/token_revoke_kyc_transaction_test.py @@ -13,6 +13,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -138,6 +140,26 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenRevokeKyc.account == account_id._to_proto() +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for TokenRevokeKycTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenRevokeKycTransaction() + tx.set_token_id(token_id_1) + tx.set_account_id(account_id_sender) + tx.transaction_id = TransactionId.generate(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenRevokeKycTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + assert reconstructed.transaction_id == tx.transaction_id + assert reconstructed.node_account_id == node_account_id + + def test_revoke_kyc_transaction_from_proto(mock_account_ids): """Test that a revoke KYC transaction can be created from a protobuf object.""" account_id, _, _, token_id, _ = mock_account_ids diff --git a/tests/unit/token_unfreeze_transaction_test.py b/tests/unit/token_unfreeze_transaction_test.py index 2d0232acf..8ad077eee 100644 --- a/tests/unit/token_unfreeze_transaction_test.py +++ b/tests/unit/token_unfreeze_transaction_test.py @@ -9,6 +9,7 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.token_unfreeze_transaction import TokenUnfreezeTransaction +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -112,6 +113,24 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenUnfreeze.account == freeze_id._to_proto() +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenUnfreezeTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenUnfreezeTransaction() + tx.set_token_id(token_id_1) + tx.set_account_id(account_id_sender) + tx.transaction_id = generate_transaction_id(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenUnfreezeTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + + def test_to_proto(mock_account_ids, mock_client): """Test converting the token unfreeze transaction to protobuf format after signing.""" account_id, freeze_id, _, token_id, _ = mock_account_ids diff --git a/tests/unit/token_unpause_transaction_test.py b/tests/unit/token_unpause_transaction_test.py index d6c57af7d..51e3e6c2e 100644 --- a/tests/unit/token_unpause_transaction_test.py +++ b/tests/unit/token_unpause_transaction_test.py @@ -12,6 +12,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_unpause_transaction import TokenUnpauseTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -169,6 +171,22 @@ def test_from_proto(mock_account_ids): assert unpause_tx.token_id.num == token_id.num +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TokenUnpauseTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenUnpauseTransaction() + tx.set_token_id(token_id_1) + tx.transaction_id = TransactionId.generate(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenUnpauseTransaction) + assert reconstructed.token_id == token_id_1 + + def test_upause_transaction_can_execute(mock_account_ids): """Test that a token upause transaction can be executed successfully.""" _, _, _, token_id, _ = mock_account_ids diff --git a/tests/unit/token_update_nfts_transaction_test.py b/tests/unit/token_update_nfts_transaction_test.py index 9bded64df..325705bc4 100644 --- a/tests/unit/token_update_nfts_transaction_test.py +++ b/tests/unit/token_update_nfts_transaction_test.py @@ -13,6 +13,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_update_nfts_transaction import TokenUpdateNftsTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -212,3 +214,25 @@ def test_update_nfts_transaction_from_proto(mock_account_ids): assert isinstance(empty_tx.token_id, TokenId) assert empty_tx.serial_numbers == [] assert empty_tx.metadata is empty_proto.metadata.value # Should be None or empty + + +def test_from_bytes(mock_account_ids): + """Test round-trip via _from_protobuf for TokenUpdateNftsTransaction.""" + operator_id, _, node_account_id, token_id_1, _ = mock_account_ids + serial_numbers = [1, 2, 3] + metadata = b"newmeta" + + tx = TokenUpdateNftsTransaction() + tx.set_token_id(token_id_1) + tx.set_serial_numbers(serial_numbers) + tx.set_metadata(metadata) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenUpdateNftsTransaction) + assert reconstructed.token_id == token_id_1 + assert list(reconstructed.serial_numbers) == serial_numbers + assert reconstructed.metadata == metadata diff --git a/tests/unit/token_update_transaction_test.py b/tests/unit/token_update_transaction_test.py index efb395bcd..1812ad466 100644 --- a/tests/unit/token_update_transaction_test.py +++ b/tests/unit/token_update_transaction_test.py @@ -17,6 +17,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateKeys, TokenUpdateParams, TokenUpdateTransaction +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -466,3 +468,38 @@ def test_build_transaction_body_with_keys(mock_account_ids, key_type, use_privat assert transaction_body.tokenUpdate.name == new_token_data["name"] verify_key_in_proto(transaction_body.tokenUpdate.adminKey, expected_admin_public, key_type) verify_key_in_proto(transaction_body.tokenUpdate.freezeKey, expected_freeze_public, key_type) + + +def test_from_bytes(mock_account_ids, new_token_data): + """Test round-trip via _from_protobuf for TokenUpdateTransaction.""" + operator_id, _, node_account_id, token_id_1, _ = mock_account_ids + key = PrivateKey.generate().public_key() + + tx = TokenUpdateTransaction() + tx.set_token_id(token_id_1) + tx.set_token_name("NewName") + tx.set_token_symbol("NNS") + tx.set_token_memo(new_token_data["memo"]) + tx.set_metadata(new_token_data["metadata"]) + tx.set_treasury_account_id(operator_id) + tx.set_auto_renew_account_id(operator_id) + tx.set_auto_renew_period(new_token_data["auto_renew_period"]) + tx.set_expiration_time(new_token_data["expiration_time"]) + tx.set_admin_key(key) + tx.transaction_id = TransactionId.generate(operator_id) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenUpdateTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.token_name == "NewName" + assert reconstructed.token_symbol == "NNS" + assert reconstructed.token_memo == new_token_data["memo"] + assert reconstructed.metadata == new_token_data["metadata"] + assert reconstructed.treasury_account_id == operator_id + assert reconstructed.auto_renew_account_id == operator_id + assert reconstructed.auto_renew_period.seconds == new_token_data["auto_renew_period"].seconds + assert reconstructed.expiration_time.seconds == new_token_data["expiration_time"].seconds + assert reconstructed.admin_key.to_bytes_raw() == key.to_bytes_raw() diff --git a/tests/unit/token_wipe_transaction_test.py b/tests/unit/token_wipe_transaction_test.py index d5cbb85ee..85e8f8cea 100644 --- a/tests/unit/token_wipe_transaction_test.py +++ b/tests/unit/token_wipe_transaction_test.py @@ -12,6 +12,7 @@ from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_wipe_transaction import TokenWipeTransaction +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -194,3 +195,46 @@ def test_build_scheduled_body_with_serial_numbers(mock_account_ids): assert schedulable_body.tokenWipe.token == token_id._to_proto() assert schedulable_body.tokenWipe.account == wipe_account_id._to_proto() assert schedulable_body.tokenWipe.serialNumbers == serial_numbers + + +def test_from_bytes_fungible(mock_account_ids): + """Test round-trip via _from_protobuf for fungible TokenWipeTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenWipeTransaction() + tx.set_token_id(token_id_1) + tx.set_account_id(account_id_sender) + tx.set_amount(50) + tx.transaction_id = generate_transaction_id(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenWipeTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + assert reconstructed.amount == 50 + assert reconstructed.serial == [] + + +def test_from_bytes_nft(mock_account_ids): + """Test round-trip via _from_protobuf for NFT TokenWipeTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + serial = [4, 5] + + tx = TokenWipeTransaction() + tx.set_token_id(token_id_1) + tx.set_account_id(account_id_sender) + tx.set_serial(serial) + tx.transaction_id = generate_transaction_id(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TokenWipeTransaction) + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + assert list(reconstructed.serial) == serial + assert reconstructed.amount == 0 diff --git a/tests/unit/topic_create_transaction_test.py b/tests/unit/topic_create_transaction_test.py index 4c92ce28e..3d994b358 100644 --- a/tests/unit/topic_create_transaction_test.py +++ b/tests/unit/topic_create_transaction_test.py @@ -9,6 +9,7 @@ from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.Duration import Duration from hiero_sdk_python.hapi.services import ( basic_types_pb2, response_header_pb2, @@ -23,6 +24,8 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -574,3 +577,31 @@ def test_mixed_key_types_in_constructor(mock_account_ids): assert transaction_body.consensusCreateTopic.submitKey.ed25519 == ed25519_public.to_bytes_raw() assert transaction_body.consensusCreateTopic.fee_schedule_key.HasField("ECDSA_secp256k1") assert len(transaction_body.consensusCreateTopic.fee_exempt_key_list) == 2 + + +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for TopicCreateTransaction.""" + account_id_sender, _, node_account_id, _, _ = mock_account_ids + + admin_key = PrivateKey.generate_ed25519().public_key() + submit_key = PrivateKey.generate_ed25519().public_key() + auto_renew_period = Duration(8000000) + + tx = TopicCreateTransaction() + tx.set_memo("hello") + tx.set_auto_renew_account(account_id_sender) + tx.set_admin_key(admin_key) + tx.set_submit_key(submit_key) + tx.set_auto_renew_period(auto_renew_period) + tx.transaction_id = TransactionId.generate(account_id_sender) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TopicCreateTransaction) + assert reconstructed.memo == "hello" + assert reconstructed.auto_renew_account == account_id_sender + assert reconstructed.admin_key == admin_key + assert reconstructed.submit_key == submit_key + assert reconstructed.auto_renew_period == auto_renew_period diff --git a/tests/unit/topic_delete_transaction_test.py b/tests/unit/topic_delete_transaction_test.py index 925c212fe..c2de5f750 100644 --- a/tests/unit/topic_delete_transaction_test.py +++ b/tests/unit/topic_delete_transaction_test.py @@ -17,6 +17,8 @@ SchedulableTransactionBody, ) from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -113,3 +115,18 @@ def test_execute_topic_delete_transaction(topic_id): # Verify the receipt contains the expected values assert receipt.status == ResponseCode.SUCCESS + + +def test_from_bytes(mock_account_ids, topic_id): + """Test round-trip via Transaction.from_bytes() for TopicDeleteTransaction.""" + _, _, node_account_id, _, _ = mock_account_ids + + tx = TopicDeleteTransaction(topic_id=topic_id) + tx.transaction_id = TransactionId.generate(AccountId(0, 0, 2)) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TopicDeleteTransaction) + assert reconstructed.topic_id == topic_id diff --git a/tests/unit/topic_message_submit_transaction_test.py b/tests/unit/topic_message_submit_transaction_test.py index b4efcaf38..98dbbbdbb 100644 --- a/tests/unit/topic_message_submit_transaction_test.py +++ b/tests/unit/topic_message_submit_transaction_test.py @@ -22,6 +22,7 @@ ) from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_response import TransactionResponse @@ -485,6 +486,22 @@ def test_topic_submit_execute_raises_error_with_validation(topic_id): assert e.value.status == ResponseCode.INVALID_SIGNATURE +def test_from_bytes(topic_id): + """Test round-trip via _from_protobuf for TopicMessageSubmitTransaction.""" + tx = TopicMessageSubmitTransaction() + tx.set_topic_id(topic_id) + tx.set_message("hello world") + tx.transaction_id = TransactionId.generate(AccountId(0, 0, 1)) + tx.node_account_id = AccountId(0, 0, 3) + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TopicMessageSubmitTransaction) + assert reconstructed.topic_id == topic_id + assert reconstructed.message == "hello world" + + def test_topic_submit_execute_returns_failed_receipt_by_default(topic_id): """Test execute returns the failing receipt by default when validation is disabled.""" message = "Hello Hiero" diff --git a/tests/unit/topic_update_transaction_test.py b/tests/unit/topic_update_transaction_test.py index 24b1d7dd6..9abf58b2e 100644 --- a/tests/unit/topic_update_transaction_test.py +++ b/tests/unit/topic_update_transaction_test.py @@ -22,6 +22,8 @@ ) from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers @@ -316,6 +318,37 @@ def test_execute_topic_update_transaction(topic_id): assert receipt.status == ResponseCode.SUCCESS +def test_from_bytes(mock_account_ids, topic_id): + """Test round-trip via Transaction.from_bytes() for TopicUpdateTransaction.""" + _, _, node_account_id, _, _ = mock_account_ids + + admin_key = PrivateKey.generate().public_key() + submit_key = PrivateKey.generate().public_key() + auto_renew_account = AccountId(0, 0, 5678) + auto_renew_period = Duration(8000000) + + tx = TopicUpdateTransaction() + tx.set_topic_id(topic_id) + tx.set_memo("Updated Memo") + tx.set_admin_key(admin_key) + tx.set_submit_key(submit_key) + tx.set_auto_renew_period(auto_renew_period) + tx.set_auto_renew_account(auto_renew_account) + tx.transaction_id = TransactionId.generate(AccountId(0, 0, 2)) + tx.node_account_id = node_account_id + tx.freeze() + + reconstructed = Transaction.from_bytes(tx.to_bytes()) + + assert isinstance(reconstructed, TopicUpdateTransaction) + assert reconstructed.topic_id == topic_id + assert reconstructed.memo == "Updated Memo" + assert reconstructed.admin_key == admin_key + assert reconstructed.submit_key == submit_key + assert reconstructed.auto_renew_period == auto_renew_period + assert reconstructed.auto_renew_account == auto_renew_account + + # This test uses fixture topic_id as parameter def test_topic_update_transaction_with_all_fields(topic_id): """Test updating a topic with all available fields."""