Skip to content

Commit 5df93b7

Browse files
authored
feat(tck): Add tck method for deleteAllowance (hiero-ledger#2338)
Signed-off-by: Ntege Daniel <danientege785@gmail.com>
1 parent 30fb7df commit 5df93b7

22 files changed

Lines changed: 921 additions & 515 deletions

src/hiero_sdk_python/tokens/token_associate_transaction.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,10 @@ def _build_proto_body(self) -> token_associate_pb2.TokenAssociateTransactionBody
9696
9797
Returns:
9898
TokenAssociateTransactionBody: The protobuf body for this transaction.
99-
100-
Raises:
101-
ValueError: If account ID or token IDs are not set.
10299
"""
103-
if not self.account_id or not self.token_ids:
104-
raise ValueError("Account ID and token IDs must be set.")
105-
106100
return token_associate_pb2.TokenAssociateTransactionBody(
107-
account=self.account_id._to_proto(), tokens=[token_id._to_proto() for token_id in self.token_ids]
101+
account=self.account_id._to_proto() if self.account_id else None,
102+
tokens=[token_id._to_proto() for token_id in (self.token_ids or [])],
108103
)
109104

110105
@classmethod

src/hiero_sdk_python/tokens/token_create_transaction.py

Lines changed: 3 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55
Module for creating and validating Hedera token transactions.
66
77
This module includes:
8-
- TokenCreateValidator: Validates token creation parameters.
98
- TokenParams: Represents token attributes.
109
- TokenKeys: Represents cryptographic keys for tokens.
1110
- TokenCreateTransaction: Handles token creation transactions on Hedera.
1211
"""
1312

1413
from __future__ import annotations
1514

15+
import ctypes
1616
from dataclasses import dataclass, field
17-
from typing import Any
1817

1918
from hiero_sdk_python.account.account_id import AccountId
2019
from hiero_sdk_python.channels import _Channel
@@ -104,101 +103,6 @@ class TokenKeys:
104103
fee_schedule_key: Key | None = None
105104

106105

107-
class TokenCreateValidator:
108-
"""Token, key and freeze checks for creating a token as per the proto."""
109-
110-
@staticmethod
111-
def _validate_token_params(token_params: TokenParams) -> None:
112-
"""Ensure valid values for the token characteristics."""
113-
TokenCreateValidator._validate_required_fields(token_params)
114-
TokenCreateValidator._validate_name_and_symbol(token_params)
115-
TokenCreateValidator._validate_initial_supply(token_params)
116-
TokenCreateValidator._validate_decimals_and_token_type(token_params)
117-
TokenCreateValidator._validate_supply_max_and_type(token_params)
118-
119-
@staticmethod
120-
def _validate_token_freeze_status(keys: TokenKeys, token_params: TokenParams) -> None:
121-
"""Ensure account is not frozen for this token."""
122-
if token_params.freeze_default and not keys.freeze_key:
123-
raise ValueError("Token is permanently frozen. Unable to proceed.")
124-
# freezeDefault=True simply starts accounts frozen; allow creation as long as
125-
# a freeze key exists so the treasury (and others) can be unfrozen later.
126-
127-
@staticmethod
128-
def _validate_required_fields(token_params: TokenParams) -> None:
129-
"""Ensure all required fields are present and not empty."""
130-
required_fields: dict[str, Any] = {
131-
"Token name": token_params.token_name,
132-
"Token symbol": token_params.token_symbol,
133-
"Treasury account ID": token_params.treasury_account_id,
134-
}
135-
for _field, _value in required_fields.items():
136-
if not _value:
137-
raise ValueError(f"{_field} is required")
138-
139-
@staticmethod
140-
def _validate_name_and_symbol(token_params: TokenParams) -> None:
141-
"""Ensure the token name & symbol are valid in length and do not contain a NUL character."""
142-
if len(token_params.token_name.encode()) > 100:
143-
raise ValueError("Token name must be between 1 and 100 bytes")
144-
if len(token_params.token_symbol.encode()) > 100:
145-
raise ValueError("Token symbol must be between 1 and 100 bytes")
146-
147-
# Ensure the token name and symbol do not contain a NUL character
148-
for attr in ["token_name", "token_symbol"]:
149-
if "\x00" in getattr(token_params, attr):
150-
raise ValueError(f"{attr.replace('_', ' ').capitalize()} must not contain the Unicode NUL character")
151-
152-
@staticmethod
153-
def _validate_initial_supply(token_params: TokenParams) -> None:
154-
"""Ensure initial supply is a non-negative integer and does not exceed max supply."""
155-
MAXIMUM_SUPPLY = 9_223_372_036_854_775_807 # 2^63 - 1
156-
157-
if token_params.initial_supply < 0:
158-
raise ValueError("Initial supply must be a non-negative integer")
159-
if token_params.initial_supply > MAXIMUM_SUPPLY:
160-
raise ValueError(f"Initial supply cannot exceed {MAXIMUM_SUPPLY}")
161-
if token_params.max_supply > MAXIMUM_SUPPLY:
162-
raise ValueError(f"Max supply cannot exceed {MAXIMUM_SUPPLY}")
163-
164-
@staticmethod
165-
def _validate_decimals_and_token_type(token_params: TokenParams) -> None:
166-
"""Ensure decimals and token_type align with either fungible or non-fungible constraints."""
167-
if token_params.decimals < 0:
168-
raise ValueError("Decimals must be a non-negative integer")
169-
170-
if token_params.token_type == TokenType.FUNGIBLE_COMMON:
171-
# Fungible tokens must have an initial supply > 0
172-
if token_params.initial_supply <= 0:
173-
raise ValueError("A Fungible Token requires an initial supply greater than zero")
174-
175-
elif token_params.token_type == TokenType.NON_FUNGIBLE_UNIQUE:
176-
# Non-fungible tokens must have zero decimals and zero initial supply
177-
if token_params.decimals != 0:
178-
raise ValueError("A Non-fungible Unique Token must have zero decimals")
179-
if token_params.initial_supply != 0:
180-
raise ValueError("A Non-fungible Unique Token requires an initial supply of zero")
181-
182-
@staticmethod
183-
def _validate_supply_max_and_type(token_params: TokenParams) -> None:
184-
"""Ensure max supply and supply type constraints."""
185-
# An infinite token must have max supply = 0.
186-
# A finite token must have max supply > 0.
187-
# Setting a max supply is only approprite for a finite token.
188-
if token_params.max_supply != 0 and token_params.supply_type != SupplyType.FINITE:
189-
raise ValueError("Setting a max supply field requires setting a finite supply type")
190-
191-
# Finite tokens have the option to set a max supply >0.
192-
# A finite token must have max supply > 0.
193-
if token_params.supply_type == SupplyType.FINITE:
194-
if token_params.max_supply <= 0:
195-
raise ValueError("A finite supply token requires max_supply greater than zero 0")
196-
197-
# Ensure max supply is greater than initial supply
198-
if token_params.initial_supply > token_params.max_supply:
199-
raise ValueError("Initial supply cannot exceed the defined max supply for a finite token")
200-
201-
202106
class TokenCreateTransaction(Transaction):
203107
"""
204108
Represents a token creation transaction on the Hedera network.
@@ -473,12 +377,6 @@ def _build_proto_body(self) -> token_create_pb2.TokenCreateTransactionBody:
473377
Raises:
474378
ValueError: If required fields are missing or invalid.
475379
"""
476-
# Validate all token params
477-
TokenCreateValidator._validate_token_params(self._token_params)
478-
479-
# Validate freeze status
480-
TokenCreateValidator._validate_token_freeze_status(self._keys, self._token_params)
481-
482380
# Convert keys
483381
admin_key_proto = key_to_proto(self._keys.admin_key)
484382
supply_key_proto = key_to_proto(self._keys.supply_key)
@@ -505,8 +403,8 @@ def _build_proto_body(self) -> token_create_pb2.TokenCreateTransactionBody:
505403
return token_create_pb2.TokenCreateTransactionBody(
506404
name=self._token_params.token_name,
507405
symbol=self._token_params.token_symbol,
508-
decimals=self._token_params.decimals,
509-
initialSupply=self._token_params.initial_supply,
406+
decimals=ctypes.c_uint32(self._token_params.decimals).value,
407+
initialSupply=ctypes.c_uint64(self._token_params.initial_supply).value,
510408
tokenType=token_type_value,
511409
supplyType=supply_type_value,
512410
maxSupply=self._token_params.max_supply,

src/hiero_sdk_python/tokens/token_freeze_transaction.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,10 @@ def _build_proto_body(self) -> token_freeze_account_pb2.TokenFreezeAccountTransa
7676
7777
Returns:
7878
TokenFreezeAccountTransactionBody: The protobuf body for this transaction.
79-
80-
Raises:
81-
ValueError: If the token ID or account ID is missing.
8279
"""
83-
if not self.token_id:
84-
raise ValueError("Missing required TokenID.")
85-
86-
if not self.account_id:
87-
raise ValueError("Missing required AccountID.")
88-
8980
return token_freeze_account_pb2.TokenFreezeAccountTransactionBody(
90-
token=self.token_id._to_proto(), account=self.account_id._to_proto()
81+
token=self.token_id._to_proto() if self.token_id else None,
82+
account=self.account_id._to_proto() if self.account_id else None,
9183
)
9284

9385
def build_transaction_body(self) -> transaction_pb2.TransactionBody:

src/hiero_sdk_python/tokens/token_mint_transaction.py

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -94,45 +94,21 @@ def set_metadata(self, metadata: bytes | list[bytes]) -> TokenMintTransaction:
9494
self.metadata = metadata
9595
return self
9696

97-
def _validate_parameters(self):
98-
"""Validates the parameters for the token mint transaction."""
99-
if self.token_id is None:
100-
raise ValueError("Token ID is required for minting.")
101-
102-
if (self.amount is not None) and (self.metadata is not None):
103-
raise ValueError("Specify either amount for fungible tokens or metadata for NFTs, not both.")
104-
10597
def _build_proto_body(self) -> token_mint_pb2.TokenMintTransactionBody:
10698
"""
10799
Returns the protobuf body for the token mint transaction (fungible or NFT).
108-
109-
Raises:
110-
ValueError: If required fields are missing or conflicting.
111100
"""
112-
self._validate_parameters()
101+
token_proto = self.token_id._to_proto() if self.token_id else None
102+
tx_body = token_mint_pb2.TokenMintTransactionBody(token=token_proto)
113103

114104
if self.amount is not None:
115-
if self.amount <= 0:
116-
raise ValueError("Amount to mint must be positive.")
117-
# Fungible token
118-
return token_mint_pb2.TokenMintTransactionBody(
119-
token=self.token_id._to_proto(),
120-
amount=self.amount,
121-
metadata=[],
122-
)
105+
tx_body.amount = self.amount
123106

124107
if self.metadata is not None:
125-
# NFT
126-
if not isinstance(self.metadata, list):
127-
raise ValueError("Metadata must be a list of byte arrays for NFTs.")
128-
if not self.metadata:
129-
raise ValueError("Metadata list cannot be empty for NFTs.")
130-
return token_mint_pb2.TokenMintTransactionBody(
131-
token=self.token_id._to_proto(), amount=0, metadata=self.metadata
132-
)
133-
134-
# Neither amount nor metadata is set
135-
raise ValueError("Specify either amount for fungible tokens or metadata for NFTs.")
108+
metadata = [self.metadata] if isinstance(self.metadata, bytes) else self.metadata
109+
tx_body.metadata.extend(metadata)
110+
111+
return tx_body
136112

137113
def build_transaction_body(self) -> transaction_pb2.TransactionBody:
138114
"""

src/hiero_sdk_python/tokens/token_pause_transaction.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,8 @@ def _build_proto_body(self) -> token_pause_pb2.TokenPauseTransactionBody:
6363
6464
Returns:
6565
TokenPauseTransactionBody: The protobuf body for this transaction.
66-
67-
Raises:
68-
ValueError: If no token_id has been set.
6966
"""
70-
if self.token_id is None or self.token_id.num == 0:
71-
raise ValueError("token_id must be set before building the transaction body")
72-
73-
return TokenPauseTransactionBody(token=self.token_id._to_proto())
67+
return TokenPauseTransactionBody(token=self.token_id._to_proto() if self.token_id else None)
7468

7569
def build_transaction_body(self) -> transaction_pb2.TransactionBody:
7670
"""

src/hiero_sdk_python/transaction/transaction_receipt.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ def serial_numbers(self) -> list[int]:
110110
"""
111111
return cast(list[int], self._receipt_proto.serialNumbers)
112112

113+
@property
114+
def new_total_supply(self) -> int:
115+
"""
116+
Returns the token's total supply after a mint or burn transaction.
117+
118+
Returns:
119+
int: The new total token supply, or 0 when not present.
120+
"""
121+
return self._receipt_proto.newTotalSupply
122+
113123
@property
114124
def file_id(self) -> FileId | None:
115125
"""

tck/handlers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
allowance,
88
key,
99
sdk, # setup, reset
10+
token,
1011
topic,
1112
)
1213
from .registry import (

tck/handlers/allowance.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1-
"""TCK RPC handler for the approveAllowance endpoint."""
1+
"""TCK RPC handlers for allowance endpoints."""
22

33
from __future__ import annotations
44

55
from hiero_sdk_python.account.account_allowance_approve_transaction import (
66
AccountAllowanceApproveTransaction,
77
)
8+
from hiero_sdk_python.account.account_allowance_delete_transaction import (
9+
AccountAllowanceDeleteTransaction,
10+
)
811
from hiero_sdk_python.account.account_id import AccountId
912
from hiero_sdk_python.hbar import Hbar
1013
from hiero_sdk_python.response_code import ResponseCode
1114
from hiero_sdk_python.tokens.nft_id import NftId
1215
from hiero_sdk_python.tokens.token_id import TokenId
1316
from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt
1417
from tck.handlers.registry import rpc_method
15-
from tck.param.allowance import AllowanceEntry, ApproveAllowanceParams
16-
from tck.response.allowance import ApproveAllowanceResponse
18+
from tck.param.allowance import (
19+
AllowanceEntry,
20+
ApproveAllowanceParams,
21+
DeleteAllowanceEntry,
22+
DeleteAllowanceParams,
23+
)
24+
from tck.response.allowance import ApproveAllowanceResponse, DeleteAllowanceResponse
1725
from tck.util.client_utils import get_client
1826
from tck.util.constants import DEFAULT_GRPC_TIMEOUT
1927

@@ -24,10 +32,7 @@ def _build_approve_allowance_transaction(
2432
"""Build an AccountAllowanceApproveTransaction from TCK params."""
2533
transaction = AccountAllowanceApproveTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
2634

27-
if not params.allowances:
28-
raise ValueError("allowances list cannot be empty")
29-
30-
for entry in params.allowances:
35+
for entry in params.allowances or []:
3136
_apply_allowance_entry(transaction, entry)
3237

3338
return transaction
@@ -106,7 +111,7 @@ def _apply_allowance_entry(
106111
spender_account_id,
107112
)
108113
else:
109-
# nft object present with only tokenId this is DeleteNftAllowanceAllSerials
114+
# nft object present with only tokenId - this is DeleteNftAllowanceAllSerials
110115
# with no approvedForAll field (defaults to delete)
111116
transaction.delete_token_nft_allowance_all_serials(
112117
token_id,
@@ -129,3 +134,51 @@ def approve_allowance(params: ApproveAllowanceParams) -> ApproveAllowanceRespons
129134
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)
130135

131136
return ApproveAllowanceResponse(status=ResponseCode(receipt.status).name)
137+
138+
139+
def _build_delete_allowance_transaction(
140+
params: DeleteAllowanceParams,
141+
) -> AccountAllowanceDeleteTransaction:
142+
"""Build an AccountAllowanceDeleteTransaction from TCK params."""
143+
transaction = AccountAllowanceDeleteTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
144+
145+
for entry in params.allowances or []:
146+
_apply_delete_allowance_entry(transaction, entry)
147+
148+
return transaction
149+
150+
151+
def _apply_delete_allowance_entry(
152+
transaction: AccountAllowanceDeleteTransaction,
153+
entry: DeleteAllowanceEntry,
154+
) -> None:
155+
"""Apply a single delete allowance entry to the transaction."""
156+
owner_account_id = _parse_optional_account_id(entry.ownerAccountId)
157+
158+
if entry.tokenId is None:
159+
raise ValueError("NFT allowance requires a tokenId")
160+
161+
if not entry.tokenId.strip():
162+
raise ValueError("Token ID cannot be an empty string")
163+
164+
token_id = TokenId.from_string(entry.tokenId)
165+
166+
for serial in entry.serialNumbers or []:
167+
nft_id = NftId(token_id=token_id, serial_number=int(serial))
168+
transaction.delete_all_token_nft_allowances(nft_id, owner_account_id)
169+
170+
171+
@rpc_method("deleteAllowance")
172+
def delete_allowance(params: DeleteAllowanceParams) -> DeleteAllowanceResponse:
173+
"""Delete allowances using TCK deleteAllowance parameters."""
174+
client = get_client(params.sessionId)
175+
176+
transaction = _build_delete_allowance_transaction(params)
177+
178+
if params.commonTransactionParams is not None:
179+
params.commonTransactionParams.apply_common_params(transaction, client)
180+
181+
response = transaction.execute(client, wait_for_receipt=False)
182+
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)
183+
184+
return DeleteAllowanceResponse(status=ResponseCode(receipt.status).name)

0 commit comments

Comments
 (0)