Skip to content

Commit 08d4faa

Browse files
committed
feat: Allow PrivateKey in TopicCreateTransaction keys
Signed-off-by: Adityarya11 <arya050411@gmail.com>
1 parent 12cee39 commit 08d4faa

File tree

5 files changed

+339
-40
lines changed

5 files changed

+339
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
88

99

1010
### Added
11-
11+
- feat: Allow `PrivateKey` to be used for keys in `TopicCreateTransaction` for consistency.
1212

1313
### Changed
1414
- Refactored token-related example scripts (`token_delete.py`, `token_dissociate.py`, etc.) for improved readability and modularity. [#370]

src/hiero_sdk_python/tokens/token_create_transaction.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"""
1313

1414
from dataclasses import dataclass, field
15-
from typing import Optional, Any, List
15+
from typing import Optional, Any, List, Union
1616

1717
from hiero_sdk_python.Duration import Duration
1818
from hiero_sdk_python.channels import _Channel
@@ -27,11 +27,14 @@
2727
from hiero_sdk_python.tokens.supply_type import SupplyType
2828
from hiero_sdk_python.account.account_id import AccountId
2929
from hiero_sdk_python.crypto.private_key import PrivateKey
30+
from hiero_sdk_python.crypto.public_key import PublicKey
3031
from hiero_sdk_python.tokens.custom_fee import CustomFee
3132

3233
AUTO_RENEW_PERIOD = Duration(7890000) # around 90 days in seconds
3334
DEFAULT_TRANSACTION_FEE = 3_000_000_000
3435

36+
Key = Union[PrivateKey, PublicKey]
37+
3538
@dataclass
3639
class TokenParams:
3740
"""
@@ -81,14 +84,14 @@ class TokenKeys:
8184
kyc_key: The KYC key for the token to grant KYC to an account.
8285
"""
8386

84-
admin_key: Optional[PrivateKey] = None
85-
supply_key: Optional[PrivateKey] = None
86-
freeze_key: Optional[PrivateKey] = None
87-
wipe_key: Optional[PrivateKey] = None
88-
metadata_key: Optional[PrivateKey] = None
89-
pause_key: Optional[PrivateKey] = None
90-
kyc_key: Optional[PrivateKey] = None
91-
fee_schedule_key: Optional[PrivateKey] = None
87+
admin_key: Optional[Key] = None
88+
supply_key: Optional[Key] = None
89+
freeze_key: Optional[Key] = None
90+
wipe_key: Optional[Key] = None
91+
metadata_key: Optional[Key] = None
92+
pause_key: Optional[Key] = None
93+
kyc_key: Optional[Key] = None
94+
fee_schedule_key: Optional[Key] = None
9295

9396
class TokenCreateValidator:
9497
"""Token, key and freeze checks for creating a token as per the proto"""
@@ -368,43 +371,43 @@ def set_memo(self, memo: str) -> "TokenCreateTransaction":
368371
self._token_params.memo = memo
369372
return self
370373

371-
def set_admin_key(self, key: PrivateKey) -> "TokenCreateTransaction":
374+
def set_admin_key(self, key: Key) -> "TokenCreateTransaction":
372375
""" Sets the admin key for the token, which allows updating and deleting the token."""
373376
self._require_not_frozen()
374377
self._keys.admin_key = key
375378
return self
376379

377-
def set_supply_key(self, key: PrivateKey) -> "TokenCreateTransaction":
380+
def set_supply_key(self, key: Key) -> "TokenCreateTransaction":
378381
""" Sets the supply key for the token, which allows minting and burning tokens."""
379382
self._require_not_frozen()
380383
self._keys.supply_key = key
381384
return self
382385

383-
def set_freeze_key(self, key: PrivateKey) -> "TokenCreateTransaction":
386+
def set_freeze_key(self, key: Key) -> "TokenCreateTransaction":
384387
""" Sets the freeze key for the token, which allows freezing and unfreezing accounts."""
385388
self._require_not_frozen()
386389
self._keys.freeze_key = key
387390
return self
388391

389-
def set_wipe_key(self, key: PrivateKey) -> "TokenCreateTransaction":
392+
def set_wipe_key(self, key: Key) -> "TokenCreateTransaction":
390393
""" Sets the wipe key for the token, which allows wiping tokens from an account."""
391394
self._require_not_frozen()
392395
self._keys.wipe_key = key
393396
return self
394397

395-
def set_metadata_key(self, key: PrivateKey) -> "TokenCreateTransaction":
398+
def set_metadata_key(self, key: Key) -> "TokenCreateTransaction":
396399
""" Sets the metadata key for the token, which allows updating NFT metadata."""
397400
self._require_not_frozen()
398401
self._keys.metadata_key = key
399402
return self
400403

401-
def set_pause_key(self, key: PrivateKey) -> "TokenCreateTransaction":
404+
def set_pause_key(self, key: Key) -> "TokenCreateTransaction":
402405
""" Sets the pause key for the token, which allows pausing and unpausing the token."""
403406
self._require_not_frozen()
404407
self._keys.pause_key = key
405408
return self
406409

407-
def set_kyc_key(self, key: PrivateKey) -> "TokenCreateTransaction":
410+
def set_kyc_key(self, key: Key) -> "TokenCreateTransaction":
408411
""" Sets the KYC key for the token, which allows granting KYC to an account."""
409412
self._require_not_frozen()
410413
self._keys.kyc_key = key
@@ -416,26 +419,43 @@ def set_custom_fees(self, custom_fees: List[CustomFee]) -> "TokenCreateTransacti
416419
self._token_params.custom_fees = custom_fees
417420
return self
418421

419-
def set_fee_schedule_key(self, key: PrivateKey) -> "TokenCreateTransaction":
422+
def set_fee_schedule_key(self, key: Key) -> "TokenCreateTransaction":
420423
"""Sets the fee schedule key for the token."""
421424
self._require_not_frozen()
422425
self._keys.fee_schedule_key = key
423426
return self
424427

425-
def _to_proto_key(self, private_key: Optional[PrivateKey]) -> Optional[basic_types_pb2.Key]:
428+
def _to_proto_key(self, key: Optional[Key]) -> Optional[basic_types_pb2.Key]:
426429
"""
427-
Helper method to convert a private key to protobuf Key format.
430+
Helper method to convert a PrivateKey or PublicKey to the protobuf Key format.
431+
432+
This ensures only public keys are serialized:
433+
- If a PublicKey is provided, it is used directly.
434+
- If a PrivateKey is provided, its corresponding public key is extracted and used.
428435
429436
Args:
430-
private_key (PrivateKey, Optional): The private key to convert, or None
437+
key (Key, Optional): The PrivateKey or PublicKey to convert.
431438
432439
Returns:
433-
basic_types_pb2.Key (Optional): The protobuf key or None if private_key is None
440+
basic_types_pb2.Key (Optional): The protobuf key, or None.
441+
442+
Raises:
443+
TypeError: If the provided key is not a PrivateKey, PublicKey, or None.
434444
"""
435-
if not private_key:
445+
if not key:
436446
return None
437447

438-
return private_key.public_key()._to_proto()
448+
# If it's a PrivateKey, get its public key first
449+
if isinstance(key, PrivateKey):
450+
return key.public_key()._to_proto()
451+
452+
# If it's already a PublicKey, just convert it
453+
if isinstance(key, PublicKey):
454+
return key._to_proto()
455+
456+
# Safety net: This will fail if a non-key is passed
457+
raise TypeError("Key must be of type PrivateKey or PublicKey")
458+
439459

440460
def freeze_with(self, client) -> "TokenCreateTransaction":
441461
"""

tests/integration/token_create_transaction_e2e_test.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from hiero_sdk_python.Duration import Duration
55
from hiero_sdk_python.crypto.private_key import PrivateKey
6+
from hiero_sdk_python.crypto.public_key import PublicKey
7+
from hiero_sdk_python.transaction.transaction import Transaction
68
from hiero_sdk_python.tokens.token_type import TokenType
79
from hiero_sdk_python.query.token_info_query import TokenInfoQuery
810
from hiero_sdk_python.timestamp import Timestamp
@@ -146,3 +148,72 @@ def test_fungible_token_create_with_fee_schedule_key():
146148
# TODO (required TokenFeeScheduleUpdateTransaction)
147149
finally:
148150
env.close()
151+
152+
@pytest.mark.integration
153+
def test_token_create_non_custodial_flow():
154+
"""
155+
Tests the full non-custodial flow:
156+
1. Operator builds a TX using only a PublicKey.
157+
2. Operator gets the transaction bytes.
158+
3. User (with the PrivateKey) signs the bytes.
159+
4. Operator executes the signed transaction.
160+
"""
161+
162+
env = IntegrationTestEnv()
163+
client = env.client
164+
165+
try:
166+
# 1. SETUP: Create a new key pair for the "user"
167+
user_private_key = PrivateKey.generate_ed25519()
168+
user_public_key = user_private_key.public_key()
169+
170+
# =================================================================
171+
# STEP 1 & 2: OPERATOR (CLIENT) BUILDS THE TRANSACTION
172+
# =================================================================
173+
174+
tx = (
175+
TokenCreateTransaction()
176+
.set_token_name("NonCustodialToken")
177+
.set_token_symbol("NCT")
178+
.set_token_type(TokenType.FUNGIBLE_COMMON)
179+
.set_treasury_account_id(client.operator_account_id)
180+
.set_initial_supply(100)
181+
.set_admin_key(user_public_key) # <-- The new feature!
182+
.freeze_with(client)
183+
)
184+
185+
tx_bytes = tx.to_bytes()
186+
187+
# =================================================================
188+
# STEP 3: USER (SIGNER) SIGNS THE TRANSACTION
189+
# =================================================================
190+
191+
tx_from_bytes = Transaction.from_bytes(tx_bytes)
192+
tx_from_bytes.sign(user_private_key)
193+
194+
# =================================================================
195+
# STEP 4: OPERATOR (CLIENT) EXECUTES THE SIGNED TX
196+
# =================================================================
197+
198+
receipt = tx_from_bytes.execute(client)
199+
200+
assert receipt is not None
201+
token_id = receipt.token_id
202+
assert token_id is not None
203+
204+
# PROOF: Query the new token and check if the admin key matches
205+
token_info = TokenInfoQuery(token_id=token_id).execute(client)
206+
207+
assert token_info.admin_key is not None
208+
209+
# This is the STRONG assertion:
210+
# Compare the bytes of the key from the network
211+
# with the bytes of the key we originally used.
212+
admin_key_bytes = token_info.admin_key.to_bytes_raw()
213+
public_key_bytes = user_public_key.to_bytes_raw()
214+
215+
assert admin_key_bytes == public_key_bytes
216+
217+
finally:
218+
# Clean up the environment
219+
env.close()

0 commit comments

Comments
 (0)