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
15 changes: 6 additions & 9 deletions src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,14 @@ def _build_proto_body(self) -> token_grant_kyc_pb2.TokenGrantKycTransactionBody:

Returns:
TokenGrantKycTransactionBody: The protobuf body for this transaction.

Raises:
ValueError: If the token ID or account ID is not set.
"""
if self.token_id is None:
raise ValueError("Missing token ID")

if self.account_id is None:
raise ValueError("Missing account ID")
kwargs = {}
if self.token_id is not None:
kwargs["token"] = self.token_id._to_proto()
if self.account_id is not None:
kwargs["account"] = self.account_id._to_proto()

return TokenGrantKycTransactionBody(token=self.token_id._to_proto(), account=self.account_id._to_proto())
return TokenGrantKycTransactionBody(**kwargs)

def build_transaction_body(self) -> transaction_pb2.TransactionBody:
"""
Expand Down
15 changes: 6 additions & 9 deletions src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,14 @@ def _build_proto_body(self) -> token_revoke_kyc_pb2.TokenRevokeKycTransactionBod

Returns:
TokenRevokeKycTransactionBody: The protobuf body for this transaction.

Raises:
ValueError: If the token ID or account ID is not set.
"""
if self.token_id is None:
raise ValueError("Missing token ID")

if self.account_id is None:
raise ValueError("Missing account ID")
kwargs = {}
if self.token_id is not None:
kwargs["token"] = self.token_id._to_proto()
if self.account_id is not None:
kwargs["account"] = self.account_id._to_proto()

return TokenRevokeKycTransactionBody(token=self.token_id._to_proto(), account=self.account_id._to_proto())
return TokenRevokeKycTransactionBody(**kwargs)

def build_transaction_body(self) -> transaction_pb2.AtomicBatchTransactionBody:
"""
Expand Down
62 changes: 60 additions & 2 deletions tck/handlers/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction
from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus
from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction
from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction
from hiero_sdk_python.tokens.token_id import TokenId
from hiero_sdk_python.tokens.token_info import TokenInfo
from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus
from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction
from hiero_sdk_python.tokens.token_pause_status import TokenPauseStatus
from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction
from hiero_sdk_python.tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction
from hiero_sdk_python.tokens.token_type import TokenType
from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt
from tck.handlers.registry import rpc_method
Expand All @@ -41,8 +43,10 @@
DeleteTokenParams,
FreezeTokenParams,
GetTokenInfoParams,
GrantTokenKycParams,
MintTokenParams,
PauseTokenParams,
RevokeTokenKycParams,
)
from tck.response.token import (
AirdropTokenResponse,
Expand All @@ -53,8 +57,10 @@
DeleteTokenResponse,
FreezeTokenResponse,
GetTokenInfoResponse,
GrantTokenKycResponse,
MintTokenResponse,
PauseTokenResponse,
RevokeTokenKycResponse,
)
from tck.util.client_utils import get_client
from tck.util.constants import DEFAULT_GRPC_TIMEOUT
Expand Down Expand Up @@ -296,13 +302,11 @@ def _build_delete_token_transaction(params: DeleteTokenParams) -> TokenDeleteTra
def _build_freeze_token_transaction(params: FreezeTokenParams) -> TokenFreezeTransaction:
"""Build a TokenFreezeTransaction from TCK params."""
transaction = TokenFreezeTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)

if params.tokenId is not None:
transaction.set_token_id(TokenId.from_string(params.tokenId))

if params.accountId is not None:
transaction.set_account_id(AccountId.from_string(params.accountId))

return transaction


Expand All @@ -316,6 +320,28 @@ def _build_pause_token_transaction(params: PauseTokenParams) -> TokenPauseTransa
return transaction


def _build_grant_token_kyc_transaction(params: GrantTokenKycParams) -> TokenGrantKycTransaction:
Comment thread
iron-prog marked this conversation as resolved.
"""Build a TokenGrantKycTransaction from TCK params."""
transaction = TokenGrantKycTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
if params.tokenId is not None:
transaction.set_token_id(TokenId.from_string(params.tokenId))

if params.accountId is not None:
transaction.set_account_id(AccountId.from_string(params.accountId))
return transaction


def _build_revoke_token_kyc_transaction(params: RevokeTokenKycParams) -> TokenRevokeKycTransaction:
"""Build a TokenRevokeKycTransaction from TCK params."""
transaction = TokenRevokeKycTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
if params.tokenId is not None:
transaction.set_token_id(TokenId.from_string(params.tokenId))

if params.accountId is not None:
transaction.set_account_id(AccountId.from_string(params.accountId))
return transaction


@rpc_method("associateToken")
def associate_token(params: AssociateTokenParams) -> AssociateTokenResponse:
"""Associate tokens with an account using TCK associateToken parameters."""
Expand Down Expand Up @@ -380,6 +406,38 @@ def pause_token(params: PauseTokenParams) -> PauseTokenResponse:
return PauseTokenResponse(status=ResponseCode(receipt.status).name)


@rpc_method("grantTokenKyc")
Comment thread
iron-prog marked this conversation as resolved.
def grant_token_kyc(params: GrantTokenKycParams) -> GrantTokenKycResponse:
"""Grant KYC to an account for a token using TCK grantTokenKyc parameters."""
client = get_client(params.sessionId)

transaction = _build_grant_token_kyc_transaction(params)

if params.commonTransactionParams is not None:
params.commonTransactionParams.apply_common_params(transaction, client)

response = transaction.execute(client, wait_for_receipt=False)
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)

