Skip to content

Commit 86e567c

Browse files
committed
feat: implement createTopic functionality and related parameters in TCK handlers
Signed-off-by: MonaaEid <monaa_eid@hotmail.com>
1 parent 376553e commit 86e567c

7 files changed

Lines changed: 97 additions & 62 deletions

File tree

tck/handlers/__init__.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
"""TCK handlers - auto-import all handler modules."""
22

33
# Import registry functions first to make them available
4+
# Import all handler modules to trigger @rpc_method decorators
5+
from . import (
6+
account,
7+
key,
8+
sdk, # setup, reset
9+
topic,
10+
)
411
from .registry import (
5-
get_handler,
612
get_all_handlers,
13+
get_handler,
714
safe_dispatch,
815
)
916

10-
# Import all handler modules to trigger @rpc_method decorators
11-
from . import sdk # setup, reset
12-
from . import key
13-
from . import account
14-
from . import topic
1517

1618
__all__ = [
1719
"get_handler",

tck/handlers/key.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from hiero_sdk_python.crypto.key_list import KeyList
24
from hiero_sdk_python.crypto.private_key import PrivateKey
35
from hiero_sdk_python.crypto.public_key import PublicKey
@@ -28,9 +30,7 @@ def generate_key(params: KeyGenerationParams) -> KeyGenerationResponse:
2830
)
2931

3032
if params.threshold is not None and params.type != KeyType.THRESHOLD_KEY:
31-
raise JsonRpcError.invalid_params_error(
32-
"invalid parameters: threshold is only allowed for thresholdKey types."
33-
)
33+
raise JsonRpcError.invalid_params_error("invalid parameters: threshold is only allowed for thresholdKey types.")
3434

