11from __future__ import annotations
22
3- import math
4- from typing import Literal , overload
5-
63from hiero_sdk_python .channels import _Channel
7- from hiero_sdk_python .client .client import Client
84from hiero_sdk_python .consensus .topic_id import TopicId
95from hiero_sdk_python .crypto .private_key import PrivateKey
106from hiero_sdk_python .executable import _Method
11- from hiero_sdk_python .hapi .services import consensus_submit_message_pb2 , timestamp_pb2 , transaction_pb2
7+ from hiero_sdk_python .hapi .services import consensus_submit_message_pb2 , transaction_pb2
128from hiero_sdk_python .hapi .services .schedulable_transaction_body_pb2 import (
139 SchedulableTransactionBody ,
1410)
11+ from hiero_sdk_python .transaction .chunked_transaction import ChunkedTransaction
1512from hiero_sdk_python .transaction .custom_fee_limit import CustomFeeLimit
16- from hiero_sdk_python .transaction .transaction import Transaction
17- from hiero_sdk_python .transaction .transaction_id import TransactionId
18- from hiero_sdk_python .transaction .transaction_receipt import TransactionReceipt
19- from hiero_sdk_python .transaction .transaction_response import TransactionResponse
2013
2114
22- class TopicMessageSubmitTransaction (Transaction ):
15+ class TopicMessageSubmitTransaction (ChunkedTransaction ):
2316 """
2417 Represents a transaction that submits a message to a Hedera Consensus Service topic.
2518
2619 Allows setting the target topic ID and message, building the transaction body,
2720 and executing the submission through a network channel.
21+
22+ Supports automatic chunking for large messages.
2823 """
2924
3025 def __init__ (
31- self ,
32- topic_id : TopicId | None = None ,
33- message : str | None = None ,
34- chunk_size : int | None = None ,
35- max_chunks : int | None = None ,
26+ self ,
27+ topic_id : TopicId | None = None ,
28+ message : bytes | str | None = None ,
29+ chunk_size : int | None = None ,
30+ max_chunks : int | None = None ,
3631 ) -> None :
3732 """
3833 Initializes a new TopicMessageSubmitTransaction instance.
3934
4035 Args:
4136 topic_id (TopicId, optional): The ID of the topic.
4237 message (str, optional): The message to submit.
43- chunk_size (int, optional): The maximum chunk size in bytes, Default: 1024.
44- max_chunks (int, optional): The maximum number of chunks allowed, Default: 20.
38+ chunk_size (int, optional): The maximum chunk size in bytes. Default: 1024.
39+ max_chunks (int, optional): The maximum number of chunks allowed. Default: 20.
4540 """
4641 super ().__init__ ()
4742 self .topic_id : TopicId | None = topic_id
48- self .message : str | None = message
49- self .chunk_size : int = chunk_size or 1024
50- self .max_chunks : int = max_chunks or 20
43+ self .message : bytes | str | None = message
44+ self .chunk_size : int = 1024
45+ self .max_chunks : int = 20
46+ if chunk_size is not None :
47+ super ().set_chunk_size (chunk_size )
48+
49+ if max_chunks is not None :
50+ super ().set_max_chunks (max_chunks )
5151
52- self ._current_chunk_index = 0
5352 self ._total_chunks = self .get_required_chunks ()
54- self ._initial_transaction_id : TransactionId | None = None
55- self ._transaction_ids : list [TransactionId ] = []
56- self ._signing_keys : list [PrivateKey ] = []
53+
54+ def _message_as_bytes (self ) -> bytes :
55+ """
56+ Returns the message encoded as bytes for chunking and protobuf serialization.
57+
58+ Returns:
59+ bytes: The message as bytes.
60+ """
61+ if self .message is None :
62+ return b""
63+
64+ return self .message .encode ("utf-8" ) if isinstance (self .message , str ) else self .message
65+
5766
5867 def get_required_chunks (self ) -> int :
5968 """
@@ -65,7 +74,8 @@ def get_required_chunks(self) -> int:
6574 if not self .message :
6675 return 1
6776
68- content = self .message .encode ("utf-8" )
77+ content = self ._message_as_bytes ()
78+ import math
6979 return math .ceil (len (content ) / self .chunk_size )
7080
7181 def set_topic_id (self , topic_id : TopicId ) -> TopicMessageSubmitTransaction :
@@ -82,7 +92,7 @@ def set_topic_id(self, topic_id: TopicId) -> TopicMessageSubmitTransaction:
8292 self .topic_id = topic_id
8393 return self
8494
85- def set_message (self , message : str ) -> TopicMessageSubmitTransaction :
95+ def set_message (self , message : bytes | str ) -> TopicMessageSubmitTransaction :
8696 """
8797 Sets the message to submit to the topic.
8898
@@ -102,16 +112,12 @@ def set_chunk_size(self, chunk_size: int) -> TopicMessageSubmitTransaction:
102112 Set maximum chunk size in bytes.
103113
104114 Args:
105- chunk_size (int): The size of each chunk in bytes.
115+ chunk_size (int): The size of each chunk in bytes.
106116
107117 Returns:
108118 TopicMessageSubmitTransaction: This transaction instance (for chaining).
109119 """
110- self ._require_not_frozen ()
111- if chunk_size <= 0 :
112- raise ValueError ("chunk_size must be positive" )
113-
114- self .chunk_size = chunk_size
120+ super ().set_chunk_size (chunk_size )
115121 self ._total_chunks = self .get_required_chunks ()
116122 return self
117123
@@ -125,11 +131,7 @@ def set_max_chunks(self, max_chunks: int) -> TopicMessageSubmitTransaction:
125131 Returns:
126132 TopicMessageSubmitTransaction: This transaction instance (for chaining).
127133 """
128- self ._require_not_frozen ()
129- if max_chunks <= 0 :
130- raise ValueError ("max_chunks must be positive" )
131-
132- self .max_chunks = max_chunks
134+ super ().set_max_chunks (max_chunks )
133135 return self
134136
135137 def set_custom_fee_limits (self , custom_fee_limits : list [CustomFeeLimit ]) -> TopicMessageSubmitTransaction :
@@ -160,21 +162,6 @@ def add_custom_fee_limit(self, custom_fee_limit: CustomFeeLimit) -> TopicMessage
160162 self .custom_fee_limits .append (custom_fee_limit )
161163 return self
162164
163- def _validate_chunking (self ) -> None :
164- """
165- Validates that chunk count does not exceed max_chunks.
166-
167- Raises:
168- ValueError: If chunk count exceeds `max_chunks`.
169- """
170- required = self .get_required_chunks ()
171-
172- if self .max_chunks and required > self .max_chunks :
173- raise ValueError (
174- f"Message requires { required } chunks but max_chunks={ self .max_chunks } . "
175- f"Increase limit with set_max_chunks()."
176- )
177-
178165 def _build_proto_body (self ) -> consensus_submit_message_pb2 .ConsensusSubmitMessageTransactionBody :
179166 """
180167 Returns the protobuf body for the topic message submit transaction.
@@ -190,14 +177,15 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa
190177 if self .message is None :
191178 raise ValueError ("Missing required fields: message." )
192179
193- content = self .message . encode ( "utf-8" )
180+ content = self ._message_as_bytes ( )
194181
195182 start_index = self ._current_chunk_index * self .chunk_size
196183 end_index = min (start_index + self .chunk_size , len (content ))
197184 chunk_content = content [start_index :end_index ]
198185
199186 body = consensus_submit_message_pb2 .ConsensusSubmitMessageTransactionBody (
200- topicID = self .topic_id ._to_proto (), message = chunk_content
187+ topicID = self .topic_id ._to_proto (),
188+ message = chunk_content
201189 )
202190
203191 # Multi-chunk metadata
@@ -217,8 +205,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody:
217205 Builds and returns the protobuf transaction body for message submission.
218206
219207 Returns:
220- TransactionBody: The protobuf transaction body containing
221- the message submission details.
208+ TransactionBody: The protobuf transaction body containing the message submission details.
222209 """
223210 consensus_submit_message_body = self ._build_proto_body ()
224211 transaction_body = self .build_base_transaction_body ()
@@ -249,180 +236,15 @@ def _get_method(self, channel: _Channel) -> _Method:
249236 """
250237 return _Method (transaction_func = channel .topic .submitMessage , query_func = None )
251238
252- def freeze_with (self , client : Client ) -> TopicMessageSubmitTransaction :
253- if self ._transaction_body_bytes :
254- return self
255-
256- self ._resolve_transaction_id (client )
257-
258- if not self ._transaction_ids :
259- base_timestamp = self .transaction_id .valid_start
260-
261- for i in range (self .get_required_chunks ()):
262- if i == 0 :
263- if self ._initial_transaction_id is None :
264- self ._initial_transaction_id = self .transaction_id
265-
266- chunk_transaction_id = self .transaction_id
267- else :
268- next_nanos = base_timestamp .nanos + i
269-
270- chunk_valid_start = timestamp_pb2 .Timestamp (
271- seconds = base_timestamp .seconds + next_nanos // 1_000_000_000 , nanos = next_nanos % 1_000_000_000
272- )
273- chunk_transaction_id = TransactionId (
274- account_id = self .transaction_id .account_id , valid_start = chunk_valid_start
275- )
276-
277- self ._transaction_ids .append (chunk_transaction_id )
278-
279- return super ().freeze_with (client )
280-
281- @overload
282- def execute (
283- self ,
284- client : Client ,
285- timeout : int | float | None = None ,
286- wait_for_receipt : Literal [True ] = True ,
287- validate_status : bool = False ,
288- ) -> TransactionReceipt : ...
289-
290- @overload
291- def execute (
292- self ,
293- client : Client ,
294- timeout : int | float | None = None ,
295- wait_for_receipt : Literal [False ] = False ,
296- validate_status : bool = False ,
297- ) -> TransactionResponse : ...
298-
299- def execute (
300- self ,
301- client : Client ,
302- timeout : int | float | None = None ,
303- wait_for_receipt : bool = True ,
304- validate_status : bool = False ,
305- ) -> TransactionReceipt | TransactionResponse :
306- """
307- Executes the topic message submit transaction.
308-
309- For multi-chunk transactions, this method will execute all chunks sequentially and return first response.
310-
311- Args:
312- client: The client to execute the transaction with.
313- timeout (int | float | None, optional): The total execution timeout (in seconds) for this execution.
314- wait_for_receipt (bool, optional): Whether to wait for consensus and return the receipt.
315- If False, the method returns a TransactionResponse immediately after submission.
316- validate_status: (bool): Whether the query should automatically validate the transaction status (default False).
317-
318- Returns:
319- TransactionReceipt: If wait_for_receipt is True (default)
320- TransactionResponse: If wait_for_receipt is False
321- """
322- # Return the first response as the JS SDK does
323- return self .execute_all (client , timeout , wait_for_receipt , validate_status )[0 ]
324-
325- @overload
326- def execute_all (
327- self ,
328- client : Client ,
329- timeout : int | float | None = None ,
330- wait_for_receipt : Literal [True ] = True ,
331- validate_status : bool = False ,
332- ) -> list [TransactionReceipt ]: ...
333-
334- @overload
335- def execute_all (
336- self ,
337- client : Client ,
338- timeout : int | float | None = None ,
339- wait_for_receipt : Literal [False ] = False ,
340- validate_status : bool = False ,
341- ) -> list [TransactionResponse ]: ...
342-
343- def execute_all (
344- self ,
345- client : Client ,
346- timeout : int | float | None = None ,
347- wait_for_receipt : bool = True ,
348- validate_status : bool = False ,
349- ) -> list [TransactionReceipt ] | list [TransactionResponse ]:
350- """
351- Executes the topic message submit transaction.
352-
353- This method will execute all chunks sequentially and return list of all responses.
354-
355- Args:
356- client: The client to execute the transaction with.
357- timeout (int | float | None, optional): The total execution timeout (in seconds) for this execution.
358- wait_for_receipt (bool, optional): Whether to wait for consensus and return the receipt.
359- If False, the method returns a TransactionResponse immediately after submission.
360- validate_status: (bool): Whether the query should automatically validate the transaction status (default False).
361-
362- Returns:
363- List[TransactionReceipt]: If wait_for_receipt is True (default)
364- List[TransactionResponse]: If wait_for_receipt is False
365- """
366- self ._validate_chunking ()
367-
368- if self .get_required_chunks () == 1 :
369- return [super ().execute (client , timeout , wait_for_receipt , validate_status )]
370-
371- # Multi-chunk transaction - execute all chunks
372- responses = []
373-
374- for chunk_index in range (self .get_required_chunks ()):
375- self ._current_chunk_index = chunk_index
376-
377- if self ._transaction_ids and chunk_index < len (self ._transaction_ids ):
378- self .transaction_id = self ._transaction_ids [chunk_index ]
379-
380- self ._transaction_body_bytes .clear ()
381- self ._signature_map .clear ()
382-
383- self .freeze_with (client )
384-
385- for signing_key in self ._signing_keys :
386- super ().sign (signing_key )
387-
388- # Execute the chunk
389- response = super ().execute (client , timeout , wait_for_receipt , validate_status )
390- responses .append (response )
391-
392- return responses
393-
394- def sign (self , private_key : PrivateKey ):
239+ def sign (self , private_key : PrivateKey ) -> TopicMessageSubmitTransaction :
395240 """
396241 Signs the transaction using the provided private key.
397242
398- For multi-chunk transactions, this stores the signing key for later use.
399-
400243 Args:
401244 private_key (PrivateKey): The private key to sign the transaction with.
402- """
403- if private_key not in self ._signing_keys :
404- self ._signing_keys .append (private_key )
405245
246+ Returns:
247+ TopicMessageSubmitTransaction: This transaction instance (for chaining).
248+ """
406249 super ().sign (private_key )
407250 return self
408-
409- @property
410- def body_size_all_chunks (self ) -> list [int ]:
411- """Returns an array of body sizes for transactions with multiple chunks."""
412- self ._require_frozen ()
413- sizes = []
414-
415- original_index = self ._current_chunk_index
416- original_transaction_id = self .transaction_id
417-
418- try :
419- for i , transaction_id in enumerate (self ._transaction_ids ):
420- self ._current_chunk_index = i
421- self .transaction_id = transaction_id
422-
423- sizes .append (self .body_size )
424- finally :
425- self ._current_chunk_index = original_index
426- self .transaction_id = original_transaction_id
427-
428- return sizes
0 commit comments