Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Add examples/tokens/token_create_transaction_pause_key.py example demonstrating token pause/unpause behavior and pause key usage (#833)
- Added `docs/sdk_developers/training/transaction_lifecycle.md` to explain the typical lifecycle of executing a transaction using the Hedera Python SDK.
- Add inactivity bot workflow to unassign stale issue assignees (#952)

### Changed
- Allow `PublicKey` for `TokenUpdateKeys` in `TokenUpdateTransaction`, enabling non-custodial workflows where operators can build transactions using only public keys (#934).

### Fixed
- Fixed inactivity bot workflow not checking out repository before running (#964)
Expand Down
36 changes: 12 additions & 24 deletions src/hiero_sdk_python/consensus/topic_create_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
from hiero_sdk_python.channels import _Channel
from hiero_sdk_python.executable import _Method
from hiero_sdk_python.account.account_id import AccountId
from hiero_sdk_python.crypto.private_key import PrivateKey
from hiero_sdk_python.crypto.public_key import PublicKey

Key = Union[PrivateKey, PublicKey]
from hiero_sdk_python.utils.key_utils import Key, key_to_proto


class TopicCreateTransaction(Transaction):
Expand Down Expand Up @@ -174,25 +171,16 @@ def set_fee_exempt_keys(self, keys: List[Key]) -> "TopicCreateTransaction":
self.fee_exempt_keys = keys
return self

def _to_proto_key(self, key: Optional[Key]) -> Optional[basic_types_pb2.Key]:
def _to_proto_key(self, key: Optional[Key]):
"""
Helper method to convert a key (PrivateKey or PublicKey) to protobuf Key format.
Backwards-compatible wrapper around `key_to_proto` for converting SDK keys
(PrivateKey or PublicKey) to protobuf `Key` messages.

Args:
key (Optional[Key]): The key to convert (PrivateKey or PublicKey), or None

Returns:
basic_types_pb2.Key (Optional): The protobuf key or None if key is None
This exists so that existing unit tests and any external callers that rely
on `_to_proto_key` continue to work after centralizing the logic in
`hiero_sdk_python.utils.key_utils.key_to_proto`.
"""
if not key:
return None

# If it's a PrivateKey, get the public key first, then convert to proto
if isinstance(key, PrivateKey):
return key.public_key()._to_proto()

# If it's a PublicKey, convert directly to proto
return key._to_proto()
return key_to_proto(key)

def _build_proto_body(self) -> consensus_create_topic_pb2.ConsensusCreateTopicTransactionBody:
"""
Expand All @@ -202,8 +190,8 @@ def _build_proto_body(self) -> consensus_create_topic_pb2.ConsensusCreateTopicTr
ConsensusCreateTopicTransactionBody: The protobuf body for this transaction.
"""
return consensus_create_topic_pb2.ConsensusCreateTopicTransactionBody(
adminKey=self._to_proto_key(self.admin_key),
submitKey=self._to_proto_key(self.submit_key),
adminKey=key_to_proto(self.admin_key),
submitKey=key_to_proto(self.submit_key),
autoRenewPeriod=(
self.auto_renew_period._to_proto()
if self.auto_renew_period is not None
Expand All @@ -214,8 +202,8 @@ def _build_proto_body(self) -> consensus_create_topic_pb2.ConsensusCreateTopicTr
else None),
memo=self.memo,
custom_fees=[custom_fee._to_topic_fee_proto() for custom_fee in self.custom_fees],
fee_schedule_key=self._to_proto_key(self.fee_schedule_key),
fee_exempt_key_list=[self._to_proto_key(key) for key in self.fee_exempt_keys],
fee_schedule_key=key_to_proto(self.fee_schedule_key),
fee_exempt_key_list=[key_to_proto(key) for key in self.fee_exempt_keys],
)

def build_transaction_body(self) -> transaction_pb2.TransactionBody:
Expand Down
64 changes: 20 additions & 44 deletions src/hiero_sdk_python/tokens/token_create_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,12 @@
from hiero_sdk_python.tokens.token_type import TokenType
from hiero_sdk_python.tokens.supply_type import SupplyType
from hiero_sdk_python.account.account_id import AccountId
from hiero_sdk_python.crypto.private_key import PrivateKey
from hiero_sdk_python.crypto.public_key import PublicKey
from hiero_sdk_python.tokens.custom_fee import CustomFee
from hiero_sdk_python.utils.key_utils import Key, key_to_proto

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

Key = Union[PrivateKey, PublicKey]

@dataclass
class TokenParams:
"""
Expand Down Expand Up @@ -446,38 +443,6 @@ def set_metadata(self, metadata: bytes | str) -> "TokenCreateTransaction":
self._token_params.metadata = metadata
return self

def _to_proto_key(self, key: Optional[Key]) -> Optional[basic_types_pb2.Key]:
"""
Helper method to convert a PrivateKey or PublicKey to the protobuf Key format.

This ensures only public keys are serialized:
- If a PublicKey is provided, it is used directly.
- If a PrivateKey is provided, its corresponding public key is extracted and used.

Args:
key (Key, Optional): The PrivateKey or PublicKey to convert.

Returns:
basic_types_pb2.Key (Optional): The protobuf key, or None.

Raises:
TypeError: If the provided key is not a PrivateKey, PublicKey, or None.
"""
if not key:
return None

# If it's a PrivateKey, get its public key first
if isinstance(key, PrivateKey):
return key.public_key()._to_proto()

# If it's already a PublicKey, just convert it
if isinstance(key, PublicKey):
return key._to_proto()

# Safety net: This will fail if a non-key is passed
raise TypeError("Key must be of type PrivateKey or PublicKey")


def freeze_with(self, client) -> "TokenCreateTransaction":
"""
Freeze the transaction with the given client.
Expand All @@ -501,6 +466,17 @@ def freeze_with(self, client) -> "TokenCreateTransaction":
return super().freeze_with(client)


def _to_proto_key(self, key: Optional[Key]):
"""
Backwards-compatible wrapper around `key_to_proto` for converting SDK keys
(PrivateKey or PublicKey) to protobuf `Key` messages.

This exists so that existing unit tests and any external callers that rely
on `_to_proto_key` continue to work after centralizing the logic in
`hiero_sdk_python.utils.key_utils.key_to_proto`.
"""
return key_to_proto(key)

def _build_proto_body(self) -> token_create_pb2.TokenCreateTransactionBody:
"""
Returns the protobuf body for the token create transaction.
Expand All @@ -518,14 +494,14 @@ def _build_proto_body(self) -> token_create_pb2.TokenCreateTransactionBody:
TokenCreateValidator._validate_token_freeze_status(self._keys, self._token_params)

# Convert keys
admin_key_proto = self._to_proto_key(self._keys.admin_key)
supply_key_proto = self._to_proto_key(self._keys.supply_key)
freeze_key_proto = self._to_proto_key(self._keys.freeze_key)
wipe_key_proto = self._to_proto_key(self._keys.wipe_key)
metadata_key_proto = self._to_proto_key(self._keys.metadata_key)
pause_key_proto = self._to_proto_key(self._keys.pause_key)
kyc_key_proto = self._to_proto_key(self._keys.kyc_key)
fee_schedules_key_proto = self._to_proto_key(self._keys.fee_schedule_key);
admin_key_proto = key_to_proto(self._keys.admin_key)
supply_key_proto = key_to_proto(self._keys.supply_key)
freeze_key_proto = key_to_proto(self._keys.freeze_key)
wipe_key_proto = key_to_proto(self._keys.wipe_key)
metadata_key_proto = key_to_proto(self._keys.metadata_key)
pause_key_proto = key_to_proto(self._keys.pause_key)
kyc_key_proto = key_to_proto(self._keys.kyc_key)
fee_schedules_key_proto = key_to_proto(self._keys.fee_schedule_key)

# Resolve enum values with defaults
token_type_value = (
Expand Down
82 changes: 41 additions & 41 deletions src/hiero_sdk_python/tokens/token_update_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from google.protobuf.wrappers_pb2 import (BytesValue, StringValue)

from hiero_sdk_python.Duration import Duration
from hiero_sdk_python.crypto.private_key import PrivateKey
from hiero_sdk_python.hbar import Hbar
from hiero_sdk_python.timestamp import Timestamp
from hiero_sdk_python.tokens.token_id import TokenId
Expand All @@ -23,6 +22,7 @@
SchedulableTransactionBody,
)
from hiero_sdk_python.hapi.services import token_update_pb2, transaction_pb2
from hiero_sdk_python.utils.key_utils import Key, key_to_proto

@dataclass
class TokenUpdateParams:
Expand Down Expand Up @@ -59,14 +59,14 @@ class TokenUpdateKeys:
metadata_key: The new metadata key for the token.
pause_key: The new pause key for the token.
"""
admin_key: Optional[PrivateKey] = None
supply_key: Optional[PrivateKey] = None
freeze_key: Optional[PrivateKey] = None
wipe_key: Optional[PrivateKey] = None
metadata_key: Optional[PrivateKey] = None
pause_key: Optional[PrivateKey] = None
kyc_key: Optional[PrivateKey] = None
fee_schedule_key: Optional[PrivateKey] = None
admin_key: Optional[Key] = None
supply_key: Optional[Key] = None
freeze_key: Optional[Key] = None
wipe_key: Optional[Key] = None
metadata_key: Optional[Key] = None
pause_key: Optional[Key] = None
kyc_key: Optional[Key] = None
fee_schedule_key: Optional[Key] = None


class TokenUpdateTransaction(Transaction):
Expand Down Expand Up @@ -124,14 +124,14 @@ def __init__(

# Initialize keys attributes
keys: TokenUpdateKeys = token_keys or TokenUpdateKeys()
self.admin_key: Optional[PrivateKey] = keys.admin_key
self.freeze_key: Optional[PrivateKey] = keys.freeze_key
self.wipe_key: Optional[PrivateKey] = keys.wipe_key
self.supply_key: Optional[PrivateKey] = keys.supply_key
self.pause_key: Optional[PrivateKey] = keys.pause_key
self.metadata_key: Optional[PrivateKey] = keys.metadata_key
self.kyc_key: Optional[PrivateKey] = keys.kyc_key
self.fee_schedule_key: Optional[PrivateKey] = keys.fee_schedule_key
self.admin_key: Optional[Key] = keys.admin_key
self.freeze_key: Optional[Key] = keys.freeze_key
self.wipe_key: Optional[Key] = keys.wipe_key
self.supply_key: Optional[Key] = keys.supply_key
self.pause_key: Optional[Key] = keys.pause_key
self.metadata_key: Optional[Key] = keys.metadata_key
self.kyc_key: Optional[Key] = keys.kyc_key
self.fee_schedule_key: Optional[Key] = keys.fee_schedule_key

self.token_key_verification_mode: TokenKeyValidation = token_key_verification_mode

Expand Down Expand Up @@ -284,13 +284,13 @@ def set_expiration_time(self, expiration_time: Timestamp) -> "TokenUpdateTransac

def set_admin_key(
self,
admin_key: PrivateKey
admin_key: Key
) -> "TokenUpdateTransaction":
"""
Sets the new admin key for the token.

Args:
admin_key (PrivateKey): The new admin key to set.
admin_key (Key): The new admin key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -301,13 +301,13 @@ def set_admin_key(

def set_freeze_key(
self,
freeze_key: PrivateKey
freeze_key: Key
) -> "TokenUpdateTransaction":
"""
Sets the new freeze key for the token.

Args:
freeze_key (PrivateKey): The new freeze key to set.
freeze_key (Key): The new freeze key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -318,13 +318,13 @@ def set_freeze_key(

def set_wipe_key(
self,
wipe_key: PrivateKey
wipe_key: Key
) -> "TokenUpdateTransaction":
"""
Sets the new wipe key for the token.

Args:
wipe_key (PrivateKey): The new wipe key to set.
wipe_key (Key): The new wipe key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -335,13 +335,13 @@ def set_wipe_key(

def set_supply_key(
self,
supply_key: PrivateKey
supply_key: Key
) -> "TokenUpdateTransaction":
"""
Sets the new supply key for the token.

Args:
supply_key (PrivateKey): The new supply key to set.
supply_key (Key): The new supply key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -352,13 +352,13 @@ def set_supply_key(

def set_pause_key(
self,
pause_key: PrivateKey
pause_key: Key
) -> "TokenUpdateTransaction":
"""
Sets the new pause key for the token.

Args:
pause_key (PrivateKey): The new pause key to set.
pause_key (Key): The new pause key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -369,13 +369,13 @@ def set_pause_key(

def set_metadata_key(
self,
metadata_key: PrivateKey
metadata_key: Key
) -> "TokenUpdateTransaction":
"""
Sets the new metadata key for the token.

Args:
metadata_key (PrivateKey): The new metadata key to set.
metadata_key (Key): The new metadata key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -384,12 +384,12 @@ def set_metadata_key(
self.metadata_key = metadata_key
return self

def set_kyc_key(self, kyc_key: PrivateKey) -> "TokenUpdateTransaction":
def set_kyc_key(self, kyc_key: Key) -> "TokenUpdateTransaction":
"""
Sets the kyc key for the token

Args:
kyc_key (Private Key): The new kyc_key to set.
kyc_key (Key): The new kyc_key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand All @@ -398,12 +398,12 @@ def set_kyc_key(self, kyc_key: PrivateKey) -> "TokenUpdateTransaction":
self.kyc_key = kyc_key
return self

def set_fee_schedule_key(self, fee_schedule_key: PrivateKey) -> "TokenUpdateTransaction":
def set_fee_schedule_key(self, fee_schedule_key: Key) -> "TokenUpdateTransaction":
"""
Sets the fee schedule key for the token

Args:
fee_schedule_key (Private Key): The new fee_schedule_key to set.
fee_schedule_key (Key): The new fee_schedule_key to set (PrivateKey or PublicKey).

Returns:
TokenUpdateTransaction: This transaction instance.
Expand Down Expand Up @@ -507,18 +507,18 @@ def _set_keys_to_proto(
Sets the keys to the protobuf transaction body.
"""
if self.admin_key:
token_update_body.adminKey.CopyFrom(self.admin_key.public_key()._to_proto())
token_update_body.adminKey.CopyFrom(key_to_proto(self.admin_key))
if self.freeze_key:
token_update_body.freezeKey.CopyFrom(self.freeze_key.public_key()._to_proto())
token_update_body.freezeKey.CopyFrom(key_to_proto(self.freeze_key))
if self.wipe_key:
token_update_body.wipeKey.CopyFrom(self.wipe_key.public_key()._to_proto())
token_update_body.wipeKey.CopyFrom(key_to_proto(self.wipe_key))
if self.supply_key:
token_update_body.supplyKey.CopyFrom(self.supply_key.public_key()._to_proto())
token_update_body.supplyKey.CopyFrom(key_to_proto(self.supply_key))
if self.metadata_key:
token_update_body.metadata_key.CopyFrom(self.metadata_key.public_key()._to_proto())
token_update_body.metadata_key.CopyFrom(key_to_proto(self.metadata_key))
if self.pause_key:
token_update_body.pause_key.CopyFrom(self.pause_key.public_key()._to_proto())
token_update_body.pause_key.CopyFrom(key_to_proto(self.pause_key))
if self.kyc_key:
token_update_body.kycKey.CopyFrom(self.kyc_key.public_key()._to_proto())
token_update_body.kycKey.CopyFrom(key_to_proto(self.kyc_key))
if self.fee_schedule_key:
token_update_body.fee_schedule_key.CopyFrom(self.fee_schedule_key.public_key()._to_proto())
token_update_body.fee_schedule_key.CopyFrom(key_to_proto(self.fee_schedule_key))
Loading