3535
if params.type == KeyType.THRESHOLD_KEY and params.threshold is None:
3636
raise JsonRpcError.invalid_params_error(
@@ -67,9 +67,7 @@ def _handle_private_key(params: KeyGenerationParams, response: KeyGenerationResp
6767
return private_key_string
6868

6969

70-
def _handle_public_key(
71-
params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool
72-
) -> str:
70+
def _handle_public_key(params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool) -> str:
7371
if params.fromKey:
7472
return _format_generated_public_key(PrivateKey.from_string(params.fromKey).public_key())
7573

tck/handlers/topic.py

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ def _build_custom_fee(custom_fee_params: CreateTopicCustomFeeParams) -> CustomFi
2525
raise ValueError("feeCollectorAccountId cannot be empty")
2626

2727
if custom_fee_params.feeCollectorAccountId is not None:
28-
custom_fee.set_fee_collector_account_id(
29-
AccountId.from_string(custom_fee_params.feeCollectorAccountId)
30-
)
28+
custom_fee.set_fee_collector_account_id(AccountId.from_string(custom_fee_params.feeCollectorAccountId))
3129

3230
if custom_fee_params.feeCollectorsExempt:
3331
custom_fee.set_all_collectors_are_exempt(custom_fee_params.feeCollectorsExempt)
@@ -37,9 +35,7 @@ def _build_custom_fee(custom_fee_params: CreateTopicCustomFeeParams) -> CustomFi
3735
custom_fee.amount = custom_fee_params.fixedFee.amount
3836

3937
if custom_fee_params.fixedFee.denominatingTokenId:
40-
custom_fee.set_denominating_token_id(
41-
TokenId.from_string(custom_fee_params.fixedFee.denominatingTokenId)
42-
)
38+
custom_fee.set_denominating_token_id(TokenId.from_string(custom_fee_params.fixedFee.denominatingTokenId))
4339

4440
return custom_fee
4541

@@ -60,22 +56,16 @@ def _build_create_topic_transaction(params: CreateTopicParams) -> TopicCreateTra
6056
transaction.set_auto_renew_period(params.autoRenewPeriod)
6157

6258
if params.autoRenewAccountId:
63-
transaction.set_auto_renew_account(
64-
AccountId.from_string(params.autoRenewAccountId)
65-
)
59+
transaction.set_auto_renew_account(AccountId.from_string(params.autoRenewAccountId))
6660

6761
if params.feeScheduleKey:
6862
transaction.set_fee_schedule_key(get_key_from_string(params.feeScheduleKey))
6963

7064
if params.feeExemptKeys:
71-
transaction.set_fee_exempt_keys(
72-
[get_key_from_string(key) for key in params.feeExemptKeys]
73-
)
65+
transaction.set_fee_exempt_keys([get_key_from_string(key) for key in params.feeExemptKeys])
7466

7567
if params.customFees is not None:
76-
transaction.set_custom_fees(
77-
[_build_custom_fee(custom_fee_params) for custom_fee_params in params.customFees]
78-
)
68+
transaction.set_custom_fees([_build_custom_fee(custom_fee_params) for custom_fee_params in params.customFees])
7969

8070
return transaction
8171

@@ -96,6 +86,7 @@ def _get_auto_renew_account_state(client, auto_renew_account_id: str | None) ->
9686
return "deleted"
9787
return "unknown"
9888

89+
9990
@rpc_method("createTopic")
10091
def create_topic(params: CreateTopicParams) -> CreateTopicResponse:
10192
client = get_client(params.sessionId)
@@ -119,14 +110,9 @@ def create_topic(params: CreateTopicParams) -> CreateTopicResponse:
119110
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)
120111
except (PrecheckError, ReceiptStatusError) as exc:
121112
status = ResponseCode(exc.status).name
122-
if (
123-
params.autoRenewAccountId
124-
and status == "INVALID_SIGNATURE"
125-
and auto_renew_account_state != "deleted"
126-
):
127-
raise JsonRpcError.hiero_error({"status": "INVALID_AUTORENEW_ACCOUNT"})
128-
raise
129-
113+
if params.autoRenewAccountId and status == "INVALID_SIGNATURE" and auto_renew_account_state != "deleted":
114+
raise JsonRpcError.hiero_error({"status": "INVALID_AUTORENEW_ACCOUNT"}) from exc
115+
raise JsonRpcError.hiero_error({"status": status}) from exc
130116
topic_id = ""
131117
if receipt.status == ResponseCode.SUCCESS and receipt.topic_id is not None:
132118
topic_id = str(receipt.topic_id)

tck/param/common.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,9 @@ def apply_common_params(self, transaction: Transaction, client: Client) -> None:
3434
"""Apply commonTransactionParams to a given transaction."""
3535
if self.transactionId is not None:
3636
try:
37-
transaction.set_transaction_id(
38-
TransactionId.from_string(self.transactionId)
39-
)
37+
transaction.set_transaction_id(TransactionId.from_string(self.transactionId))
4038
except Exception:
41-
transaction.set_transaction_id(
42-
TransactionId.generate(AccountId.from_string(self.transactionId))
43-
)
39+
transaction.set_transaction_id(TransactionId.generate(AccountId.from_string(self.transactionId)))
4440

4541
if self.maxTransactionFee is not None:
4642
transaction.transaction_fee = int(self.maxTransactionFee)

tck/param/topic.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
@dataclass
1717
class CreateTopicFixedFeeParams:
1818
"""Parameters for a fixed fee custom fee in topic creation."""
19+
1920
amount: int | None = None
2021
denominatingTokenId: str | None = None
2122

2223
@classmethod
23-
def parse_json_params(cls, params: dict) -> "CreateTopicFixedFeeParams":
24+
def parse_json_params(cls, params: dict) -> CreateTopicFixedFeeParams:
2425
return cls(
2526
amount=to_int(params.get("amount")),
2627
denominatingTokenId=non_empty_string_or_none(params.get("denominatingTokenId")),
@@ -30,12 +31,13 @@ def parse_json_params(cls, params: dict) -> "CreateTopicFixedFeeParams":
3031
@dataclass
3132
class CreateTopicCustomFeeParams:
3233
"""Parameters for a custom fee in topic creation."""
34+
3335
feeCollectorAccountId: str | None = None
3436
feeCollectorsExempt: bool | None = None
3537
fixedFee: CreateTopicFixedFeeParams | None = None
3638

3739
@classmethod
38-
def parse_json_params(cls, params: dict) -> "CreateTopicCustomFeeParams":
40+
def parse_json_params(cls, params: dict) -> CreateTopicCustomFeeParams:
3941
fixed_fee = params.get("fixedFee")
4042

4143
fee_collector_account_id = params.get("feeCollectorAccountId")
@@ -45,17 +47,14 @@ def parse_json_params(cls, params: dict) -> "CreateTopicCustomFeeParams":
4547
return cls(
4648
feeCollectorAccountId=fee_collector_account_id,
4749
feeCollectorsExempt=to_bool(params.get("feeCollectorsExempt")),
48-
fixedFee=(
49-
CreateTopicFixedFeeParams.parse_json_params(fixed_fee)
50-
if isinstance(fixed_fee, dict)
51-
else None
52-
),
50+
fixedFee=(CreateTopicFixedFeeParams.parse_json_params(fixed_fee) if isinstance(fixed_fee, dict) else None),
5351
)
5452

5553

5654
@dataclass
5755
class CreateTopicParams(BaseTransactionParams):
5856
"""Parameters for creating a topic. Extends BaseTransactionParams to include common transaction parameters."""
57+
5958
memo: str | None = None
6059
adminKey: str | None = None
6160
submitKey: str | None = None
@@ -66,7 +65,7 @@ class CreateTopicParams(BaseTransactionParams):
6665
customFees: list[CreateTopicCustomFeeParams] | None = None
6766

6867
@classmethod
69-
def parse_json_params(cls, params: dict) -> "CreateTopicParams":
68+
def parse_json_params(cls, params: dict) -> CreateTopicParams:
7069
fee_exempt_keys = params.get("feeExemptKeys")
7170
if fee_exempt_keys is not None and not isinstance(fee_exempt_keys, list):
7271
raise ValueError("feeExemptKeys must be a list")
@@ -85,10 +84,7 @@ def parse_json_params(cls, params: dict) -> "CreateTopicParams":
8584
feeScheduleKey=non_empty_string_or_none(params.get("feeScheduleKey")),
8685
feeExemptKeys=non_empty_string_list(fee_exempt_keys),
8786
customFees=(
88-
[
89-
CreateTopicCustomFeeParams.parse_json_params(custom_fee)
90-
for custom_fee in custom_fees
91-
]
87+
[CreateTopicCustomFeeParams.parse_json_params(custom_fee) for custom_fee in custom_fees]
9288
if custom_fees is not None
9389
else None
9490
),

tck/util/param_utils.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from __future__ import annotations
2+
3+
14
def parse_session_id(params: dict) -> str:
25
"""Parse sessionId from the json rpc params."""
36
session_id = params.get("sessionId")
@@ -16,9 +19,7 @@ def parse_common_transaction_params(params: dict):
1619
if common_params is None:
1720
return None
1821

19-
return CommonTransactionParams.parse_json_params(
20-
params.get("commonTransactionParams")
21-
)
22+
return CommonTransactionParams.parse_json_params(params.get("commonTransactionParams"))
2223

2324

2425
def to_int(value) -> int | None:
@@ -28,6 +29,7 @@ def to_int(value) -> int | None:
2829
except (TypeError, ValueError):
2930
return None
3031

32+
3133
def non_empty_string_or_none(value: str | None) -> str | None:
3234
"""Trim string values; convert blank strings to None."""
3335
if not isinstance(value, str):
@@ -49,6 +51,7 @@ def non_empty_string_list(values) -> list[str] | None:
4951

5052
return cleaned_values
5153

54+
5255
def to_bool(value) -> bool | None:
5356
"""Helper to convert value to bool."""
5457
if isinstance(value, str):

tests/tck/topic_handlers_test.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Focused createTopic tests for TCK handlers."""
2+
23
from __future__ import annotations
34

45
from unittest.mock import MagicMock, patch
@@ -27,7 +28,18 @@
2728

2829

2930
def _params(**overrides):
30-
base = dict(sessionId="test", memo=None, adminKey=None, submitKey=None, autoRenewPeriod=None, autoRenewAccountId=None, feeScheduleKey=None, feeExemptKeys=None, customFees=None, commonTransactionParams=None)
31+
base = dict(
32+
sessionId="test",
33+
memo=None,
34+
adminKey=None,
35+
submitKey=None,
36+
autoRenewPeriod=None,
37+
autoRenewAccountId=None,
38+
feeScheduleKey=None,
39+
feeExemptKeys=None,
40+
customFees=None,
41+
commonTransactionParams=None,
42+
)
3143
base.update(overrides)
3244
return CreateTopicParams(**base)
3345

@@ -55,17 +67,40 @@ def test_parse_json_and_validation():
5567

5668

5769
def test_build_custom_fee_and_reject_empty_collector():
58-
fee = _build_custom_fee(CreateTopicCustomFeeParams(feeCollectorAccountId="0.0.98", feeCollectorsExempt=True, fixedFee=CreateTopicFixedFeeParams(amount=100, denominatingTokenId="0.0.500")))
70+
fee = _build_custom_fee(
71+
CreateTopicCustomFeeParams(
72+
feeCollectorAccountId="0.0.98",
73+
feeCollectorsExempt=True,
74+
fixedFee=CreateTopicFixedFeeParams(amount=100, denominatingTokenId="0.0.500"),
75+
)
76+
)
5977
assert fee.amount == 100
6078
assert fee.fee_collector_account_id == AccountId(0, 0, 98)
6179
assert fee.denominating_token_id == TokenId(0, 0, 500)
6280
assert fee.all_collectors_are_exempt is True
6381
with pytest.raises(ValueError, match="feeCollectorAccountId cannot be empty"):
64-
_build_custom_fee(CreateTopicCustomFeeParams(feeCollectorAccountId="", fixedFee=CreateTopicFixedFeeParams(amount=1)))
82+
_build_custom_fee(
83+
CreateTopicCustomFeeParams(feeCollectorAccountId="", fixedFee=CreateTopicFixedFeeParams(amount=1))
84+
)
6585

6686

6787
def test_build_transaction_core_fields():
68-
tx = _build_create_topic_transaction(_params(memo="m", adminKey=TEST_KEY, submitKey=TEST_KEY, autoRenewPeriod=7776000, autoRenewAccountId="0.0.98", feeScheduleKey=TEST_KEY, feeExemptKeys=[TEST_KEY], customFees=[CreateTopicCustomFeeParams(feeCollectorAccountId="0.0.98", fixedFee=CreateTopicFixedFeeParams(amount=100))]))
88+
tx = _build_create_topic_transaction(
89+
_params(
90+
memo="m",
91+
adminKey=TEST_KEY,
92+
submitKey=TEST_KEY,
93+
autoRenewPeriod=7776000,
94+
autoRenewAccountId="0.0.98",
95+
feeScheduleKey=TEST_KEY,
96+
feeExemptKeys=[TEST_KEY],
97+
customFees=[
98+
CreateTopicCustomFeeParams(
99+
feeCollectorAccountId="0.0.98", fixedFee=CreateTopicFixedFeeParams(amount=100)
100+
)
101+
],
102+
)
103+
)
69104
assert tx.memo == "m"
70105
assert getattr(tx.auto_renew_period, "seconds", tx.auto_renew_period) == 7776000
71106
assert len(tx.custom_fees) == 1
@@ -78,7 +113,9 @@ def test_build_transaction_core_fields():
78113
(None, "0.0.98", "exists"),
79114
(PrecheckError(status=ResponseCode.INVALID_ACCOUNT_ID), "0.0.999", "missing"),
80115
(
81-
ReceiptStatusError(status=ResponseCode.ACCOUNT_DELETED, transaction_id=None, transaction_receipt=MagicMock()),
116+
ReceiptStatusError(
117+
status=ResponseCode.ACCOUNT_DELETED, transaction_id=None, transaction_receipt=MagicMock()
118+
),
82119
"0.0.98",
83120
"deleted",
84121
),
@@ -114,6 +151,23 @@ def test_create_topic_rejects_missing_auto_renew(_, mock_get_client, client_mock
114151
def test_create_topic_with_all_parameters(_, mock_get_client, client_mock):
115152
mock_get_client.return_value = client_mock
116153
with patch.object(TopicCreateTransaction, "execute", return_value=_success_response(2000)):
117-
result = create_topic(_params(memo="Full", adminKey=TEST_KEY, submitKey=TEST_KEY, autoRenewPeriod=7776000, autoRenewAccountId="0.0.98", feeScheduleKey=TEST_KEY, feeExemptKeys=[TEST_KEY], customFees=[CreateTopicCustomFeeParams(feeCollectorAccountId="0.0.98", feeCollectorsExempt=True, fixedFee=CreateTopicFixedFeeParams(amount=500, denominatingTokenId="0.0.500"))]))
154+
result = create_topic(
155+
_params(
156+
memo="Full",
157+
adminKey=TEST_KEY,
158+
submitKey=TEST_KEY,
159+
autoRenewPeriod=7776000,
160+
autoRenewAccountId="0.0.98",
161+
feeScheduleKey=TEST_KEY,
162+
feeExemptKeys=[TEST_KEY],
163+
customFees=[
164+
CreateTopicCustomFeeParams(
165+
feeCollectorAccountId="0.0.98",
166+
feeCollectorsExempt=True,
167+
fixedFee=CreateTopicFixedFeeParams(amount=500, denominatingTokenId="0.0.500"),
168+
)
169+
],
170+
)
171+
)
118172
assert result.status == "SUCCESS"
119173
assert result.topicId == "0.0.2000"

0 commit comments

Comments
 (0)