Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
de3f02f
fix: correct _get_transaction_class map keys and missing entries
yuval99g Apr 20, 2026
d15785d
feat: implement _from_protobuf for account transactions
yuval99g Apr 20, 2026
216fce8
feat: implement _from_protobuf for token transactions
yuval99g Apr 20, 2026
dad80e3
feat: implement _from_protobuf for consensus, file, schedule, and sys…
yuval99g Apr 20, 2026
9ed28ba
test: add _from_protobuf round-trip tests for all transaction types
yuval99g Apr 20, 2026
79a524c
fix: remove duplicate CryptoUpdateTransactionBody import in account_u…
yuval99g Apr 20, 2026
386290b
chore: suppress cyclomatic complexity warnings on _from_protobuf methods
yuval99g Apr 20, 2026
e67e7b6
chore: suppress cyclomatic complexity warning on AccountUpdateTransac…
yuval99g Apr 20, 2026
bbe3d4a
refactor: move PublicKey imports to module level in _from_protobuf files
yuval99g Apr 20, 2026
ec1098a
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g Apr 20, 2026
95c0360
refactor: move local imports to module level in
yuval99g Apr 20, 2026
49657e7
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g Apr 21, 2026
5a3e93f
test: update _from_protobuf tests to use Transaction.from_bytes()
yuval99g Apr 21, 2026
c01a7ff
test: update _from_protobuf tests to use
yuval99g Apr 21, 2026
ca2e7b6
fix: correct PRNG dispatch key from utilPrng to util_prng
yuval99g Apr 23, 2026
a258834
fix: add _from_topic_fee_proto for correct topic fee deserialization
yuval99g Apr 23, 2026
87e867b
fix: use Key.from_proto_key() for composite key deserialization
yuval99g Apr 23, 2026
27a1ab0
fix: preserve amount=0 in
yuval99g Apr 23, 2026
9e73dda
fix: validate pending airdrop IDs after _from_protobuf deserialization
yuval99g Apr 23, 2026
193db65
fix: preserve empty memo and False wait_for_expiry in ScheduleCreateT…
yuval99g Apr 23, 2026
2f07aee
fix: use HasField for fileID and normalize contents in FileAppendTran…
yuval99g Apr 23, 2026
97eb501
fix: add HasField validation for required fixed_fee in CustomFixedFee
yuval99g Apr 23, 2026
bcf8816
fix: correct AccountCreateTransaction auto_renew_period round-trip
yuval99g Apr 23, 2026
d8489a5
fix: preserve absent fields during TopicUpdateTransaction deserializa…
yuval99g Apr 23, 2026
dc74961
test: improve round-trip coverage in from_bytes tests
yuval99g Apr 23, 2026
1b40987
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g Apr 23, 2026
def6f8d
feat: implement _from_protobuf for contract, node, ethereum, and prng…
yuval99g Apr 23, 2026
0f9b3e3
fix: remove unused import
yuval99g Apr 23, 2026
8858c14
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g Apr 26, 2026
be7ab5d
fix: remove CryptoKey import as
yuval99g Apr 26, 2026
34d8c18
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g May 7, 2026
64b64ba
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g May 10, 2026
641ffdc
Update token_update_transaction.py
yuval99g May 11, 2026
f59e80c
Remove unused import for Key from key_utils
yuval99g May 11, 2026
673f505
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g May 12, 2026
5f0b320
Merge branch 'main' into feat/implement-from-protobuf-deserialization
yuval99g May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 25 additions & 1 deletion src/hiero_sdk_python/account/account_create_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -371,3 +371,27 @@ def _get_method(self, channel: _Channel) -> _Method:
_Method: An instance of _Method containing the transaction and query functions.
"""
return _Method(transaction_func=channel.crypto.createAccount, query_func=None)

@classmethod
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):

transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
if transaction_body.HasField("cryptoCreateAccount"):
body = transaction_body.cryptoCreateAccount
if body.HasField("key"):
transaction.key = Key.from_proto_key(body.key)
transaction.initial_balance = body.initialBalance
transaction.receiver_signature_required = body.receiverSigRequired
transaction.auto_renew_period = None
if body.HasField("autoRenewPeriod"):
transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod)
transaction.account_memo = body.memo if body.memo else None
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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
11 changes: 11 additions & 0 deletions src/hiero_sdk_python/account/account_delete_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 28 additions & 0 deletions src/hiero_sdk_python/account/account_update_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,31 @@ def _get_method(self, channel: _Channel) -> _Method:
_Method: An object containing the transaction function to update an account.
"""
return _Method(transaction_func=channel.crypto.updateAccount, query_func=None)

