88from hiero_sdk_python .crypto .key import Key
99from hiero_sdk_python .exceptions import PrecheckError
1010from hiero_sdk_python .executable import _Executable , _ExecutionState
11+ from hiero_sdk_python .hapi .sdk .transaction_list_pb2 import TransactionList
1112from hiero_sdk_python .hapi .services import basic_types_pb2 , transaction_contents_pb2 , transaction_pb2
1213from hiero_sdk_python .hapi .services .schedulable_transaction_body_pb2 import SchedulableTransactionBody
1314from hiero_sdk_python .hapi .services .transaction_response_pb2 import TransactionResponse as TransactionResponseProto
@@ -655,13 +656,41 @@ def to_bytes(self) -> bytes:
655656 Raises:
656657 Exception: If the transaction has not been frozen yet.
657658 """
658- self . _require_frozen ()
659+ transaction_list = TransactionList ()
659660
660- # Get the transaction protobuf
661- transaction_proto = self ._to_proto ()
661+ if len ( self . node_account_ids ) == 0 :
662+ transaction_body = self .build_transaction_body ()
662663
663- # Serialize to bytes
664- return transaction_proto .SerializeToString ()
664+ if self .transaction_id is not None :
665+ transaction_body .transactionID .CopyFrom (self .transaction_id ._to_proto ())
666+
667+ transaction = transaction_contents_pb2 .SignedTransaction (bodyBytes = transaction_body .SerializeToString ())
668+
669+ transaction_list .transaction_list .append (
670+ transaction_pb2 .Transaction (signedTransactionBytes = transaction .SerializeToString ())
671+ )
672+
673+ else :
674+ transaction_body = self .build_transaction_body ()
675+
676+ if self .transaction_id is not None :
677+ transaction_body .transactionID .CopyFrom (self .transaction_id ._to_proto ())
678+
679+ for node_account_id in self .node_account_ids :
680+ transaction_body .nodeAccountID .CopyFrom (node_account_id ._to_proto ())
681+ transaction = transaction_contents_pb2 .SignedTransaction (bodyBytes = transaction_body .SerializeToString ())
682+
683+ if self ._transaction_body_bytes :
684+ if self ._signature_map and self ._signature_map [transaction_body .SerializeToString ()]:
685+ transaction .sigMap .CopyFrom (self ._signature_map [transaction_body .SerializeToString ()])
686+ else :
687+ transaction .sigMap .CopyFrom (basic_types_pb2 .SignatureMap (sigPair = []))
688+
689+ transaction_list .transaction_list .append (
690+ transaction_pb2 .Transaction (signedTransactionBytes = transaction .SerializeToString ())
691+ )
692+
693+ return transaction_list .SerializeToString ()
665694
666695 @staticmethod
667696 def from_bytes (transaction_bytes : bytes ):
@@ -726,42 +755,116 @@ def from_bytes(transaction_bytes: bytes):
726755 """
727756 if not isinstance (transaction_bytes , bytes ):
728757 raise ValueError ("transaction_bytes must be bytes" )
729-
730758 if len (transaction_bytes ) == 0 :
731759 raise ValueError ("transaction_bytes cannot be empty" )
732760
733761 try :
734- transaction_proto = transaction_pb2 .Transaction ()
735- transaction_proto .ParseFromString (transaction_bytes )
736- except Exception as e :
737- raise ValueError (f"Failed to parse transaction bytes: { e } " ) from e
762+ return Transaction ._process_transaction_list_bytes (transaction_bytes )
763+ except Exception :
764+ try :
765+ return Transaction ._process_single_transaction_bytes (transaction_bytes )
766+ except Exception as e :
767+ raise ValueError (f"Failed to parse transaction_bytes { e } " ) from e
738768
769+ @staticmethod
770+ def _process_base_transaction_bytes (transaction_proto : transaction_pb2 .Transaction ):
739771 try :
740772 signed_transaction = transaction_contents_pb2 .SignedTransaction ()
741773 signed_transaction .ParseFromString (transaction_proto .signedTransactionBytes )
742774 except Exception as e :
743775 raise ValueError (f"Failed to parse signed transaction: { e } " ) from e
744-
745776 try :
746777 transaction_body = transaction_pb2 .TransactionBody ()
747778 transaction_body .ParseFromString (signed_transaction .bodyBytes )
748779 except Exception as e :
749780 raise ValueError (f"Failed to parse transaction body: { e } " ) from e
750-
751781 transaction_type = transaction_body .WhichOneof ("data" )
752-
753782 if transaction_type is None :
754783 raise ValueError ("Transaction body does not contain any transaction data" )
755-
756784 transaction_class = Transaction ._get_transaction_class (transaction_type )
757-
758785 if transaction_class is None :
759786 raise ValueError (f"Unknown transaction type: { transaction_type } " )
760787
761- return transaction_class ._from_protobuf (
762- transaction_body , signed_transaction .bodyBytes , signed_transaction .sigMap
788+ return transaction_class , transaction_body , signed_transaction
789+
790+ # Deprecated, for backward compatiblity only
791+ @staticmethod
792+ def _process_single_transaction_bytes (transaction_bytes ):
793+ try :
794+ transaction_proto = transaction_pb2 .Transaction ()
795+ transaction_proto .ParseFromString (transaction_bytes )
796+ except Exception as e :
797+ raise ValueError (f"Failed to parse transaction bytes: { e } " ) from e
798+
799+ transaction_class , transaction_body , signed_transaction = Transaction ._process_base_transaction_bytes (
800+ transaction_proto
763801 )
764802
803+ restore_transaction : Transaction = transaction_class ._from_protobuf (transaction_body )
804+
805+ if restore_transaction ._node_account_id is not None :
806+ restore_transaction .set_node_account_id (restore_transaction ._node_account_id )
807+
808+ if signed_transaction .HasField ("sigMap" ):
809+ if signed_transaction .sigMap .sigPair :
810+ restore_transaction ._signature_map [signed_transaction .bodyBytes ] = signed_transaction .sigMap
811+
812+ restore_transaction ._transaction_body_bytes [restore_transaction ._node_account_id ] = (
813+ signed_transaction .bodyBytes
814+ )
815+
816+ return restore_transaction
817+
818+ @staticmethod
819+ def _process_transaction_list_bytes (transaction_bytes : bytes ):
820+ transaction_list = TransactionList ()
821+ transaction_list .ParseFromString (transaction_bytes )
822+
823+ if not transaction_list .transaction_list :
824+ raise ValueError ("TransactionList contains no transactions" )
825+
826+ restored_transaction : Transaction = None
827+
828+ for transaction_proto in transaction_list .transaction_list :
829+ transaction_class , transaction_body , signed_transaction = Transaction ._process_base_transaction_bytes (
830+ transaction_proto
831+ )
832+
833+ tmp_transaction : Transaction = transaction_class ._from_protobuf (transaction_body )
834+
835+ node_id = tmp_transaction ._node_account_id
836+
837+ if restored_transaction is None :
838+ restored_transaction = tmp_transaction
839+
840+ if node_id is not None :
841+ restored_transaction .node_account_ids = [node_id ]
842+
843+ if tmp_transaction ._signature_map :
844+ for key , value in tmp_transaction ._signature_map .items ():
845+ restored_transaction ._signature_map [key ] = value
846+ restored_transaction ._transaction_body_bytes [node_id ] = key
847+
848+ else :
849+ if node_id is not None and node_id not in restored_transaction .node_account_ids :
850+ restored_transaction .node_account_ids .append (node_id )
851+
852+ if tmp_transaction ._signature_map :
853+ for key , value in tmp_transaction ._signature_map .items ():
854+ restored_transaction ._signature_map [key ] = value
855+ restored_transaction ._transaction_body_bytes [node_id ] = key
856+
857+ if signed_transaction .HasField ("sigMap" ):
858+ if signed_transaction .sigMap .sigPair :
859+ restored_transaction ._signature_map [signed_transaction .bodyBytes ] = signed_transaction .sigMap
860+
861+ restored_transaction ._transaction_body_bytes [node_id ] = signed_transaction .bodyBytes
862+
863+ if restored_transaction is None :
864+ raise ValueError ("No valid transactions could be parsed from the byte stream" )
865+
866+ return restored_transaction
867+
765868 @staticmethod
766869 def _get_transaction_class (transaction_type : str ):
767870 """
@@ -843,7 +946,7 @@ def _get_transaction_class(transaction_type: str):
843946 raise ValueError (f"Failed to import transaction class for type '{ transaction_type } ': { e } " ) from e
844947
845948 @classmethod
846- def _from_protobuf (cls , transaction_body , body_bytes : bytes , sig_map ):
949+ def _from_protobuf (cls , transaction_body ):
847950 """
848951 Creates a transaction instance from protobuf components.
849952
@@ -864,7 +967,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
864967 transaction .transaction_id = TransactionId ._from_proto (transaction_body .transactionID )
865968
866969 if transaction_body .HasField ("nodeAccountID" ):
867- transaction .node_account_id = AccountId ._from_proto (transaction_body .nodeAccountID )
970+ transaction ._node_account_id = AccountId ._from_proto (transaction_body .nodeAccountID )
868971
869972 transaction .transaction_fee = transaction_body .transactionFee
870973 transaction .transaction_valid_duration = transaction_body .transactionValidDuration .seconds
@@ -879,14 +982,6 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
879982 CustomFeeLimit ._from_proto (fee ) for fee in transaction_body .max_custom_fees
880983 ]
881984
882- if transaction .node_account_id :
883- # restore for the original frozen node
884- transaction .set_node_account_id (transaction .node_account_id )
885- transaction ._transaction_body_bytes [transaction .node_account_id ] = body_bytes
886-
887- if sig_map and sig_map .sigPair :
888- transaction ._signature_map [body_bytes ] = sig_map
889-
890985 return transaction
891986
892987 def set_batch_key (self , key : Key ):
0 commit comments