From de3f02f2c20c65456746d7015ab952f0fff014ee Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 14:58:57 +0300 Subject: [PATCH 01/28] fix: correct _get_transaction_class map keys and missing entries WhichOneof("data") returns snake_case for newer proto fields, so fix camelCase keys for token_pause, token_unpause, token_fee_schedule_update, and token_update_nfts. Add missing entries for tokenClaimAirdrop, token_fee_schedule_update, and freeze. Fix incorrect module paths. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/transaction/transaction.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/hiero_sdk_python/transaction/transaction.py b/src/hiero_sdk_python/transaction/transaction.py index 7b1369de9..1e31275b9 100644 --- a/src/hiero_sdk_python/transaction/transaction.py +++ b/src/hiero_sdk_python/transaction/transaction.py @@ -772,7 +772,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", @@ -789,20 +789,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", "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", } From d15785db5d07f9ce1fd4d024cf3cc3f076dffce3 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 14:59:08 +0300 Subject: [PATCH 02/28] feat: implement _from_protobuf for account transactions Add _from_protobuf classmethod to AccountCreateTransaction, AccountUpdateTransaction, AccountDeleteTransaction, AccountAllowanceApproveTransaction, and AccountAllowanceDeleteTransaction so that Transaction.from_bytes() correctly restores all fields. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- .../account_allowance_approve_transaction.py | 10 ++++++ .../account_allowance_delete_transaction.py | 8 +++++ .../account/account_create_transaction.py | 24 ++++++++++++++ .../account/account_delete_transaction.py | 11 +++++++ .../account/account_update_transaction.py | 31 +++++++++++++++++++ 5 files changed, 84 insertions(+) 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..467aae9c0 100644 --- a/src/hiero_sdk_python/account/account_create_transaction.py +++ b/src/hiero_sdk_python/account/account_create_transaction.py @@ -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): + from hiero_sdk_python.crypto.public_key import PublicKey + + 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 = PublicKey._from_proto(body.key) + transaction.initial_balance = body.initialBalance + transaction.receiver_signature_required = body.receiverSigRequired + 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..03bb563a5 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -358,3 +358,34 @@ 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): + from hiero_sdk_python.crypto.public_key import PublicKey + from hiero_sdk_python.hapi.services.crypto_update_pb2 import CryptoUpdateTransactionBody + + 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 = PublicKey._from_proto(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 From 216fce8ccb9877442fca6e6b7f56671b9e996ed2 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 14:59:18 +0300 Subject: [PATCH 03/28] feat: implement _from_protobuf for token transactions Add _from_protobuf classmethod to all token transaction types: TokenCreateTransaction, TokenUpdateTransaction, TokenMintTransaction, TokenBurnTransaction, TokenDeleteTransaction, TokenFreezeTransaction, TokenUnfreezeTransaction, TokenGrantKycTransaction, TokenRevokeKycTransaction, TokenWipeTransaction, TokenPauseTransaction, TokenUnpauseTransaction, TokenAssociateTransaction, TokenDissociateTransaction, TokenFeeScheduleUpdateTransaction, TokenAirdropTransaction, TokenClaimAirdropTransaction, TokenCancelAirdropTransaction, TokenRejectTransaction, and TokenUpdateNftsTransaction. Also add HasField guards on all inner message-type fields to avoid silent default-value population. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- .../tokens/token_airdrop_claim.py | 10 +++++ .../tokens/token_airdrop_transaction.py | 31 +++++++++++++ .../token_airdrop_transaction_cancel.py | 10 +++++ .../tokens/token_associate_transaction.py | 10 +++++ .../tokens/token_burn_transaction.py | 11 +++++ .../tokens/token_create_transaction.py | 45 +++++++++++++++++++ .../tokens/token_delete_transaction.py | 9 ++++ .../tokens/token_dissociate_transaction.py | 10 +++++ .../token_fee_schedule_update_transaction.py | 10 +++++ .../tokens/token_freeze_transaction.py | 11 +++++ .../tokens/token_grant_kyc_transaction.py | 11 +++++ .../tokens/token_mint_transaction.py | 11 +++++ .../tokens/token_pause_transaction.py | 9 ++++ .../tokens/token_reject_transaction.py | 15 +++++++ .../tokens/token_revoke_kyc_transaction.py | 11 +++++ .../tokens/token_unfreeze_transaction.py | 11 +++++ .../tokens/token_unpause_transaction.py | 9 ++++ .../tokens/token_update_nfts_transaction.py | 11 +++++ .../tokens/token_update_transaction.py | 40 +++++++++++++++++ .../tokens/token_wipe_transaction.py | 13 ++++++ 20 files changed, 298 insertions(+) diff --git a/src/hiero_sdk_python/tokens/token_airdrop_claim.py b/src/hiero_sdk_python/tokens/token_airdrop_claim.py index cbfd93c61..565e019dd 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_claim.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_claim.py @@ -115,6 +115,16 @@ 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): + 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 + ] + 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..9ee2ac1bf 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_transaction.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_transaction.py @@ -62,6 +62,37 @@ 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): + from hiero_sdk_python.account.account_id import AccountId + 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 + + 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 1f8e363e5..6cecb7a76 100644 --- a/src/hiero_sdk_python/tokens/token_create_transaction.py +++ b/src/hiero_sdk_python/tokens/token_create_transaction.py @@ -559,3 +559,48 @@ 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): + from hiero_sdk_python.crypto.public_key import PublicKey + + 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 5f0af078d..b7c05c397 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -470,6 +470,46 @@ 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): + from hiero_sdk_python.crypto.public_key import PublicKey + + 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 = PublicKey._from_proto(body.adminKey) + if body.HasField("freezeKey"): + transaction.freeze_key = PublicKey._from_proto(body.freezeKey) + if body.HasField("wipeKey"): + transaction.wipe_key = PublicKey._from_proto(body.wipeKey) + if body.HasField("supplyKey"): + transaction.supply_key = PublicKey._from_proto(body.supplyKey) + if body.HasField("metadata_key"): + transaction.metadata_key = PublicKey._from_proto(body.metadata_key) + if body.HasField("pause_key"): + transaction.pause_key = PublicKey._from_proto(body.pause_key) + if body.HasField("kycKey"): + transaction.kyc_key = PublicKey._from_proto(body.kycKey) + if body.HasField("fee_schedule_key"): + transaction.fee_schedule_key = PublicKey._from_proto(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..4a42dbcdf 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 if body.amount else None + transaction.serial = list(body.serialNumbers) + return transaction + def _from_proto(self, proto: TokenWipeAccountTransactionBody) -> TokenWipeTransaction: """ Deserializes a TokenWipeAccountTransactionBody from a protobuf object. From dad80e3eb4bf8655dd8f539c8f973d9d79d54a42 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 14:59:25 +0300 Subject: [PATCH 04/28] feat: implement _from_protobuf for consensus, file, schedule, and system transactions Add _from_protobuf classmethod to TopicCreateTransaction, TopicUpdateTransaction, TopicDeleteTransaction, TopicMessageSubmitTransaction, FileCreateTransaction, FileAppendTransaction, FileUpdateTransaction, FileDeleteTransaction, ScheduleCreateTransaction, ScheduleSignTransaction, ScheduleDeleteTransaction, and FreezeTransaction. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- .../consensus/topic_create_transaction.py | 22 +++++++++++++++ .../consensus/topic_delete_transaction.py | 9 +++++++ .../topic_message_submit_transaction.py | 11 ++++++++ .../consensus/topic_update_transaction.py | 27 +++++++++++++++++++ .../file/file_append_transaction.py | 7 +++++ .../file/file_create_transaction.py | 7 +++++ .../file/file_delete_transaction.py | 9 +++++++ .../file/file_update_transaction.py | 15 +++++++++++ .../schedule/schedule_create_transaction.py | 17 ++++++++++++ .../schedule/schedule_delete_transaction.py | 9 +++++++ .../schedule/schedule_sign_transaction.py | 9 +++++++ .../system/freeze_transaction.py | 13 +++++++++ 12 files changed, 155 insertions(+) diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index 82be5de98..c1ff26854 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -250,3 +250,25 @@ 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): + from hiero_sdk_python.crypto.public_key import PublicKey + + 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 = PublicKey._from_proto(body.adminKey) + if body.HasField("submitKey"): + transaction.submit_key = PublicKey._from_proto(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 = PublicKey._from_proto(body.fee_schedule_key) + transaction.custom_fees = [CustomFixedFee._from_proto(f) for f in body.custom_fees] + transaction.fee_exempt_keys = [PublicKey._from_proto(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 960a1e17a..84fbb352a 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 a886073bf..816d0be57 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -308,3 +308,30 @@ 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): + transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) + if transaction_body.HasField("consensusUpdateTopic"): + body = transaction_body.consensusUpdateTopic + 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 = PublicKey._from_proto(body.adminKey) + if body.HasField("submitKey"): + transaction.submit_key = PublicKey._from_proto(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 = PublicKey._from_proto(body.fee_schedule_key) + if body.HasField("custom_fees"): + transaction.custom_fees = [CustomFixedFee._from_proto(f) for f in body.custom_fees.fees] + if body.HasField("fee_exempt_key_list"): + transaction.fee_exempt_keys = [PublicKey._from_proto(k) for k in body.fee_exempt_key_list.keys] + return transaction diff --git a/src/hiero_sdk_python/file/file_append_transaction.py b/src/hiero_sdk_python/file/file_append_transaction.py index aebb4ad9b..a4cc6abd6 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. 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..b267e09de 100644 --- a/src/hiero_sdk_python/file/file_update_transaction.py +++ b/src/hiero_sdk_python/file/file_update_transaction.py @@ -214,3 +214,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 = [PublicKey._from_proto(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/schedule/schedule_create_transaction.py b/src/hiero_sdk_python/schedule/schedule_create_transaction.py index e9b7d8b9d..2ca5322d3 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.memo else None + if body.HasField("expiration_time"): + transaction.expiration_time = Timestamp._from_protobuf(body.expiration_time) + transaction.wait_for_expiry = body.wait_for_expiry if body.wait_for_expiry else None + 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 From 9ed28ba0654936d1494d2c2ac239c4a6b8d90609 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 14:59:31 +0300 Subject: [PATCH 05/28] test: add _from_protobuf round-trip tests for all transaction types Add test_from_protobuf to every transaction test file that was missing deserialization coverage. Each test builds a transaction, serializes it via build_transaction_body(), reconstructs via _from_protobuf(), and asserts all fields are correctly restored. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- ...ount_allowance_approve_transaction_test.py | 19 +++++++++ ...count_allowance_delete_transaction_test.py | 20 ++++++++++ tests/unit/account_create_transaction_test.py | 19 +++++++++ tests/unit/account_delete_transaction_test.py | 20 ++++++++++ tests/unit/account_update_transaction_test.py | 18 +++++++++ tests/unit/file_append_transaction_test.py | 19 +++++++++ tests/unit/file_create_transaction_test.py | 17 ++++++++ tests/unit/file_delete_transaction_test.py | 14 +++++++ tests/unit/file_update_transaction_test.py | 18 +++++++++ tests/unit/freeze_transaction_test.py | 15 +++++++ .../unit/schedule_create_transaction_test.py | 24 ++++++++++++ .../unit/schedule_delete_transaction_test.py | 15 +++++++ tests/unit/schedule_sign_transaction_test.py | 15 +++++++ tests/unit/token_airdrop_transaction_test.py | 16 ++++++++ .../unit/token_associate_transaction_test.py | 20 ++++++++++ tests/unit/token_burn_transaction_test.py | 31 +++++++++++++++ tests/unit/token_delete_transaction_test.py | 15 +++++++ .../unit/token_dissociate_transaction_test.py | 20 ++++++++++ ...en_fee_schedule_update_transaction_test.py | 22 +++++++++++ tests/unit/token_freeze_transaction_test.py | 17 ++++++++ .../unit/token_grant_kyc_transaction_test.py | 17 ++++++++ tests/unit/token_mint_transaction_test.py | 35 +++++++++++++++++ tests/unit/token_pause_transaction_test.py | 15 +++++++ .../unit/token_revoke_kyc_transaction_test.py | 17 ++++++++ tests/unit/token_unfreeze_transaction_test.py | 17 ++++++++ tests/unit/token_unpause_transaction_test.py | 15 +++++++ .../token_update_nfts_transaction_test.py | 21 ++++++++++ tests/unit/token_update_transaction_test.py | 19 +++++++++ tests/unit/token_wipe_transaction_test.py | 39 +++++++++++++++++++ tests/unit/topic_create_transaction_test.py | 17 ++++++++ tests/unit/topic_delete_transaction_test.py | 14 +++++++ .../topic_message_submit_transaction_test.py | 17 ++++++++ tests/unit/topic_update_transaction_test.py | 17 ++++++++ 33 files changed, 634 insertions(+) diff --git a/tests/unit/account_allowance_approve_transaction_test.py b/tests/unit/account_allowance_approve_transaction_test.py index 1655f2fdb..a12e35c3f 100644 --- a/tests/unit/account_allowance_approve_transaction_test.py +++ b/tests/unit/account_allowance_approve_transaction_test.py @@ -349,3 +349,22 @@ 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_protobuf(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) + + tx = AccountAllowanceApproveTransaction() + tx.approve_hbar_allowance(owner, spender, Hbar(10)) + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = AccountAllowanceApproveTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert len(reconstructed.hbar_allowances) == 1 + assert reconstructed.hbar_allowances[0].owner_account_id == owner + assert reconstructed.hbar_allowances[0].spender_account_id == spender diff --git a/tests/unit/account_allowance_delete_transaction_test.py b/tests/unit/account_allowance_delete_transaction_test.py index fb619e0bc..f7448b811 100644 --- a/tests/unit/account_allowance_delete_transaction_test.py +++ b/tests/unit/account_allowance_delete_transaction_test.py @@ -259,3 +259,23 @@ 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_protobuf(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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = AccountAllowanceDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert len(reconstructed.nft_wipe) == 1 + assert reconstructed.nft_wipe[0].token_id == token_id + assert reconstructed.nft_wipe[0].owner_account_id == owner diff --git a/tests/unit/account_create_transaction_test.py b/tests/unit/account_create_transaction_test.py index d5c2ac0d9..3162d2327 100644 --- a/tests/unit/account_create_transaction_test.py +++ b/tests/unit/account_create_transaction_test.py @@ -539,3 +539,22 @@ 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_protobuf(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 + + body = tx.build_transaction_body() + reconstructed = AccountCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..5441a3943 100644 --- a/tests/unit/account_delete_transaction_test.py +++ b/tests/unit/account_delete_transaction_test.py @@ -247,6 +247,26 @@ def test_build_scheduled_body(delete_params): assert schedulable_body.cryptoDelete.transferAccountID == delete_params["transfer_account_id"]._to_proto() +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf 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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = AccountDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..5deb6fc0a 100644 --- a/tests/unit/account_update_transaction_test.py +++ b/tests/unit/account_update_transaction_test.py @@ -813,3 +813,21 @@ 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_protobuf(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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = AccountUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.account_id == account_id_sender + assert reconstructed.account_memo == "updated" diff --git a/tests/unit/file_append_transaction_test.py b/tests/unit/file_append_transaction_test.py index 479acefd5..2ce7bc84e 100644 --- a/tests/unit/file_append_transaction_test.py +++ b/tests/unit/file_append_transaction_test.py @@ -372,3 +372,22 @@ def test_file_append_execute_all_returns_receipt_without_validation(file_id): receipts = tx.execute_all(client) assert receipts[0].status == ResponseCode.INVALID_FILE_ID + + +@pytest.mark.unit +def test_from_protobuf(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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = FileAppendTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.file_id == test_file_id + assert reconstructed.contents == b"appended" diff --git a/tests/unit/file_create_transaction_test.py b/tests/unit/file_create_transaction_test.py index dbdc1a5e4..78f4e21b1 100644 --- a/tests/unit/file_create_transaction_test.py +++ b/tests/unit/file_create_transaction_test.py @@ -260,3 +260,20 @@ def test_file_create_transaction_from_proto(): assert from_proto.contents == b"" assert from_proto.file_memo == "" assert from_proto.keys == [] + + +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for FileCreateTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + + tx = FileCreateTransaction() + tx.set_contents(b"file contents") + tx.set_file_memo("my file") + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = FileCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.contents == b"file contents" + assert reconstructed.file_memo == "my file" diff --git a/tests/unit/file_delete_transaction_test.py b/tests/unit/file_delete_transaction_test.py index 2aa95840f..9fdde2736 100644 --- a/tests/unit/file_delete_transaction_test.py +++ b/tests/unit/file_delete_transaction_test.py @@ -131,3 +131,17 @@ def test_get_method(): assert method.query is None assert method.transaction == mock_file_stub.deleteFile + + +def test_from_protobuf(mock_account_ids, file_id): + """Test round-trip via _from_protobuf for FileDeleteTransaction.""" + account_id, _, node_account_id, _, _ = mock_account_ids + + tx = FileDeleteTransaction(file_id=file_id) + tx.node_account_id = node_account_id + tx.operator_account_id = account_id + + body = tx.build_transaction_body() + reconstructed = FileDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..42aa6e946 100644 --- a/tests/unit/file_update_transaction_test.py +++ b/tests/unit/file_update_transaction_test.py @@ -358,3 +358,21 @@ def test_encode_contents_string(): # Test None handling encoded = file_tx._encode_contents(None) assert encoded is None + + +def test_from_protobuf(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) + + tx = FileUpdateTransaction() + tx.set_file_id(test_file_id) + tx.set_contents(b"updated") + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = FileUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.file_id == test_file_id + assert reconstructed.contents == b"updated" diff --git a/tests/unit/freeze_transaction_test.py b/tests/unit/freeze_transaction_test.py index 026545b9f..9b26fcb3d 100644 --- a/tests/unit/freeze_transaction_test.py +++ b/tests/unit/freeze_transaction_test.py @@ -289,3 +289,18 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for FreezeTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + + tx = FreezeTransaction() + tx.set_freeze_type(FreezeType.FREEZE_ONLY) + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = FreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.freeze_type == FreezeType.FREEZE_ONLY diff --git a/tests/unit/schedule_create_transaction_test.py b/tests/unit/schedule_create_transaction_test.py index 2354acd49..41e6555fb 100644 --- a/tests/unit/schedule_create_transaction_test.py +++ b/tests/unit/schedule_create_transaction_test.py @@ -15,6 +15,7 @@ ScheduleCreateTransaction, ) from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction @@ -265,3 +266,26 @@ def test_to_proto(mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 + + +def test_from_protobuf(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.operator_account_id = account_id_sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = ScheduleCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.schedule_memo == "test memo" + assert reconstructed.payer_account_id == account_id_sender diff --git a/tests/unit/schedule_delete_transaction_test.py b/tests/unit/schedule_delete_transaction_test.py index 65c4e9383..b6fe4461e 100644 --- a/tests/unit/schedule_delete_transaction_test.py +++ b/tests/unit/schedule_delete_transaction_test.py @@ -249,3 +249,18 @@ def test_schedule_delete_transaction_can_execute(): receipt = transaction.execute(client) assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + + +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for ScheduleDeleteTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + schedule_id = ScheduleId(0, 0, 99) + + tx = ScheduleDeleteTransaction(schedule_id=schedule_id) + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = ScheduleDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..3f946075c 100644 --- a/tests/unit/schedule_sign_transaction_test.py +++ b/tests/unit/schedule_sign_transaction_test.py @@ -192,3 +192,18 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for ScheduleSignTransaction.""" + operator_id, _, node_account_id, _, _ = mock_account_ids + schedule_id = ScheduleId(0, 0, 42) + + tx = ScheduleSignTransaction(schedule_id=schedule_id) + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = ScheduleSignTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..17b982e75 100644 --- a/tests/unit/token_airdrop_transaction_test.py +++ b/tests/unit/token_airdrop_transaction_test.py @@ -402,3 +402,19 @@ 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_protobuf(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.operator_account_id = sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenAirdropTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert len(reconstructed.token_transfers[token_id_1]) == 2 diff --git a/tests/unit/token_associate_transaction_test.py b/tests/unit/token_associate_transaction_test.py index 9c6bd5f3e..e7054d523 100644 --- a/tests/unit/token_associate_transaction_test.py +++ b/tests/unit/token_associate_transaction_test.py @@ -217,3 +217,23 @@ 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_protobuf(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 + + body = tx.build_transaction_body() + reconstructed = TokenAssociateTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..dc0d29943 100644 --- a/tests/unit/token_burn_transaction_test.py +++ b/tests/unit/token_burn_transaction_test.py @@ -182,3 +182,34 @@ 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_protobuf_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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenBurnTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert reconstructed.amount == 100 + + +def test_from_protobuf_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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenBurnTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert list(reconstructed.serials) == serials diff --git a/tests/unit/token_delete_transaction_test.py b/tests/unit/token_delete_transaction_test.py index bb961eb4b..feec4a743 100644 --- a/tests/unit/token_delete_transaction_test.py +++ b/tests/unit/token_delete_transaction_test.py @@ -113,3 +113,18 @@ 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_protobuf(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 + + body = tx.build_transaction_body() + reconstructed = TokenDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..e127099d3 100644 --- a/tests/unit/token_dissociate_transaction_test.py +++ b/tests/unit/token_dissociate_transaction_test.py @@ -180,6 +180,26 @@ def test_from_proto(mock_account_ids): assert reconstructed_tx.token_ids[1] == token_id_2 +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf 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 + + body = tx.build_transaction_body() + reconstructed = TokenDissociateTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..065a3b484 100644 --- a/tests/unit/token_fee_schedule_update_transaction_test.py +++ b/tests/unit/token_fee_schedule_update_transaction_test.py @@ -102,3 +102,25 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf 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.operator_account_id = account_id_sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenFeeScheduleUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert len(reconstructed.custom_fees) == 1 + assert reconstructed.custom_fees[0].amount == 100 diff --git a/tests/unit/token_freeze_transaction_test.py b/tests/unit/token_freeze_transaction_test.py index e19f15dd8..ab4855842 100644 --- a/tests/unit/token_freeze_transaction_test.py +++ b/tests/unit/token_freeze_transaction_test.py @@ -136,3 +136,20 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf 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 + + body = tx.build_transaction_body() + reconstructed = TokenFreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..67af497ab 100644 --- a/tests/unit/token_grant_kyc_transaction_test.py +++ b/tests/unit/token_grant_kyc_transaction_test.py @@ -145,6 +145,23 @@ def test_grant_kyc_transaction_from_proto(mock_account_ids): assert from_proto.account_id == AccountId() +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf 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.operator_account_id = account_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenGrantKycTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..ef540e672 100644 --- a/tests/unit/token_mint_transaction_test.py +++ b/tests/unit/token_mint_transaction_test.py @@ -299,3 +299,38 @@ 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_protobuf_fungible(mock_account_ids): + """Test round-trip via _from_protobuf 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 + + body = tx.build_transaction_body() + reconstructed = TokenMintTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert reconstructed.amount == 500 + + +def test_from_protobuf_nft(mock_account_ids): + """Test round-trip via _from_protobuf 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 + + body = tx.build_transaction_body() + reconstructed = TokenMintTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..7812b751d 100644 --- a/tests/unit/token_pause_transaction_test.py +++ b/tests/unit/token_pause_transaction_test.py @@ -161,3 +161,18 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for TokenPauseTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenPauseTransaction() + tx.set_token_id(token_id_1) + tx.operator_account_id = account_id_sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenPauseTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..05291b830 100644 --- a/tests/unit/token_revoke_kyc_transaction_test.py +++ b/tests/unit/token_revoke_kyc_transaction_test.py @@ -138,6 +138,23 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenRevokeKyc.account == account_id._to_proto() +def test_from_protobuf(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.operator_account_id = account_id_sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenRevokeKycTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + + 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..1ce0515f3 100644 --- a/tests/unit/token_unfreeze_transaction_test.py +++ b/tests/unit/token_unfreeze_transaction_test.py @@ -112,6 +112,23 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenUnfreeze.account == freeze_id._to_proto() +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf 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 + + body = tx.build_transaction_body() + reconstructed = TokenUnfreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..c941b3f14 100644 --- a/tests/unit/token_unpause_transaction_test.py +++ b/tests/unit/token_unpause_transaction_test.py @@ -169,6 +169,21 @@ def test_from_proto(mock_account_ids): assert unpause_tx.token_id.num == token_id.num +def test_from_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for TokenUnpauseTransaction.""" + account_id_sender, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenUnpauseTransaction() + tx.set_token_id(token_id_1) + tx.operator_account_id = account_id_sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenUnpauseTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..78cd41ca9 100644 --- a/tests/unit/token_update_nfts_transaction_test.py +++ b/tests/unit/token_update_nfts_transaction_test.py @@ -212,3 +212,24 @@ 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_protobuf(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.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenUpdateNftsTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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..dd38575c6 100644 --- a/tests/unit/token_update_transaction_test.py +++ b/tests/unit/token_update_transaction_test.py @@ -466,3 +466,22 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for TokenUpdateTransaction.""" + operator_id, _, node_account_id, token_id_1, _ = mock_account_ids + + tx = TokenUpdateTransaction() + tx.set_token_id(token_id_1) + tx.set_token_name("NewName") + tx.set_token_symbol("NNS") + tx.operator_account_id = operator_id + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TokenUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert reconstructed.token_name == "NewName" + assert reconstructed.token_symbol == "NNS" diff --git a/tests/unit/token_wipe_transaction_test.py b/tests/unit/token_wipe_transaction_test.py index d5cbb85ee..13beae60d 100644 --- a/tests/unit/token_wipe_transaction_test.py +++ b/tests/unit/token_wipe_transaction_test.py @@ -194,3 +194,42 @@ 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_protobuf_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 + + body = tx.build_transaction_body() + reconstructed = TokenWipeTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + assert reconstructed.amount == 50 + + +def test_from_protobuf_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 + + body = tx.build_transaction_body() + reconstructed = TokenWipeTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.token_id == token_id_1 + assert reconstructed.account_id == account_id_sender + assert list(reconstructed.serial) == serial diff --git a/tests/unit/topic_create_transaction_test.py b/tests/unit/topic_create_transaction_test.py index 4c92ce28e..79fe0c4f6 100644 --- a/tests/unit/topic_create_transaction_test.py +++ b/tests/unit/topic_create_transaction_test.py @@ -574,3 +574,20 @@ 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_protobuf(mock_account_ids): + """Test round-trip via _from_protobuf for TopicCreateTransaction.""" + account_id_sender, _, node_account_id, _, _ = mock_account_ids + + tx = TopicCreateTransaction() + tx.set_memo("hello") + tx.set_auto_renew_account(account_id_sender) + tx.operator_account_id = account_id_sender + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TopicCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.memo == "hello" + assert reconstructed.auto_renew_account == account_id_sender diff --git a/tests/unit/topic_delete_transaction_test.py b/tests/unit/topic_delete_transaction_test.py index 925c212fe..4e3aa847f 100644 --- a/tests/unit/topic_delete_transaction_test.py +++ b/tests/unit/topic_delete_transaction_test.py @@ -113,3 +113,17 @@ def test_execute_topic_delete_transaction(topic_id): # Verify the receipt contains the expected values assert receipt.status == ResponseCode.SUCCESS + + +def test_from_protobuf(mock_account_ids, topic_id): + """Test round-trip via _from_protobuf for TopicDeleteTransaction.""" + _, _, node_account_id, _, _ = mock_account_ids + + tx = TopicDeleteTransaction(topic_id=topic_id) + tx.operator_account_id = AccountId(0, 0, 2) + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TopicDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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 b0adf7a69..35338a597 100644 --- a/tests/unit/topic_message_submit_transaction_test.py +++ b/tests/unit/topic_message_submit_transaction_test.py @@ -482,6 +482,23 @@ def test_topic_submit_execute_raises_error_with_validation(topic_id): assert e.value.status == ResponseCode.INVALID_SIGNATURE +def test_from_protobuf(topic_id): + """Test round-trip via _from_protobuf for TopicMessageSubmitTransaction.""" + from hiero_sdk_python.account.account_id import AccountId + + tx = TopicMessageSubmitTransaction() + tx.set_topic_id(topic_id) + tx.set_message("hello world") + tx.operator_account_id = AccountId(0, 0, 1) + tx.node_account_id = AccountId(0, 0, 3) + + body = tx.build_transaction_body() + reconstructed = TopicMessageSubmitTransaction._from_protobuf(body, body.SerializeToString(), None) + + 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 12124de98..7d4f073c1 100644 --- a/tests/unit/topic_update_transaction_test.py +++ b/tests/unit/topic_update_transaction_test.py @@ -140,6 +140,23 @@ def test_execute_topic_update_transaction(topic_id): assert receipt.status == ResponseCode.SUCCESS +def test_from_protobuf(mock_account_ids, topic_id): + """Test round-trip via _from_protobuf for TopicUpdateTransaction.""" + _, _, node_account_id, _, _ = mock_account_ids + + tx = TopicUpdateTransaction() + tx.set_topic_id(topic_id) + tx.set_memo("Updated Memo") + tx.operator_account_id = AccountId(0, 0, 2) + tx.node_account_id = node_account_id + + body = tx.build_transaction_body() + reconstructed = TopicUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + + assert reconstructed.topic_id == topic_id + assert reconstructed.memo == "Updated Memo" + + # 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.""" From 79a524cf4e33ac48b8628252abb8248daca3e3eb Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 15:48:11 +0300 Subject: [PATCH 06/28] fix: remove duplicate CryptoUpdateTransactionBody import in account_update_transaction Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/account/account_update_transaction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hiero_sdk_python/account/account_update_transaction.py b/src/hiero_sdk_python/account/account_update_transaction.py index 03bb563a5..681074ba2 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -362,7 +362,6 @@ def _get_method(self, channel: _Channel) -> _Method: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): from hiero_sdk_python.crypto.public_key import PublicKey - from hiero_sdk_python.hapi.services.crypto_update_pb2 import CryptoUpdateTransactionBody transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("cryptoUpdateAccount"): From 386290ba4d762aa42b1b68c99d2ec74ff9b705fe Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 15:57:09 +0300 Subject: [PATCH 07/28] chore: suppress cyclomatic complexity warnings on _from_protobuf methods Wide proto deserialization methods necessarily have many HasField branches. Suppress PLR0912 rather than splitting them into less-readable helpers. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/consensus/topic_create_transaction.py | 2 +- src/hiero_sdk_python/consensus/topic_update_transaction.py | 2 +- src/hiero_sdk_python/tokens/token_airdrop_claim.py | 2 +- src/hiero_sdk_python/tokens/token_create_transaction.py | 2 +- src/hiero_sdk_python/tokens/token_update_transaction.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index c1ff26854..8f65a31b9 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -252,7 +252,7 @@ def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.topic.createTopic, query_func=None) @classmethod - def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 from hiero_sdk_python.crypto.public_key import PublicKey transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) diff --git a/src/hiero_sdk_python/consensus/topic_update_transaction.py b/src/hiero_sdk_python/consensus/topic_update_transaction.py index 816d0be57..6d166359d 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -310,7 +310,7 @@ def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.topic.updateTopic, query_func=None) @classmethod - def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + 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 diff --git a/src/hiero_sdk_python/tokens/token_airdrop_claim.py b/src/hiero_sdk_python/tokens/token_airdrop_claim.py index 565e019dd..2ee650e26 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_claim.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_claim.py @@ -116,7 +116,7 @@ def _pending_airdrop_ids_to_proto(self) -> list[Any]: ] @classmethod - def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + 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 diff --git a/src/hiero_sdk_python/tokens/token_create_transaction.py b/src/hiero_sdk_python/tokens/token_create_transaction.py index 6cecb7a76..38b792123 100644 --- a/src/hiero_sdk_python/tokens/token_create_transaction.py +++ b/src/hiero_sdk_python/tokens/token_create_transaction.py @@ -561,7 +561,7 @@ 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): + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 from hiero_sdk_python.crypto.public_key import PublicKey transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index b7c05c397..1a3240fb4 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -471,7 +471,7 @@ 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): + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 from hiero_sdk_python.crypto.public_key import PublicKey transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) From e67e7b6ce41259ead2f370fa5c90621522caee45 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 15:58:19 +0300 Subject: [PATCH 08/28] chore: suppress cyclomatic complexity warning on AccountUpdateTransaction._from_protobuf Missed in previous commit; wide proto deserialization necessarily has many branches. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/account/account_update_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hiero_sdk_python/account/account_update_transaction.py b/src/hiero_sdk_python/account/account_update_transaction.py index 681074ba2..af753769e 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -360,7 +360,7 @@ def _get_method(self, channel: _Channel) -> _Method: return _Method(transaction_func=channel.crypto.updateAccount, query_func=None) @classmethod - def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 from hiero_sdk_python.crypto.public_key import PublicKey transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) From bbe3d4a7e751e5191591abd09618b2bb427aee09 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 16:10:21 +0300 Subject: [PATCH 09/28] refactor: move PublicKey imports to module level in _from_protobuf files Local imports inside _from_protobuf were unnecessary; no circular import exists. Moved to top-level for consistency with the rest of the codebase. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/account/account_create_transaction.py | 2 +- src/hiero_sdk_python/account/account_update_transaction.py | 3 +-- src/hiero_sdk_python/consensus/topic_create_transaction.py | 3 +-- src/hiero_sdk_python/tokens/token_create_transaction.py | 3 +-- src/hiero_sdk_python/tokens/token_update_transaction.py | 3 +-- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/hiero_sdk_python/account/account_create_transaction.py b/src/hiero_sdk_python/account/account_create_transaction.py index 467aae9c0..768e00665 100644 --- a/src/hiero_sdk_python/account/account_create_transaction.py +++ b/src/hiero_sdk_python/account/account_create_transaction.py @@ -10,6 +10,7 @@ from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key import Key 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.executable import _Method from hiero_sdk_python.hapi.services import crypto_create_pb2, duration_pb2, transaction_pb2 @@ -374,7 +375,6 @@ def _get_method(self, channel: _Channel) -> _Method: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): - from hiero_sdk_python.crypto.public_key import PublicKey transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("cryptoCreateAccount"): diff --git a/src/hiero_sdk_python/account/account_update_transaction.py b/src/hiero_sdk_python/account/account_update_transaction.py index af753769e..33b3537ad 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -10,6 +10,7 @@ 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.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services.crypto_update_pb2 import CryptoUpdateTransactionBody @@ -361,8 +362,6 @@ def _get_method(self, channel: _Channel) -> _Method: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 - from hiero_sdk_python.crypto.public_key import PublicKey - transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("cryptoUpdateAccount"): body = transaction_body.cryptoUpdateAccount diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index 8f65a31b9..1d9e98511 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -19,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.public_key import PublicKey from hiero_sdk_python.utils.key_utils import Key, key_to_proto @@ -253,8 +254,6 @@ def _get_method(self, channel: _Channel) -> _Method: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 - from hiero_sdk_python.crypto.public_key import PublicKey - transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("consensusCreateTopic"): body = transaction_body.consensusCreateTopic diff --git a/src/hiero_sdk_python/tokens/token_create_transaction.py b/src/hiero_sdk_python/tokens/token_create_transaction.py index 38b792123..0bad7353d 100644 --- a/src/hiero_sdk_python/tokens/token_create_transaction.py +++ b/src/hiero_sdk_python/tokens/token_create_transaction.py @@ -29,6 +29,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, key_to_proto @@ -562,8 +563,6 @@ def _get_method(self, channel: _Channel) -> _Method: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 - from hiero_sdk_python.crypto.public_key import PublicKey - transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("tokenCreation"): body = transaction_body.tokenCreation diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index 1a3240fb4..6c2ff4ccc 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -25,6 +25,7 @@ from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation 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, key_to_proto @@ -472,8 +473,6 @@ def _get_method(self, channel: _Channel) -> _Method: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912 - from hiero_sdk_python.crypto.public_key import PublicKey - transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("tokenUpdate"): body = transaction_body.tokenUpdate From 95c03609690c471c3f6a4bdade9f4e64a10eab54 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 20 Apr 2026 18:10:59 +0300 Subject: [PATCH 10/28] refactor: move local imports to module level in token_airdrop_transaction Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/token_airdrop_transaction.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/hiero_sdk_python/tokens/token_airdrop_transaction.py b/src/hiero_sdk_python/tokens/token_airdrop_transaction.py index 9ee2ac1bf..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 @@ -64,11 +66,6 @@ def _build_proto_body(self) -> token_airdrop_pb2.TokenAirdropTransactionBody: @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): - from hiero_sdk_python.account.account_id import AccountId - 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 - transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) if transaction_body.HasField("tokenAirdrop"): for transfer in transaction_body.tokenAirdrop.token_transfers: From 5a3e93f8d47944a2415b3fb266c517cfe4d4c6b5 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Tue, 21 Apr 2026 16:44:04 +0300 Subject: [PATCH 11/28] test: update _from_protobuf tests to use Transaction.from_bytes() - Route round-trip tests through the public Transaction.from_bytes() API to cover the dispatch map, not just the protected _from_protobuf hook - Rename test_from_protobuf -> test_from_bytes to reflect the tested API - Expand field coverage: inactive branches, staking fields, fee fields, allowance types, scheduled body, and key deserialization - Use freeze() + to_bytes() for proper serialization round-trip Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- ...ount_allowance_approve_transaction_test.py | 24 +++++++++++++++---- ...count_allowance_delete_transaction_test.py | 12 ++++++---- tests/unit/account_create_transaction_test.py | 8 ++++--- tests/unit/account_update_transaction_test.py | 19 +++++++++++---- tests/unit/file_append_transaction_test.py | 9 +++---- tests/unit/file_update_transaction_test.py | 11 +++++---- tests/unit/freeze_transaction_test.py | 24 +++++++++++++------ .../unit/schedule_create_transaction_test.py | 13 ++++++---- tests/unit/token_airdrop_transaction_test.py | 15 ++++++++---- .../unit/token_associate_transaction_test.py | 8 ++++--- tests/unit/token_burn_transaction_test.py | 22 ++++++++++------- tests/unit/token_delete_transaction_test.py | 8 ++++--- ...en_fee_schedule_update_transaction_test.py | 6 ++++- tests/unit/token_freeze_transaction_test.py | 1 + .../unit/token_revoke_kyc_transaction_test.py | 11 +++++---- .../token_update_nfts_transaction_test.py | 11 +++++---- tests/unit/token_update_transaction_test.py | 11 +++++---- tests/unit/token_wipe_transaction_test.py | 17 ++++++++----- tests/unit/topic_create_transaction_test.py | 11 +++++++++ .../topic_message_submit_transaction_test.py | 14 ++++++----- tests/unit/topic_update_transaction_test.py | 13 ++++++++++ 21 files changed, 191 insertions(+), 77 deletions(-) diff --git a/tests/unit/account_allowance_approve_transaction_test.py b/tests/unit/account_allowance_approve_transaction_test.py index a12e35c3f..3627f42d8 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 @@ -351,20 +353,34 @@ def test_zero_amount_allowances(account_allowance_transaction, sample_accounts, assert account_allowance_transaction.token_allowances[0].amount == 0 -def test_from_protobuf(mock_account_ids): +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.operator_account_id = operator_id + 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() - body = tx.build_transaction_body() - reconstructed = AccountAllowanceApproveTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 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 diff --git a/tests/unit/account_allowance_delete_transaction_test.py b/tests/unit/account_allowance_delete_transaction_test.py index f7448b811..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 @@ -261,7 +263,7 @@ def test_empty_nft_wipe_list(account_allowance_delete_transaction): assert len(proto_body.nftAllowances) == 0 -def test_from_protobuf(mock_account_ids): +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) @@ -270,12 +272,14 @@ def test_from_protobuf(mock_account_ids): tx = AccountAllowanceDeleteTransaction() tx.delete_all_token_nft_allowances(nft_id, owner) - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = AccountAllowanceDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 3162d2327..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 @@ -541,7 +542,7 @@ def test_set_stake_account_id_reset_stake_node_id(): assert tx.staked_node_id is None -def test_from_protobuf(mock_account_ids): +def test_from_bytes(mock_account_ids): """Test round-trip via _from_protobuf for AccountCreateTransaction.""" operator_id, node_account_id = mock_account_ids @@ -552,9 +553,10 @@ def test_from_protobuf(mock_account_ids): tx.set_receiver_signature_required(True) tx.transaction_id = generate_transaction_id(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = AccountCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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_update_transaction_test.py b/tests/unit/account_update_transaction_test.py index 5deb6fc0a..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 @@ -815,7 +817,7 @@ def test_set_keylist_threshold_key(): assert len(update_body.key.thresholdKey.keys.keys) == 3 -def test_from_protobuf(mock_account_ids): +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) @@ -823,11 +825,20 @@ def test_from_protobuf(mock_account_ids): tx = AccountUpdateTransaction() tx.set_account_id(account_id_sender) tx.set_account_memo("updated") - tx.operator_account_id = operator_id + 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() - body = tx.build_transaction_body() - reconstructed = AccountUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 2ce7bc84e..7a03cff05 100644 --- a/tests/unit/file_append_transaction_test.py +++ b/tests/unit/file_append_transaction_test.py @@ -375,7 +375,7 @@ def test_file_append_execute_all_returns_receipt_without_validation(file_id): @pytest.mark.unit -def test_from_protobuf(mock_account_ids): +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) @@ -383,11 +383,12 @@ def test_from_protobuf(mock_account_ids): tx = FileAppendTransaction() tx.set_file_id(test_file_id) tx.set_contents(b"appended") - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = FileAppendTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, FileAppendTransaction) assert reconstructed.file_id == test_file_id assert reconstructed.contents == b"appended" diff --git a/tests/unit/file_update_transaction_test.py b/tests/unit/file_update_transaction_test.py index 42aa6e946..9984807cf 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 @@ -360,7 +362,7 @@ def test_encode_contents_string(): assert encoded is None -def test_from_protobuf(mock_account_ids): +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) @@ -368,11 +370,12 @@ def test_from_protobuf(mock_account_ids): tx = FileUpdateTransaction() tx.set_file_id(test_file_id) tx.set_contents(b"updated") - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = FileUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, FileUpdateTransaction) assert reconstructed.file_id == test_file_id assert reconstructed.contents == b"updated" diff --git a/tests/unit/freeze_transaction_test.py b/tests/unit/freeze_transaction_test.py index 9b26fcb3d..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 @@ -291,16 +293,24 @@ def test_build_proto_body_with_none_fields(): assert proto_body.freeze_type == proto_FreezeType.UNKNOWN_FREEZE_TYPE -def test_from_protobuf(mock_account_ids): +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() - tx.set_freeze_type(FreezeType.FREEZE_ONLY) - tx.operator_account_id = operator_id + 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() - body = tx.build_transaction_body() - reconstructed = FreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) - assert reconstructed.freeze_type == FreezeType.FREEZE_ONLY + 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 41e6555fb..d94da76a8 100644 --- a/tests/unit/schedule_create_transaction_test.py +++ b/tests/unit/schedule_create_transaction_test.py @@ -16,6 +16,8 @@ ) 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 @@ -268,7 +270,7 @@ def test_to_proto(mock_client): assert len(proto.signedTransactionBytes) > 0 -def test_from_protobuf(mock_account_ids): +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 @@ -281,11 +283,14 @@ def test_from_protobuf(mock_account_ids): tx.set_schedule_memo("test memo") tx.set_payer_account_id(account_id_sender) tx._set_schedulable_body(schedulable_body) - tx.operator_account_id = account_id_sender + tx.transaction_id = TransactionId.generate(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = ScheduleCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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/token_airdrop_transaction_test.py b/tests/unit/token_airdrop_transaction_test.py index 17b982e75..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 @@ -404,17 +406,22 @@ def test_from_proto_without_token_transfer(mock_account_ids): assert not airdrop_tx.token_transfers -def test_from_protobuf(mock_account_ids): +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.operator_account_id = sender + tx.transaction_id = TransactionId.generate(sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenAirdropTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 e7054d523..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 @@ -219,7 +220,7 @@ def test_from_proto_builds_transaction(mock_account_ids): assert tx.token_ids[1] == token_id_2 -def test_from_protobuf(mock_account_ids): +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 @@ -229,10 +230,11 @@ def test_from_protobuf(mock_account_ids): 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() - body = tx.build_transaction_body() - reconstructed = TokenAssociateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 diff --git a/tests/unit/token_burn_transaction_test.py b/tests/unit/token_burn_transaction_test.py index dc0d29943..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 @@ -184,32 +186,36 @@ def test_build_scheduled_body_nft(mock_account_ids): assert schedulable_body.tokenBurn.serialNumbers == serials -def test_from_protobuf_fungible(mock_account_ids): +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.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenBurnTransaction._from_protobuf(body, body.SerializeToString(), None) + 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_protobuf_nft(mock_account_ids): +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.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenBurnTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 feec4a743..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 @@ -115,7 +116,7 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenDeletion.token == token_id._to_proto() -def test_from_protobuf(mock_account_ids): +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 @@ -123,8 +124,9 @@ def test_from_protobuf(mock_account_ids): tx.set_token_id(token_id_1) tx.transaction_id = generate_transaction_id(account_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, TokenDeleteTransaction) assert reconstructed.token_id == token_id_1 diff --git a/tests/unit/token_fee_schedule_update_transaction_test.py b/tests/unit/token_fee_schedule_update_transaction_test.py index 065a3b484..c713c9164 100644 --- a/tests/unit/token_fee_schedule_update_transaction_test.py +++ b/tests/unit/token_fee_schedule_update_transaction_test.py @@ -123,4 +123,8 @@ def test_from_protobuf(mock_account_ids): assert reconstructed.token_id == token_id_1 assert len(reconstructed.custom_fees) == 1 - assert reconstructed.custom_fees[0].amount == 100 + 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 ab4855842..c624c2559 100644 --- a/tests/unit/token_freeze_transaction_test.py +++ b/tests/unit/token_freeze_transaction_test.py @@ -151,5 +151,6 @@ def test_from_protobuf(mock_account_ids): body = tx.build_transaction_body() reconstructed = TokenFreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + assert isinstance(reconstructed, TokenFreezeTransaction) assert reconstructed.token_id == token_id_1 assert reconstructed.account_id == account_id_sender diff --git a/tests/unit/token_revoke_kyc_transaction_test.py b/tests/unit/token_revoke_kyc_transaction_test.py index 05291b830..6a7520795 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,19 +140,20 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenRevokeKyc.account == account_id._to_proto() -def test_from_protobuf(mock_account_ids): +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.operator_account_id = account_id_sender + tx.transaction_id = TransactionId.generate(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenRevokeKycTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 diff --git a/tests/unit/token_update_nfts_transaction_test.py b/tests/unit/token_update_nfts_transaction_test.py index 78cd41ca9..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 @@ -214,7 +216,7 @@ def test_update_nfts_transaction_from_proto(mock_account_ids): assert empty_tx.metadata is empty_proto.metadata.value # Should be None or empty -def test_from_protobuf(mock_account_ids): +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] @@ -224,12 +226,13 @@ def test_from_protobuf(mock_account_ids): tx.set_token_id(token_id_1) tx.set_serial_numbers(serial_numbers) tx.set_metadata(metadata) - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenUpdateNftsTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 dd38575c6..7409b0f31 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 @@ -468,7 +470,7 @@ def test_build_transaction_body_with_keys(mock_account_ids, key_type, use_privat verify_key_in_proto(transaction_body.tokenUpdate.freezeKey, expected_freeze_public, key_type) -def test_from_protobuf(mock_account_ids): +def test_from_bytes(mock_account_ids): """Test round-trip via _from_protobuf for TokenUpdateTransaction.""" operator_id, _, node_account_id, token_id_1, _ = mock_account_ids @@ -476,12 +478,13 @@ def test_from_protobuf(mock_account_ids): tx.set_token_id(token_id_1) tx.set_token_name("NewName") tx.set_token_symbol("NNS") - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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" diff --git a/tests/unit/token_wipe_transaction_test.py b/tests/unit/token_wipe_transaction_test.py index 13beae60d..3625f1158 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 @@ -196,7 +197,7 @@ def test_build_scheduled_body_with_serial_numbers(mock_account_ids): assert schedulable_body.tokenWipe.serialNumbers == serial_numbers -def test_from_protobuf_fungible(mock_account_ids): +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 @@ -206,16 +207,18 @@ def test_from_protobuf_fungible(mock_account_ids): tx.set_amount(50) tx.transaction_id = generate_transaction_id(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenWipeTransaction._from_protobuf(body, body.SerializeToString(), None) + 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_protobuf_nft(mock_account_ids): +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] @@ -226,10 +229,12 @@ def test_from_protobuf_nft(mock_account_ids): tx.set_serial(serial) tx.transaction_id = generate_transaction_id(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenWipeTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 is None diff --git a/tests/unit/topic_create_transaction_test.py b/tests/unit/topic_create_transaction_test.py index 79fe0c4f6..74b9037dd 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, @@ -580,9 +581,16 @@ def test_from_protobuf(mock_account_ids): """Test round-trip via _from_protobuf 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.operator_account_id = account_id_sender tx.node_account_id = node_account_id @@ -591,3 +599,6 @@ def test_from_protobuf(mock_account_ids): 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_message_submit_transaction_test.py b/tests/unit/topic_message_submit_transaction_test.py index 35338a597..4ff7e1e76 100644 --- a/tests/unit/topic_message_submit_transaction_test.py +++ b/tests/unit/topic_message_submit_transaction_test.py @@ -6,6 +6,7 @@ import pytest +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError from hiero_sdk_python.hapi.services import ( @@ -20,6 +21,8 @@ ) 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 from tests.unit.mock_server import mock_hedera_servers @@ -482,19 +485,18 @@ def test_topic_submit_execute_raises_error_with_validation(topic_id): assert e.value.status == ResponseCode.INVALID_SIGNATURE -def test_from_protobuf(topic_id): +def test_from_bytes(topic_id): """Test round-trip via _from_protobuf for TopicMessageSubmitTransaction.""" - from hiero_sdk_python.account.account_id import AccountId - tx = TopicMessageSubmitTransaction() tx.set_topic_id(topic_id) tx.set_message("hello world") - tx.operator_account_id = AccountId(0, 0, 1) + tx.transaction_id = TransactionId.generate(AccountId(0, 0, 1)) tx.node_account_id = AccountId(0, 0, 3) + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TopicMessageSubmitTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, TopicMessageSubmitTransaction) assert reconstructed.topic_id == topic_id assert reconstructed.message == "hello world" diff --git a/tests/unit/topic_update_transaction_test.py b/tests/unit/topic_update_transaction_test.py index 7d4f073c1..cfe65c460 100644 --- a/tests/unit/topic_update_transaction_test.py +++ b/tests/unit/topic_update_transaction_test.py @@ -144,9 +144,18 @@ def test_from_protobuf(mock_account_ids, topic_id): """Test round-trip via _from_protobuf 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.operator_account_id = AccountId(0, 0, 2) tx.node_account_id = node_account_id @@ -155,6 +164,10 @@ def test_from_protobuf(mock_account_ids, topic_id): 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 From c01a7ff60c87f82556bc8a17ada0622fd38e407d Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Tue, 21 Apr 2026 17:06:56 +0300 Subject: [PATCH 12/28] test: update _from_protobuf tests to use Transaction.from_bytes() - Route round-trip tests through the public Transaction.from_bytes() API to cover the dispatch map, not just the protected _from_protobuf hook - Rename test_from_protobuf -> test_from_bytes to reflect the tested API - Expand field coverage: inactive branches, staking fields, fee fields, allowance types, scheduled body, and key deserialization - Use freeze() + to_bytes() for proper serialization round-trip Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- tests/unit/account_delete_transaction_test.py | 13 ++++++++----- tests/unit/file_create_transaction_test.py | 13 ++++++++----- tests/unit/file_delete_transaction_test.py | 13 ++++++++----- .../unit/schedule_delete_transaction_test.py | 13 ++++++++----- tests/unit/schedule_sign_transaction_test.py | 13 ++++++++----- .../unit/token_dissociate_transaction_test.py | 10 ++++++---- ...en_fee_schedule_update_transaction_test.py | 13 ++++++++----- tests/unit/token_freeze_transaction_test.py | 9 +++++---- .../unit/token_grant_kyc_transaction_test.py | 13 ++++++++----- tests/unit/token_mint_transaction_test.py | 19 +++++++++++-------- tests/unit/token_pause_transaction_test.py | 13 ++++++++----- tests/unit/token_unfreeze_transaction_test.py | 10 ++++++---- tests/unit/token_unpause_transaction_test.py | 13 ++++++++----- tests/unit/topic_create_transaction_test.py | 13 ++++++++----- tests/unit/topic_delete_transaction_test.py | 13 ++++++++----- tests/unit/topic_update_transaction_test.py | 13 ++++++++----- 16 files changed, 124 insertions(+), 80 deletions(-) diff --git a/tests/unit/account_delete_transaction_test.py b/tests/unit/account_delete_transaction_test.py index 5441a3943..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,8 +249,8 @@ def test_build_scheduled_body(delete_params): assert schedulable_body.cryptoDelete.transferAccountID == delete_params["transfer_account_id"]._to_proto() -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for AccountDeleteTransaction.""" +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) @@ -257,12 +259,13 @@ def test_from_protobuf(mock_account_ids): account_id=account_id_sender, transfer_account_id=account_id_recipient, ) - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = AccountDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 diff --git a/tests/unit/file_create_transaction_test.py b/tests/unit/file_create_transaction_test.py index 78f4e21b1..cc9b203af 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 @@ -262,18 +264,19 @@ def test_file_create_transaction_from_proto(): assert from_proto.keys == [] -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for FileCreateTransaction.""" +def test_from_bytes(mock_account_ids): + """Test round-trip via Transaction.from_bytes() for FileCreateTransaction.""" operator_id, _, node_account_id, _, _ = mock_account_ids tx = FileCreateTransaction() tx.set_contents(b"file contents") tx.set_file_memo("my file") - tx.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = FileCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, FileCreateTransaction) assert reconstructed.contents == b"file contents" assert reconstructed.file_memo == "my file" diff --git a/tests/unit/file_delete_transaction_test.py b/tests/unit/file_delete_transaction_test.py index 9fdde2736..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 @@ -133,15 +135,16 @@ def test_get_method(): assert method.transaction == mock_file_stub.deleteFile -def test_from_protobuf(mock_account_ids, file_id): - """Test round-trip via _from_protobuf for FileDeleteTransaction.""" +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.operator_account_id = account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = FileDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, FileDeleteTransaction) assert reconstructed.file_id == file_id diff --git a/tests/unit/schedule_delete_transaction_test.py b/tests/unit/schedule_delete_transaction_test.py index b6fe4461e..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 @@ -251,16 +253,17 @@ def test_schedule_delete_transaction_can_execute(): assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for ScheduleDeleteTransaction.""" +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.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = ScheduleDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 3f946075c..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 @@ -194,16 +196,17 @@ def test_schedule_id_property_access(): assert retrieved_schedule_id == schedule_id -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for ScheduleSignTransaction.""" +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.operator_account_id = operator_id + tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = ScheduleSignTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, ScheduleSignTransaction) assert reconstructed.schedule_id == schedule_id diff --git a/tests/unit/token_dissociate_transaction_test.py b/tests/unit/token_dissociate_transaction_test.py index e127099d3..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,8 +181,8 @@ def test_from_proto(mock_account_ids): assert reconstructed_tx.token_ids[1] == token_id_2 -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenDissociateTransaction.""" +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() @@ -190,10 +191,11 @@ def test_from_protobuf(mock_account_ids): 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() - body = tx.build_transaction_body() - reconstructed = TokenDissociateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 diff --git a/tests/unit/token_fee_schedule_update_transaction_test.py b/tests/unit/token_fee_schedule_update_transaction_test.py index c713c9164..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 @@ -104,8 +106,8 @@ def test_build_transaction_body_with_empty_custom_fees(mock_account_ids): assert len(transaction_body.token_fee_schedule_update.custom_fees) == 0 -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenFeeScheduleUpdateTransaction.""" +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( @@ -115,12 +117,13 @@ def test_from_protobuf(mock_account_ids): ) tx = TokenFeeScheduleUpdateTransaction(token_id=token_id_1, custom_fees=[fee]) - tx.operator_account_id = account_id_sender + tx.transaction_id = TransactionId.generate(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenFeeScheduleUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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] diff --git a/tests/unit/token_freeze_transaction_test.py b/tests/unit/token_freeze_transaction_test.py index c624c2559..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 @@ -138,8 +139,8 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenFreeze.account == freeze_id._to_proto() -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenFreezeTransaction.""" +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() @@ -147,9 +148,9 @@ def test_from_protobuf(mock_account_ids): 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() - body = tx.build_transaction_body() - reconstructed = TokenFreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) assert isinstance(reconstructed, TokenFreezeTransaction) assert reconstructed.token_id == token_id_1 diff --git a/tests/unit/token_grant_kyc_transaction_test.py b/tests/unit/token_grant_kyc_transaction_test.py index 67af497ab..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,19 +147,20 @@ def test_grant_kyc_transaction_from_proto(mock_account_ids): assert from_proto.account_id == AccountId() -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenGrantKycTransaction.""" +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.operator_account_id = account_id + tx.transaction_id = TransactionId.generate(account_id) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenGrantKycTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, TokenGrantKycTransaction) assert reconstructed.token_id == token_id assert reconstructed.account_id == account_id diff --git a/tests/unit/token_mint_transaction_test.py b/tests/unit/token_mint_transaction_test.py index ef540e672..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 @@ -301,8 +302,8 @@ def test_build_scheduled_body_nft(mock_account_ids, metadata): assert schedulable_body.tokenMint.metadata == metadata -def test_from_protobuf_fungible(mock_account_ids): - """Test round-trip via _from_protobuf for fungible TokenMintTransaction.""" +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() @@ -310,16 +311,17 @@ def test_from_protobuf_fungible(mock_account_ids): tx.set_amount(500) tx.transaction_id = generate_transaction_id(payer_account) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenMintTransaction._from_protobuf(body, body.SerializeToString(), None) + 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_protobuf_nft(mock_account_ids): - """Test round-trip via _from_protobuf for NFT TokenMintTransaction.""" +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"] @@ -328,9 +330,10 @@ def test_from_protobuf_nft(mock_account_ids): tx.set_metadata(nft_metadata) tx.transaction_id = generate_transaction_id(payer_account) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenMintTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 7812b751d..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 @@ -163,16 +165,17 @@ def test_build_scheduled_body(token_id): assert schedulable_body.token_pause.token == token_id._to_proto() -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenPauseTransaction.""" +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.operator_account_id = account_id_sender + tx.transaction_id = TransactionId.generate(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenPauseTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, TokenPauseTransaction) assert reconstructed.token_id == token_id_1 diff --git a/tests/unit/token_unfreeze_transaction_test.py b/tests/unit/token_unfreeze_transaction_test.py index 1ce0515f3..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,8 +113,8 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenUnfreeze.account == freeze_id._to_proto() -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenUnfreezeTransaction.""" +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() @@ -121,10 +122,11 @@ def test_from_protobuf(mock_account_ids): 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() - body = tx.build_transaction_body() - reconstructed = TokenUnfreezeTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 diff --git a/tests/unit/token_unpause_transaction_test.py b/tests/unit/token_unpause_transaction_test.py index c941b3f14..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,18 +171,19 @@ def test_from_proto(mock_account_ids): assert unpause_tx.token_id.num == token_id.num -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TokenUnpauseTransaction.""" +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.operator_account_id = account_id_sender + tx.transaction_id = TransactionId.generate(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TokenUnpauseTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, TokenUnpauseTransaction) assert reconstructed.token_id == token_id_1 diff --git a/tests/unit/topic_create_transaction_test.py b/tests/unit/topic_create_transaction_test.py index 74b9037dd..3d994b358 100644 --- a/tests/unit/topic_create_transaction_test.py +++ b/tests/unit/topic_create_transaction_test.py @@ -24,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 @@ -577,8 +579,8 @@ def test_mixed_key_types_in_constructor(mock_account_ids): assert len(transaction_body.consensusCreateTopic.fee_exempt_key_list) == 2 -def test_from_protobuf(mock_account_ids): - """Test round-trip via _from_protobuf for TopicCreateTransaction.""" +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() @@ -591,12 +593,13 @@ def test_from_protobuf(mock_account_ids): tx.set_admin_key(admin_key) tx.set_submit_key(submit_key) tx.set_auto_renew_period(auto_renew_period) - tx.operator_account_id = account_id_sender + tx.transaction_id = TransactionId.generate(account_id_sender) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TopicCreateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 diff --git a/tests/unit/topic_delete_transaction_test.py b/tests/unit/topic_delete_transaction_test.py index 4e3aa847f..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 @@ -115,15 +117,16 @@ def test_execute_topic_delete_transaction(topic_id): assert receipt.status == ResponseCode.SUCCESS -def test_from_protobuf(mock_account_ids, topic_id): - """Test round-trip via _from_protobuf for TopicDeleteTransaction.""" +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.operator_account_id = AccountId(0, 0, 2) + tx.transaction_id = TransactionId.generate(AccountId(0, 0, 2)) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TopicDeleteTransaction._from_protobuf(body, body.SerializeToString(), None) + reconstructed = Transaction.from_bytes(tx.to_bytes()) + assert isinstance(reconstructed, TopicDeleteTransaction) assert reconstructed.topic_id == topic_id diff --git a/tests/unit/topic_update_transaction_test.py b/tests/unit/topic_update_transaction_test.py index cfe65c460..c1b539acf 100644 --- a/tests/unit/topic_update_transaction_test.py +++ b/tests/unit/topic_update_transaction_test.py @@ -20,6 +20,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 @@ -140,8 +142,8 @@ def test_execute_topic_update_transaction(topic_id): assert receipt.status == ResponseCode.SUCCESS -def test_from_protobuf(mock_account_ids, topic_id): - """Test round-trip via _from_protobuf for TopicUpdateTransaction.""" +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() @@ -156,12 +158,13 @@ def test_from_protobuf(mock_account_ids, topic_id): tx.set_submit_key(submit_key) tx.set_auto_renew_period(auto_renew_period) tx.set_auto_renew_account(auto_renew_account) - tx.operator_account_id = AccountId(0, 0, 2) + tx.transaction_id = TransactionId.generate(AccountId(0, 0, 2)) tx.node_account_id = node_account_id + tx.freeze() - body = tx.build_transaction_body() - reconstructed = TopicUpdateTransaction._from_protobuf(body, body.SerializeToString(), None) + 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 From ca2e7b6df2741be49b7b4eb6306aef6a3d43653b Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:19:05 +0300 Subject: [PATCH 13/28] fix: correct PRNG dispatch key from utilPrng to util_prng TransactionBody.WhichOneof('data') returns the snake_case field name 'util_prng', not camelCase 'utilPrng', causing from_bytes() to fail silently for PRNG transactions. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/transaction/transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hiero_sdk_python/transaction/transaction.py b/src/hiero_sdk_python/transaction/transaction.py index 1e31275b9..47afbc632 100644 --- a/src/hiero_sdk_python/transaction/transaction.py +++ b/src/hiero_sdk_python/transaction/transaction.py @@ -799,7 +799,7 @@ def _get_transaction_class(transaction_type: str): "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_airdrop_transaction_cancel.TokenCancelAirdropTransaction", From a2588349660056defd30a72ef3089bd7ca70c034 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:47:44 +0300 Subject: [PATCH 14/28] fix: add _from_topic_fee_proto for correct topic fee deserialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _to_topic_fee_proto() emits FixedCustomFee protos but _from_protobuf was calling _from_proto() which expects CustomFee — an incompatible wire format. Add _from_topic_fee_proto() to CustomFixedFee and use it in TopicCreateansaction and TopicUpdateTransaction deserialization. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- .../consensus/topic_create_transaction.py | 2 +- .../consensus/topic_update_transaction.py | 2 +- .../tokens/custom_fixed_fee.py | 21 +++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index 1d9e98511..b9d117d9c 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -268,6 +268,6 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: transaction.auto_renew_account = AccountId._from_proto(body.autoRenewAccount) if body.HasField("fee_schedule_key"): transaction.fee_schedule_key = PublicKey._from_proto(body.fee_schedule_key) - transaction.custom_fees = [CustomFixedFee._from_proto(f) for f in body.custom_fees] + transaction.custom_fees = [CustomFixedFee._from_topic_fee_proto(f) for f in body.custom_fees] transaction.fee_exempt_keys = [PublicKey._from_proto(k) for k in body.fee_exempt_key_list] return transaction diff --git a/src/hiero_sdk_python/consensus/topic_update_transaction.py b/src/hiero_sdk_python/consensus/topic_update_transaction.py index 6d166359d..c5829727b 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -331,7 +331,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: if body.HasField("fee_schedule_key"): transaction.fee_schedule_key = PublicKey._from_proto(body.fee_schedule_key) if body.HasField("custom_fees"): - transaction.custom_fees = [CustomFixedFee._from_proto(f) for f in body.custom_fees.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 = [PublicKey._from_proto(k) for k in body.fee_exempt_key_list.keys] 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..2c5f18195 100644 --- a/src/hiero_sdk_python/tokens/custom_fixed_fee.py +++ b/src/hiero_sdk_python/tokens/custom_fixed_fee.py @@ -283,6 +283,27 @@ 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 + + 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. From 87e867b1a4dfb0deea959f74bcbc440d6bfe3ed3 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:56:02 +0300 Subject: [PATCH 15/28] fix: use Key.from_proto_key() for composite key deserialization PublicKey._from_proto() only handles primitive ed25519/ECDSA keys and raises on KeyList or ThresholdKey. Replace with Key.from_proto_key() which dispatches correctly across all key variants. Affects: account_update, account_create, topic_create, file_update, token_update _from_protobuf methods. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- .../account/account_create_transaction.py | 3 +-- .../account/account_update_transaction.py | 3 +-- .../consensus/topic_create_transaction.py | 10 +++++----- .../file/file_update_transaction.py | 3 ++- .../tokens/token_update_transaction.py | 16 ++++++++-------- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/hiero_sdk_python/account/account_create_transaction.py b/src/hiero_sdk_python/account/account_create_transaction.py index 768e00665..9571a21a8 100644 --- a/src/hiero_sdk_python/account/account_create_transaction.py +++ b/src/hiero_sdk_python/account/account_create_transaction.py @@ -10,7 +10,6 @@ from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key import Key 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.executable import _Method from hiero_sdk_python.hapi.services import crypto_create_pb2, duration_pb2, transaction_pb2 @@ -380,7 +379,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): if transaction_body.HasField("cryptoCreateAccount"): body = transaction_body.cryptoCreateAccount if body.HasField("key"): - transaction.key = PublicKey._from_proto(body.key) + transaction.key = Key.from_proto_key(body.key) transaction.initial_balance = body.initialBalance transaction.receiver_signature_required = body.receiverSigRequired if body.HasField("autoRenewPeriod"): diff --git a/src/hiero_sdk_python/account/account_update_transaction.py b/src/hiero_sdk_python/account/account_update_transaction.py index 33b3537ad..4723e9b4a 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -10,7 +10,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.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services.crypto_update_pb2 import CryptoUpdateTransactionBody @@ -368,7 +367,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: if body.HasField("accountIDToUpdate"): transaction.account_id = AccountId._from_proto(body.accountIDToUpdate) if body.HasField("key"): - transaction.key = PublicKey._from_proto(body.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"): diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index b9d117d9c..689776201 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -19,7 +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.public_key import PublicKey +from hiero_sdk_python.crypto.key import Key as CryptoKey from hiero_sdk_python.utils.key_utils import Key, key_to_proto @@ -259,15 +259,15 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: body = transaction_body.consensusCreateTopic transaction.memo = body.memo if body.memo else "" if body.HasField("adminKey"): - transaction.admin_key = PublicKey._from_proto(body.adminKey) + transaction.admin_key = CryptoKey.from_proto_key(body.adminKey) if body.HasField("submitKey"): - transaction.submit_key = PublicKey._from_proto(body.submitKey) + transaction.submit_key = CryptoKey.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 = PublicKey._from_proto(body.fee_schedule_key) + transaction.fee_schedule_key = CryptoKey.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 = [PublicKey._from_proto(k) for k in body.fee_exempt_key_list] + transaction.fee_exempt_keys = [CryptoKey.from_proto_key(k) for k in body.fee_exempt_key_list] 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 b267e09de..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 @@ -222,7 +223,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): body = transaction_body.fileUpdate if body.HasField("fileID"): transaction.file_id = FileId._from_proto(body.fileID) - transaction.keys = [PublicKey._from_proto(k) for k in body.keys.keys] if body.keys.keys else None + 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) diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index 6c2ff4ccc..782ad595f 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -492,21 +492,21 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: 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 = PublicKey._from_proto(body.adminKey) + transaction.admin_key = Key.from_proto_key(body.adminKey) if body.HasField("freezeKey"): - transaction.freeze_key = PublicKey._from_proto(body.freezeKey) + transaction.freeze_key = Key.from_proto_key(body.freezeKey) if body.HasField("wipeKey"): - transaction.wipe_key = PublicKey._from_proto(body.wipeKey) + transaction.wipe_key = Key.from_proto_key(body.wipeKey) if body.HasField("supplyKey"): - transaction.supply_key = PublicKey._from_proto(body.supplyKey) + transaction.supply_key = Key.from_proto_key(body.supplyKey) if body.HasField("metadata_key"): - transaction.metadata_key = PublicKey._from_proto(body.metadata_key) + transaction.metadata_key = Key.from_proto_key(body.metadata_key) if body.HasField("pause_key"): - transaction.pause_key = PublicKey._from_proto(body.pause_key) + transaction.pause_key = Key.from_proto_key(body.pause_key) if body.HasField("kycKey"): - transaction.kyc_key = PublicKey._from_proto(body.kycKey) + transaction.kyc_key = Key.from_proto_key(body.kycKey) if body.HasField("fee_schedule_key"): - transaction.fee_schedule_key = PublicKey._from_proto(body.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: From 27a1ab080b27e7ea76d760ea9b1ebeadca23b56c Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:56:27 +0300 Subject: [PATCH 16/28] fix: preserve amount=0 in TokenWipeTransaction deserialization uint64 zero is a valid wipe amount. The conditional 'body.amount if body.amount else None' incorrectly treated zero as absent. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/token_wipe_transaction.py | 2 +- tests/unit/token_wipe_transaction_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hiero_sdk_python/tokens/token_wipe_transaction.py b/src/hiero_sdk_python/tokens/token_wipe_transaction.py index 4a42dbcdf..377c4727e 100644 --- a/src/hiero_sdk_python/tokens/token_wipe_transaction.py +++ b/src/hiero_sdk_python/tokens/token_wipe_transaction.py @@ -158,7 +158,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): transaction.token_id = TokenId._from_proto(body.token) if body.HasField("account"): transaction.account_id = AccountId._from_proto(body.account) - transaction.amount = body.amount if body.amount else None + transaction.amount = body.amount transaction.serial = list(body.serialNumbers) return transaction diff --git a/tests/unit/token_wipe_transaction_test.py b/tests/unit/token_wipe_transaction_test.py index 3625f1158..85e8f8cea 100644 --- a/tests/unit/token_wipe_transaction_test.py +++ b/tests/unit/token_wipe_transaction_test.py @@ -237,4 +237,4 @@ def test_from_bytes_nft(mock_account_ids): assert reconstructed.token_id == token_id_1 assert reconstructed.account_id == account_id_sender assert list(reconstructed.serial) == serial - assert reconstructed.amount is None + assert reconstructed.amount == 0 From 9e73ddab5b74e92259abda394af79fbc80c8bdb5 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:56:54 +0300 Subject: [PATCH 17/28] fix: validate pending airdrop IDs after _from_protobuf deserialization _from_proto() calls _validate_all() but the new _from_protobuf path skipped it, allowing invalid (duplicate or >10) pending_airdrops to deserialize into an invalid transaction object. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/token_airdrop_claim.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hiero_sdk_python/tokens/token_airdrop_claim.py b/src/hiero_sdk_python/tokens/token_airdrop_claim.py index 2ee650e26..91b5b3d02 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_claim.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_claim.py @@ -123,6 +123,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: transaction._pending_airdrop_ids = [ PendingAirdropId._from_proto(a) for a in body.pending_airdrops ] + transaction._validate_all(transaction._pending_airdrop_ids) return transaction @classmethod From 193db6509b5459921a816d4c5fea4f4b911d9cc2 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:57:17 +0300 Subject: [PATCH 18/28] fix: preserve empty memo and False wait_for_expiry in ScheduleCreateTransaction Collapsing falsy proto scalars to None breaks round-trip symmetry: an explicit empty memo or wait_for_expiry=False would be lost on deserialization. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/schedule/schedule_create_transaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hiero_sdk_python/schedule/schedule_create_transaction.py b/src/hiero_sdk_python/schedule/schedule_create_transaction.py index 2ca5322d3..991048d7b 100644 --- a/src/hiero_sdk_python/schedule/schedule_create_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_create_transaction.py @@ -241,8 +241,8 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): transaction.admin_key = PublicKey._from_proto(body.adminKey) if body.HasField("scheduledTransactionBody"): transaction.schedulable_body = body.scheduledTransactionBody - transaction.schedule_memo = body.memo if body.memo else None + 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 if body.wait_for_expiry else None + transaction.wait_for_expiry = body.wait_for_expiry return transaction From 2f07aeea94cb789fd5b5b7d88d7890d6f683dfde Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 10:57:40 +0300 Subject: [PATCH 19/28] fix: use HasField for fileID and normalize contents in FileAppendTransaction Truthiness check on a proto3 message field is unreliable; HasField() is the correct way to detect presence. Empty contents normalized to None for consistency with the SDK model. Signed-off-by: Yuval Goihberg Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/file/file_append_transaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hiero_sdk_python/file/file_append_transaction.py b/src/hiero_sdk_python/file/file_append_transaction.py index a4cc6abd6..a57296b64 100644 --- a/src/hiero_sdk_python/file/file_append_transaction.py +++ b/src/hiero_sdk_python/file/file_append_transaction.py @@ -259,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 From 97eb501a99c18fcde3cd0a6c243f3a44587648c7 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 11:26:13 +0300 Subject: [PATCH 20/28] fix: add HasField validation for required fixed_fee in CustomFixedFee _from_proto and _from_topic_fee_proto now raise ValueError when fixed_fee is absent, matching the documented raises clause. Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/custom_fixed_fee.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hiero_sdk_python/tokens/custom_fixed_fee.py b/src/hiero_sdk_python/tokens/custom_fixed_fee.py index 2c5f18195..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 @@ -288,6 +290,8 @@ def _from_topic_fee_proto(cls, proto_fee: custom_fees_pb2.FixedCustomFee) -> Cus """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 From bcf8816f664969e592dbc3bc62dc1980c6a7359a Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 11:28:29 +0300 Subject: [PATCH 21/28] fix: correct AccountCreateTransaction auto_renew_period round-trip Reset auto_renew_period to None before the HasField guard so a proto without autoRenewPeriod doesn't inherit the constructor default. Guard _build_proto_body against None auto_renew_period. Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/account/account_create_transaction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hiero_sdk_python/account/account_create_transaction.py b/src/hiero_sdk_python/account/account_create_transaction.py index 9571a21a8..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, @@ -382,6 +382,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): 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 From d8489a5383146a509cae6dbce1c3d1177ca10439 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 11:46:45 +0300 Subject: [PATCH 22/28] fix: preserve absent fields during TopicUpdateTransaction deserialization Signed-off-by: Yuval Goihberg --- .../consensus/topic_update_transaction.py | 15 +++++++++------ src/hiero_sdk_python/transaction/transaction.py | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/hiero_sdk_python/consensus/topic_update_transaction.py b/src/hiero_sdk_python/consensus/topic_update_transaction.py index c5829727b..20b1944fe 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -10,6 +10,7 @@ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.consensus.topic_id import TopicId +from hiero_sdk_python.crypto.key import Key as CryptoKey from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method @@ -32,7 +33,7 @@ def __init__( memo: str | None = None, admin_key: PublicKey | None = None, submit_key: PublicKey | None = None, - auto_renew_period: Duration | None = Duration(7890000), + auto_renew_period: Duration | None = None, auto_renew_account: AccountId | None = None, expiration_time: Timestamp | None = None, custom_fees: list[CustomFixedFee] | None = None, @@ -53,7 +54,7 @@ def __init__( """ super().__init__() self.topic_id: TopicId | None = topic_id - self.memo: str = memo or "" + self.memo: str | None = memo self.admin_key: PublicKey | None = admin_key self.submit_key: PublicKey | None = submit_key self.auto_renew_period: Duration | None = auto_renew_period @@ -314,14 +315,16 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: 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 = PublicKey._from_proto(body.adminKey) + transaction.admin_key = CryptoKey.from_proto_key(body.adminKey) if body.HasField("submitKey"): - transaction.submit_key = PublicKey._from_proto(body.submitKey) + transaction.submit_key = CryptoKey.from_proto_key(body.submitKey) if body.HasField("autoRenewPeriod"): transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod) if body.HasField("autoRenewAccount"): @@ -329,9 +332,9 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: if body.HasField("expirationTime"): transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime) if body.HasField("fee_schedule_key"): - transaction.fee_schedule_key = PublicKey._from_proto(body.fee_schedule_key) + transaction.fee_schedule_key = CryptoKey.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 = [PublicKey._from_proto(k) for k in body.fee_exempt_key_list.keys] + transaction.fee_exempt_keys = [CryptoKey.from_proto_key(k) for k in body.fee_exempt_key_list.keys] return transaction diff --git a/src/hiero_sdk_python/transaction/transaction.py b/src/hiero_sdk_python/transaction/transaction.py index 47afbc632..a31d1d23b 100644 --- a/src/hiero_sdk_python/transaction/transaction.py +++ b/src/hiero_sdk_python/transaction/transaction.py @@ -469,7 +469,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) @@ -494,7 +494,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) From dc74961f28a5cf48e92e47afdae6fce88d72cd58 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 11:54:04 +0300 Subject: [PATCH 23/28] test: improve round-trip coverage in from_bytes tests Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- ...ccount_allowance_approve_transaction_test.py | 2 ++ tests/unit/file_create_transaction_test.py | 8 ++++++++ tests/unit/file_update_transaction_test.py | 9 +++++++++ tests/unit/token_revoke_kyc_transaction_test.py | 2 ++ tests/unit/token_update_transaction_test.py | 17 ++++++++++++++++- 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/unit/account_allowance_approve_transaction_test.py b/tests/unit/account_allowance_approve_transaction_test.py index 3627f42d8..b71f7f07c 100644 --- a/tests/unit/account_allowance_approve_transaction_test.py +++ b/tests/unit/account_allowance_approve_transaction_test.py @@ -375,6 +375,7 @@ def test_from_bytes(mock_account_ids): 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 @@ -384,3 +385,4 @@ def test_from_bytes(mock_account_ids): 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/file_create_transaction_test.py b/tests/unit/file_create_transaction_test.py index cc9b203af..db6237a16 100644 --- a/tests/unit/file_create_transaction_test.py +++ b/tests/unit/file_create_transaction_test.py @@ -268,9 +268,14 @@ 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() @@ -280,3 +285,6 @@ def test_from_bytes(mock_account_ids): 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_update_transaction_test.py b/tests/unit/file_update_transaction_test.py index 9984807cf..766ab2f2a 100644 --- a/tests/unit/file_update_transaction_test.py +++ b/tests/unit/file_update_transaction_test.py @@ -366,10 +366,15 @@ 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() @@ -379,3 +384,7 @@ def test_from_bytes(mock_account_ids): 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/token_revoke_kyc_transaction_test.py b/tests/unit/token_revoke_kyc_transaction_test.py index 6a7520795..750ba7c2b 100644 --- a/tests/unit/token_revoke_kyc_transaction_test.py +++ b/tests/unit/token_revoke_kyc_transaction_test.py @@ -156,6 +156,8 @@ def test_from_bytes(mock_account_ids): 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): diff --git a/tests/unit/token_update_transaction_test.py b/tests/unit/token_update_transaction_test.py index 7409b0f31..1812ad466 100644 --- a/tests/unit/token_update_transaction_test.py +++ b/tests/unit/token_update_transaction_test.py @@ -470,14 +470,22 @@ def test_build_transaction_body_with_keys(mock_account_ids, key_type, use_privat verify_key_in_proto(transaction_body.tokenUpdate.freezeKey, expected_freeze_public, key_type) -def test_from_bytes(mock_account_ids): +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() @@ -488,3 +496,10 @@ def test_from_bytes(mock_account_ids): 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() From def6f8d4387111e8fcb37369cdd68e314afaeb89 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 18:25:12 +0300 Subject: [PATCH 24/28] feat: implement _from_protobuf for contract, node, ethereum, and prng transactions Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Yuval Goihberg --- .../contract/contract_create_transaction.py | 33 +++++++++++++++++++ .../contract/contract_delete_transaction.py | 15 +++++++++ .../contract/contract_execute_transaction.py | 12 +++++++ .../contract/contract_update_transaction.py | 32 ++++++++++++++++++ .../contract/ethereum_transaction.py | 11 +++++++ .../nodes/node_create_transaction.py | 20 +++++++++++ .../nodes/node_delete_transaction.py | 8 +++++ .../nodes/node_update_transaction.py | 25 ++++++++++++++ src/hiero_sdk_python/prng_transaction.py | 8 +++++ .../tokens/token_update_transaction.py | 3 +- 10 files changed, 166 insertions(+), 1 deletion(-) 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/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/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index 782ad595f..a5586e574 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -25,8 +25,9 @@ from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.utils.key_utils import Key, key_to_proto +from hiero_sdk_python.utils.key_utils import key_to_proto @dataclass From 0f9b3e39b9ea412868f6aefb94893380366136e8 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Thu, 23 Apr 2026 18:34:06 +0300 Subject: [PATCH 25/28] fix: remove unused import Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/token_update_transaction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index a5586e574..e98f3ddc1 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -26,7 +26,6 @@ from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.crypto.key import Key -from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.utils.key_utils import key_to_proto From be7ab5d46d8967431a1960b6ae7ad2a07d660539 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Sun, 26 Apr 2026 09:47:31 +0300 Subject: [PATCH 26/28] fix: remove CryptoKey import as Signed-off-by: Yuval Goihberg --- .../consensus/topic_create_transaction.py | 13 ++++++------- .../consensus/topic_update_transaction.py | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index 12d9efd01..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,8 +19,8 @@ ) 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 as CryptoKey -from hiero_sdk_python.utils.key_utils import Key, key_to_proto +from hiero_sdk_python.crypto.key import Key +from hiero_sdk_python.utils.key_utils import key_to_proto class TopicCreateTransaction(Transaction): @@ -260,15 +259,15 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: body = transaction_body.consensusCreateTopic transaction.memo = body.memo if body.memo else "" if body.HasField("adminKey"): - transaction.admin_key = CryptoKey.from_proto_key(body.adminKey) + transaction.admin_key = Key.from_proto_key(body.adminKey) if body.HasField("submitKey"): - transaction.submit_key = CryptoKey.from_proto_key(body.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 = CryptoKey.from_proto_key(body.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 = [CryptoKey.from_proto_key(k) for k in body.fee_exempt_key_list] + 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_update_transaction.py b/src/hiero_sdk_python/consensus/topic_update_transaction.py index 20b1944fe..a4897ce45 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -10,7 +10,7 @@ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.consensus.topic_id import TopicId -from hiero_sdk_python.crypto.key import Key as CryptoKey +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 @@ -322,9 +322,9 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: if body.HasField("memo"): transaction.memo = body.memo.value if body.HasField("adminKey"): - transaction.admin_key = CryptoKey.from_proto_key(body.adminKey) + transaction.admin_key = Key.from_proto_key(body.adminKey) if body.HasField("submitKey"): - transaction.submit_key = CryptoKey.from_proto_key(body.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"): @@ -332,9 +332,9 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: if body.HasField("expirationTime"): transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime) if body.HasField("fee_schedule_key"): - transaction.fee_schedule_key = CryptoKey.from_proto_key(body.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 = [CryptoKey.from_proto_key(k) for k in body.fee_exempt_key_list.keys] + transaction.fee_exempt_keys = [Key.from_proto_key(k) for k in body.fee_exempt_key_list.keys] return transaction From 641ffdc3a683a61e2047f188971dbb8a4c6f0b96 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 11 May 2026 10:39:40 +0300 Subject: [PATCH 27/28] Update token_update_transaction.py Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/token_update_transaction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index feca2d921..2dbc38389 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -26,7 +26,6 @@ from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation 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 From f59e80c751254fc7f30a49ef329294803fb06de2 Mon Sep 17 00:00:00 2001 From: Yuval Goihberg Date: Mon, 11 May 2026 10:43:57 +0300 Subject: [PATCH 28/28] Remove unused import for Key from key_utils Signed-off-by: Yuval Goihberg --- src/hiero_sdk_python/tokens/token_create_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hiero_sdk_python/tokens/token_create_transaction.py b/src/hiero_sdk_python/tokens/token_create_transaction.py index dd9f84c8e..035ee43ab 100644 --- a/src/hiero_sdk_python/tokens/token_create_transaction.py +++ b/src/hiero_sdk_python/tokens/token_create_transaction.py @@ -31,7 +31,7 @@ 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, key_to_proto +from hiero_sdk_python.utils.key_utils import key_to_proto AUTO_RENEW_PERIOD = Duration(7890000) # around 90 days in seconds