return GrantTokenKycResponse(status=ResponseCode(receipt.status).name)


@rpc_method("revokeTokenKyc")
def revoke_token_kyc(params: RevokeTokenKycParams) -> RevokeTokenKycResponse:
"""Revoke KYC from an account for a token using TCK revokeTokenKyc parameters."""
client = get_client(params.sessionId)

transaction = _build_revoke_token_kyc_transaction(params)

if params.commonTransactionParams is not None:
params.commonTransactionParams.apply_common_params(transaction, client)

response = transaction.execute(client, wait_for_receipt=False)
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)

return RevokeTokenKycResponse(status=ResponseCode(receipt.status).name)


def _build_airdrop_token_transaction(params: AirdropTokenParams) -> TokenAirdropTransaction:
"""Build a TokenAirdropTransaction from TCK params."""
tx = TokenAirdropTransaction()
Expand Down
36 changes: 36 additions & 0 deletions tck/param/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,42 @@ def parse_json_params(cls, params: dict) -> FreezeTokenParams:
)


@dataclass
class GrantTokenKycParams(BaseTransactionParams):
"""Request parameters for the grantTokenKyc endpoint."""

tokenId: str | None = None
accountId: str | None = None

@classmethod
def parse_json_params(cls, params: dict) -> GrantTokenKycParams:
"""Parse JSON-RPC params into a GrantTokenKycParams instance."""
return cls(
tokenId=params.get("tokenId"),
accountId=params.get("accountId"),
sessionId=parse_session_id(params),
commonTransactionParams=parse_common_transaction_params(params),
)


@dataclass
class RevokeTokenKycParams(BaseTransactionParams):
"""Request parameters for the revokeTokenKyc endpoint."""

tokenId: str | None = None
accountId: str | None = None

@classmethod
def parse_json_params(cls, params: dict) -> RevokeTokenKycParams:
"""Parse JSON-RPC params into a RevokeTokenKycParams instance."""
return cls(
tokenId=params.get("tokenId"),
accountId=params.get("accountId"),
sessionId=parse_session_id(params),
commonTransactionParams=parse_common_transaction_params(params),
)


@dataclass
class PauseTokenParams(BaseTransactionParams):
"""Request parameters for the pauseToken endpoint."""
Expand Down
10 changes: 10 additions & 0 deletions tck/response/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ class FreezeTokenResponse(StatusOnlyResponse):
"""Response payload for freezeToken."""


@dataclass
class GrantTokenKycResponse(StatusOnlyResponse):
"""Response payload for grantTokenKyc."""


@dataclass
class RevokeTokenKycResponse(StatusOnlyResponse):
"""Response payload for revokeTokenKyc."""


@dataclass
class PauseTokenResponse(StatusOnlyResponse):
"""Response payload for pauseToken."""
Expand Down
20 changes: 10 additions & 10 deletions tests/unit/token_grant_kyc_transaction_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@ def test_build_transaction_body(mock_account_ids):
assert transaction_body.tokenGrantKyc.account == account_id._to_proto()


def test_build_transaction_body_validation(mock_account_ids):
"""Test validation when building transaction body."""
def test_build_transaction_body_allows_missing_ids(mock_account_ids):
"""Do not reject missing IDs locally; defer validation to the network per TCK."""
account_id, _, _, token_id, _ = mock_account_ids

# Test missing token ID
# Missing token ID: build must succeed, with the token field left unset.
grant_kyc_tx = TokenGrantKycTransaction(account_id=account_id)
body = grant_kyc_tx._build_proto_body()
assert not body.HasField("token")
assert body.account == account_id._to_proto()

with pytest.raises(ValueError, match="Missing token ID"):
grant_kyc_tx.build_transaction_body()

# Test missing account ID
# Missing account ID: build must succeed, with the account field left unset.
grant_kyc_tx = TokenGrantKycTransaction(token_id=token_id)

with pytest.raises(ValueError, match="Missing account ID"):
grant_kyc_tx.build_transaction_body()
body = grant_kyc_tx._build_proto_body()
assert body.token == token_id._to_proto()
assert not body.HasField("account")


def test_constructor_with_parameters(mock_account_ids):
Expand Down
20 changes: 10 additions & 10 deletions tests/unit/token_revoke_kyc_transaction_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@ def test_build_transaction_body(mock_account_ids):
assert transaction_body.tokenRevokeKyc.account == account_id._to_proto()


def test_build_transaction_body_validation(mock_account_ids):
"""Test validation when building transaction body."""
def test_build_transaction_body_allows_missing_ids(mock_account_ids):
"""Allow missing IDs so validation is performed by the network, per the TCK spec."""
account_id, _, _, token_id, _ = mock_account_ids

# Test missing token ID
# Missing token ID: build must succeed, with the token field left unset.
revoke_kyc_tx = TokenRevokeKycTransaction(account_id=account_id)
body = revoke_kyc_tx._build_proto_body()
assert not body.HasField("token")
assert body.account == account_id._to_proto()

with pytest.raises(ValueError, match="Missing token ID"):
revoke_kyc_tx.build_transaction_body()

# Test missing account ID
# Missing account ID: build must succeed, with the account field left unset.
revoke_kyc_tx = TokenRevokeKycTransaction(token_id=token_id)

with pytest.raises(ValueError, match="Missing account ID"):
revoke_kyc_tx.build_transaction_body()
body = revoke_kyc_tx._build_proto_body()
assert body.token == token_id._to_proto()
assert not body.HasField("account")


def test_constructor_with_parameters(mock_account_ids):
Expand Down
Loading