Skip to content

Commit 3071135

Browse files
committed
Create ChunkedTransaction class
Signed-off-by: MonaaEid <monaa_eid@hotmail.com>
1 parent 04c4496 commit 3071135

7 files changed

Lines changed: 103 additions & 34 deletions

File tree

.github/workflows/pr-check-primary-codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
egress-policy: audit
4343

4444
- name: Initialize CodeQL
45-
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
45+
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
4646
with:
4747
languages: ${{ matrix.language }}
4848
build-mode: ${{ matrix.build-mode }}
@@ -66,6 +66,6 @@ jobs:
6666
run: uv sync --frozen --all-groups --all-packages --all-extras
6767

6868
- name: Perform CodeQL Analysis
69-
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
69+
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
7070
with:
7171
category: "/language:${{matrix.language}}"

src/hiero_sdk_python/file/file_append_transaction.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ def __init__(
3939
super().__init__()
4040
self.file_id: FileId | None = file_id
4141
self.contents: bytes | None = self._encode_contents(contents)
42-
self.max_chunks: int = max_chunks if max_chunks is not None else 20
43-
self.chunk_size: int = chunk_size if chunk_size is not None else 4096
42+
self.max_chunks: int = 20
43+
self.chunk_size: int = 4096
4444
self._default_transaction_fee = Hbar(5).to_tinybars()
4545

46+
if max_chunks is not None:
47+
self.set_max_chunks(max_chunks)
48+
if chunk_size is not None:
49+
self.set_chunk_size(chunk_size)
50+
4651
self._total_chunks = self._calculate_total_chunks()
4752

4853
def _encode_contents(self, contents: str | bytes | None) -> bytes | None:
@@ -121,8 +126,7 @@ def set_max_chunks(self, max_chunks: int) -> FileAppendTransaction:
121126
Returns:
122127
FileAppendTransaction: This transaction instance.
123128
"""
124-
self._require_not_frozen()
125-
self.max_chunks = max_chunks
129+
super().set_max_chunks(max_chunks)
126130
return self
127131

128132
def set_chunk_size(self, chunk_size: int) -> FileAppendTransaction:
@@ -135,8 +139,7 @@ def set_chunk_size(self, chunk_size: int) -> FileAppendTransaction:
135139
Returns:
136140
FileAppendTransaction: This transaction instance.
137141
"""
138-
self._require_not_frozen()
139-
self.chunk_size = chunk_size
142+
super().set_max_chunks(max_chunks)
140143
self._total_chunks = self._calculate_total_chunks()
141144
return self
142145

src/hiero_sdk_python/transaction/chunked_transaction.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
from hiero_sdk_python.transaction.transaction_response import TransactionResponse
1313

1414

15-
if TYPE_CHECKING:
16-
pass
17-
18-
1915
class ChunkedTransaction(Transaction, ABC):
2016
"""
2117
Abstract base class for transactions that support chunking.
@@ -43,17 +39,6 @@ def __init__(self) -> None:
4339
self.chunk_size: int = 1024
4440
self.max_chunks: int = 20
4541

46-
@abstractmethod
47-
def get_required_chunks(self) -> int:
48-
"""
49-
Returns the number of chunks required for the current content.
50-
51-
Subclasses must implement this based on their content type.
52-
53-
Returns:
54-
int: Number of chunks required.
55-
"""
56-
pass
5742

5843
@abstractmethod
5944
def _build_proto_body(self):
@@ -148,10 +133,9 @@ def freeze_with(self, client: Client) -> ChunkedTransaction:
148133
if self._transaction_body_bytes:
149134
return self
150135

151-
self._validate_chunking()
152136
self._resolve_transaction_id(client)
153137

154-
if self.transaction_id is None or self.transaction_id.valid_start is None:
138+
if self.transaction_id.valid_start is None:
155139
raise ValueError("Transaction ID with valid_start must be set before freezing chunked transaction.")
156140

157141
# Generate transaction IDs for all chunks if not already done
@@ -265,11 +249,10 @@ def execute_all(
265249
List[TransactionReceipt]: If wait_for_receipt is True (default)
266250
List[TransactionResponse]: If wait_for_receipt is False
267251
"""
268-
self._validate_chunking()
269-
270-
required_chunks = self.get_required_chunks()
252+
self._validate_chunking() # Moved here
271253

272-
if required_chunks == 1:
254+
# For single-chunk transactions, delegate to the standard execution flow.
255+
if self.get_required_chunks() == 1:
273256
return [
274257
super().execute(
275258
client,
@@ -279,13 +262,14 @@ def execute_all(
279262
)
280263
]
281264

282-
# Ensure the initial transaction ID and chunk transaction IDs exist.
283-
if not self._transaction_ids:
265+
# For multi-chunk transactions, ensure we are frozen before proceeding.
266+
if not self._transaction_body_bytes:
284267
self.freeze_with(client)
285268

286269
responses = []
270+
required_chunks = self.get_required_chunks()
287271

288-
for chunk_index in range(required_chunks):
272+
for chunk_index in range(self.get_required_chunks()):
289273
self._current_chunk_index = chunk_index
290274

291275
if chunk_index < len(self._transaction_ids):

tck/handlers/token.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from hiero_sdk_python.tokens.fee_assessment_method import FeeAssessmentMethod
1414
from hiero_sdk_python.tokens.nft_id import NftId
1515
from hiero_sdk_python.tokens.supply_type import SupplyType
16+
from hiero_sdk_python.tokens.token_airdrop_claim import TokenClaimAirdropTransaction
17+
from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId
1618
from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction
1719
from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction
1820
from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction
@@ -28,6 +30,7 @@
2830
from tck.param.token import (
2931
AirdropTokenParams,
3032
AssociateTokenParams,
33+
ClaimTokenParams,
3134
CreateTokenParams,
3235
DeleteTokenParams,
3336
FreezeTokenParams,
@@ -37,6 +40,7 @@
3740
from tck.response.token import (
3841
AirdropTokenResponse,
3942
AssociateTokenResponse,
43+
ClaimTokenResponse,
4044
CreateTokenResponse,
4145
DeleteTokenResponse,
4246
FreezeTokenResponse,
@@ -429,3 +433,48 @@ def airdrop_token(params: AirdropTokenParams) -> AirdropTokenResponse:
429433
)
430434

431435
return AirdropTokenResponse(status=ResponseCode(receipt.status).name)
436+
437+
438+
def _build_claim_token_transaction(params: ClaimTokenParams) -> TokenClaimAirdropTransaction:
439+
"""Build a TokenClaimAirdropTransaction from TCK params."""
440+
transaction = TokenClaimAirdropTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
441+
442+
sender_id = AccountId.from_string(params.senderAccountId)
443+
receiver_id = AccountId.from_string(params.receiverAccountId)
444+
token_id = TokenId.from_string(params.tokenId)
445+
446+
if params.serialNumbers:
447+
for serial_number in params.serialNumbers:
448+
transaction.add_pending_airdrop_id(
449+
PendingAirdropId(
450+
sender_id=sender_id,
451+
receiver_id=receiver_id,
452+
nft_id=NftId(token_id=token_id, serial_number=int(serial_number)),
453+
)
454+
)
455+
else:
456+
transaction.add_pending_airdrop_id(
457+
PendingAirdropId(
458+
sender_id=sender_id,
459+
receiver_id=receiver_id,
460+
token_id=token_id,
461+
)
462+
)
463+
464+
return transaction
465+
466+
467+
@rpc_method("claimToken")
468+
def claim_token(params: ClaimTokenParams) -> ClaimTokenResponse:
469+
"""Claim pending token airdrops using TCK claimToken parameters."""
470+
client = get_client(params.sessionId)
471+
472+
transaction = _build_claim_token_transaction(params)
473+
474+
if params.commonTransactionParams is not None:
475+
params.commonTransactionParams.apply_common_params(transaction, client)
476+
477+
response = transaction.execute(client, wait_for_receipt=False)
478+
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)
479+
480+
return ClaimTokenResponse(status=ResponseCode(receipt.status).name)

tck/param/token.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,31 @@ def parse_json_params(cls, params: dict) -> AirdropTokenParams:
204204
sessionId=parse_session_id(params),
205205
commonTransactionParams=parse_common_transaction_params(params),
206206
)
207+
208+
209+
@dataclass
210+
class ClaimTokenParams(BaseTransactionParams):
211+
"""Request parameters for the claimToken endpoint."""
212+
213+
senderAccountId: str | None = None
214+
receiverAccountId: str | None = None
215+
tokenId: str | None = None
216+
serialNumbers: list[str] | None = None
217+
218+
@classmethod
219+
def parse_json_params(cls, params: dict) -> ClaimTokenParams:
220+
"""Parse JSON-RPC params into a ClaimTokenParams instance."""
221+
serial_numbers = params.get("serialNumbers")
222+
if serial_numbers is not None and not isinstance(serial_numbers, list):
223+
raise ValueError("serialNumbers must be a list")
224+
if serial_numbers is not None and any(not isinstance(serial_number, str) for serial_number in serial_numbers):
225+
raise ValueError("each serialNumbers item must be a string")
226+
227+
return cls(
228+
senderAccountId=params.get("senderAccountId"),
229+
receiverAccountId=params.get("receiverAccountId"),
230+
tokenId=params.get("tokenId"),
231+
serialNumbers=serial_numbers,
232+
sessionId=parse_session_id(params),
233+
commonTransactionParams=parse_common_transaction_params(params),
234+
)

tck/response/token.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,8 @@ class PauseTokenResponse(StatusOnlyResponse):
4747
@dataclass
4848
class AirdropTokenResponse(StatusOnlyResponse):
4949
"""Response payload for airdropToken."""
50+
51+
52+
@dataclass
53+
class ClaimTokenResponse(StatusOnlyResponse):
54+
"""Response payload for claimToken."""

tests/integration/topic_message_submit_transaction_e2e_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def test_topic_message_submit_transaction_fails_if_max_chunks_less_than_requied(
108108
message_tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).set_max_chunks(2)
109109

110110
with pytest.raises(ValueError):
111-
message_tx.freeze_with(env.client)
111+
message_tx.execute(env.client)
112112

113113
delete_topic(env.client, topic_id)
114114

@@ -289,7 +289,7 @@ def test_integration_topic_message_submit_transaction_fails_if_required_chunk_gr
289289
with pytest.raises(
290290
ValueError, match="Message requires 4 chunks but max_chunks=2. Increase limit with set_max_chunks()."
291291
):
292-
message_transaction.freeze_with(env.client)
292+
message_transaction.execute(env.client)
293293

294294
delete_topic(env.client, topic_id)
295295

0 commit comments

Comments
 (0)