@classmethod
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
if transaction_body.HasField("cryptoUpdateAccount"):
body = transaction_body.cryptoUpdateAccount
if body.HasField("accountIDToUpdate"):
transaction.account_id = AccountId._from_proto(body.accountIDToUpdate)
if body.HasField("key"):
transaction.key = Key.from_proto_key(body.key)
if body.HasField("autoRenewPeriod"):
transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod)
if body.HasField("memo"):
transaction.account_memo = body.memo.value
if body.HasField("expirationTime"):
transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime)
if body.HasField("receiverSigRequiredWrapper"):
transaction.receiver_signature_required = body.receiverSigRequiredWrapper.value
if body.HasField("max_automatic_token_associations"):
transaction.max_automatic_token_associations = body.max_automatic_token_associations.value
if body.HasField("decline_reward"):
transaction.decline_staking_reward = body.decline_reward.value
staked_id = body.WhichOneof("staked_id")
if staked_id == "staked_account_id":
transaction.staked_account_id = AccountId._from_proto(body.staked_account_id)
elif staked_id == "staked_node_id":
transaction.staked_node_id = body.staked_node_id
return transaction
22 changes: 21 additions & 1 deletion src/hiero_sdk_python/consensus/topic_create_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +19,7 @@
)
from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee
from hiero_sdk_python.transaction.transaction import Transaction
from hiero_sdk_python.crypto.key import Key
from hiero_sdk_python.utils.key_utils import key_to_proto


Expand Down Expand Up @@ -251,3 +251,23 @@ def _get_method(self, channel: _Channel) -> _Method:
_Method: The method for executing the transaction.
"""
return _Method(transaction_func=channel.topic.createTopic, query_func=None)

@classmethod
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
if transaction_body.HasField("consensusCreateTopic"):
body = transaction_body.consensusCreateTopic
transaction.memo = body.memo if body.memo else ""
if body.HasField("adminKey"):
transaction.admin_key = Key.from_proto_key(body.adminKey)
if body.HasField("submitKey"):
transaction.submit_key = Key.from_proto_key(body.submitKey)
if body.HasField("autoRenewPeriod"):
transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod)
if body.HasField("autoRenewAccount"):
transaction.auto_renew_account = AccountId._from_proto(body.autoRenewAccount)
if body.HasField("fee_schedule_key"):
transaction.fee_schedule_key = Key.from_proto_key(body.fee_schedule_key)
transaction.custom_fees = [CustomFixedFee._from_topic_fee_proto(f) for f in body.custom_fees]
transaction.fee_exempt_keys = [Key.from_proto_key(k) for k in body.fee_exempt_key_list]
return transaction
9 changes: 9 additions & 0 deletions src/hiero_sdk_python/consensus/topic_delete_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
yuval99g marked this conversation as resolved.

def freeze_with(self, client: Client) -> TopicMessageSubmitTransaction:
if self._transaction_body_bytes:
return self
Expand Down
29 changes: 29 additions & 0 deletions src/hiero_sdk_python/consensus/topic_update_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,32 @@ def _get_method(self, channel: _Channel) -> _Method:
_Method: The method to execute the transaction.
"""
return _Method(transaction_func=channel.topic.updateTopic, query_func=None)

@classmethod
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): # noqa: PLR0912
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
if transaction_body.HasField("consensusUpdateTopic"):
body = transaction_body.consensusUpdateTopic
transaction.memo = None
transaction.auto_renew_period = None
if body.HasField("topicID"):
transaction.topic_id = TopicId._from_proto(body.topicID)
if body.HasField("memo"):
transaction.memo = body.memo.value
if body.HasField("adminKey"):
transaction.admin_key = Key.from_proto_key(body.adminKey)
if body.HasField("submitKey"):
transaction.submit_key = Key.from_proto_key(body.submitKey)
if body.HasField("autoRenewPeriod"):
transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod)
if body.HasField("autoRenewAccount"):
transaction.auto_renew_account = AccountId._from_proto(body.autoRenewAccount)
if body.HasField("expirationTime"):
transaction.expiration_time = Timestamp._from_protobuf(body.expirationTime)
if body.HasField("fee_schedule_key"):
transaction.fee_schedule_key = Key.from_proto_key(body.fee_schedule_key)
if body.HasField("custom_fees"):
transaction.custom_fees = [CustomFixedFee._from_topic_fee_proto(f) for f in body.custom_fees.fees]
if body.HasField("fee_exempt_key_list"):
transaction.fee_exempt_keys = [Key.from_proto_key(k) for k in body.fee_exempt_key_list.keys]
return transaction
Comment thread
coderabbitai[bot] marked this conversation as resolved.
33 changes: 33 additions & 0 deletions src/hiero_sdk_python/contract/contract_create_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions src/hiero_sdk_python/contract/contract_delete_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions src/hiero_sdk_python/contract/contract_execute_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 32 additions & 0 deletions src/hiero_sdk_python/contract/contract_update_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading