Skip to content

Commit 708140f

Browse files
committed
feat: NFTTokenCreateTransaction
feat: NFTTokenCreateTransaction feat: NFTTokenCreateTransaction local tests passed fix: supply type enum fix: supply type enum error fix: supply type default as infinite fix: duplicate imports
1 parent 70de9a5 commit 708140f

File tree

7 files changed

+206
-57
lines changed

7 files changed

+206
-57
lines changed

examples/README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ transaction = TokenCreateTransaction(
8585
token_params=TokenParams(
8686
token_name="ExampleToken",
8787
token_symbol="EXT",
88-
decimals=2, # 0 for NON_FUNGIBLE_UNIQUE
89-
initial_supply=1000, # 0 for NON_FUNGIBLE_UNIQUE
88+
decimals=2, # 0 for NON_FUNGIBLE_UNIQUE
89+
initial_supply=1000, # 0 for NON_FUNGIBLE_UNIQUE
9090
token_type=TokenType.FUNGIBLE_COMMON, # or TokenType.NON_FUNGIBLE_UNIQUE
91+
max_supply=1000 # Must be 0 for INFINITE
92+
supply_type=SupplyType.FINITE, # or SupplyType.INFINITE
9193
freeze_default=False,
9294
treasury_account_id=operator_id
9395
),
@@ -108,9 +110,11 @@ transaction = (
108110
TokenCreateTransaction() # no params => uses default placeholders which are next overwritten.
109111
.set_token_name("ExampleToken")
110112
.set_token_symbol("EXT")
111-
.set_decimals(2) # 0 for NON_FUNGIBLE_UNIQUE
112-
.set_initial_supply(1000) # 0 for NON_FUNGIBLE_UNIQUE
113-
.set_token_type(TokenType.FUNGIBLE_COMMON) # or TokenType.NON_FUNGIBLE_UNIQUE
113+
.set_decimals(2) # 0 for NON_FUNGIBLE_UNIQUE
114+
.set_initial_supply(1000) # 0 for NON_FUNGIBLE_UNIQUE
115+
.set_token_type(TokenType.FUNGIBLE_COMMON) # or TokenType.NON_FUNGIBLE_UNIQUE
116+
.set_max_supply(1000) # Must be 0 for INFINITE
117+
.set_supply_type(SupplyType.FINITE) # or SupplyType.INFINITE
114118
.set_freeze_default(False)
115119
.set_treasury_account_id(operator_id)
116120
.set_admin_key(admin_key) # added but optional. Necessary for Token Delete or Update.
@@ -352,7 +356,7 @@ transaction.execute(client)
352356
transaction.execute(client)
353357
```
354358
#### Method Chaining:
355-
```
359+
```
356360
transaction = (
357361
TopicCreateTransaction()
358362
.set_memo("My Super Topic Memo")

examples/token_create.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
PrivateKey,
2626
TokenCreateTransaction,
2727
Network,
28-
TokenType
28+
TokenType,
29+
SupplyType
2930
)
3031

3132
# Load environment variables from .env file
@@ -60,6 +61,8 @@ def create_token():
6061
.set_initial_supply(10) # 0 for NON_FUNGIBLE_UNIQUE
6162
.set_treasury_account_id(operator_id) # Also known as treasury account
6263
.set_token_type(TokenType.FUNGIBLE_COMMON) # or TokenType.NON_FUNGIBLE_UNIQUE
64+
.set_token_type(SupplyType.FINITE) # or SupplyType.INFINITE
65+
.set_max_supply(100)
6366
.set_admin_key(admin_key) # Optional
6467
.set_supply_key(supply_key) # Optional
6568
.set_freeze_key(freeze_key) # Optional
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from enum import Enum
2+
3+
class SupplyType(Enum):
4+
INFINITE = 0
5+
FINITE = 1

src/hiero_sdk_python/tokens/token_create_transaction.py

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from hiero_sdk_python.hapi.services import token_create_pb2, basic_types_pb2
1616
from hiero_sdk_python.response_code import ResponseCode
1717
from hiero_sdk_python.tokens.token_type import TokenType
18+
from hiero_sdk_python.tokens.supply_type import SupplyType
1819
from hiero_sdk_python.account.account_id import AccountId
1920
from hiero_sdk_python.crypto.private_key import PrivateKey
2021

@@ -25,14 +26,15 @@ class TokenCreateValidator:
2526
"""Token, key and freeze checks for creating a token as per the proto"""
2627

2728
@staticmethod
28-
def validate_token_params(token_params):
29+
def _validate_token_params(token_params):
2930
"""
3031
Ensure valid values for the token characteristics.
3132
"""
3233
TokenCreateValidator._validate_required_fields(token_params)
3334
TokenCreateValidator._validate_name_and_symbol(token_params)
3435
TokenCreateValidator._validate_initial_supply(token_params)
3536
TokenCreateValidator._validate_decimals_and_token_type(token_params)
37+
TokenCreateValidator._validate_supply_max_and_type(token_params)
3638

3739
@staticmethod
3840
def _validate_required_fields(token_params):
@@ -70,15 +72,18 @@ def _validate_initial_supply(token_params):
7072
"""
7173
Ensure initial supply is a non-negative integer and does not exceed max supply.
7274
"""
73-
MAX_SUPPLY = 9_223_372_036_854_775_807 # 2^63 - 1
75+
MAXIMUM_SUPPLY = 9_223_372_036_854_775_807 # 2^63 - 1
7476

7577
if (
7678
not isinstance(token_params.initial_supply, int)
7779
or token_params.initial_supply < 0
7880
):
7981
raise ValueError("Initial supply must be a non-negative integer")
80-
if token_params.initial_supply > MAX_SUPPLY:
81-
raise ValueError(f"Initial supply cannot exceed {MAX_SUPPLY}")
82+
if token_params.initial_supply > MAXIMUM_SUPPLY:
83+
raise ValueError(f"Initial supply cannot exceed {MAXIMUM_SUPPLY}")
84+
if token_params.max_supply > MAXIMUM_SUPPLY:
85+
raise ValueError(f"Max supply cannot exceed {MAXIMUM_SUPPLY}")
86+
8287

8388
@staticmethod
8489
def _validate_decimals_and_token_type(token_params):
@@ -101,7 +106,7 @@ def _validate_decimals_and_token_type(token_params):
101106
raise ValueError("A Non-fungible Unique Token requires an initial supply of zero")
102107

103108
@staticmethod
104-
def validate_token_freeze_status(keys, token_params):
109+
def _validate_token_freeze_status(keys, token_params):
105110
"""Ensure account is not frozen for this token."""
106111
if token_params.freeze_default:
107112
if not keys.freeze_key:
@@ -112,6 +117,27 @@ def validate_token_freeze_status(keys, token_params):
112117
"Token frozen. Please complete a Token Unfreeze Transaction."
113118
)
114119

120+
@staticmethod
121+
def _validate_supply_max_and_type(token_params):
122+
"""Ensure max supply and supply type constraints."""
123+
# An infinite token must have max supply = 0.
124+
# A finite token must have max supply > 0.
125+
if token_params.max_supply != 0: # Setting a max supply is only approprite for a finite token.
126+
if token_params.supply_type != SupplyType.FINITE:
127+
raise ValueError("Setting a max supply field requires setting a finite supply type")
128+
129+
# Finite tokens have the option to set a max supply >0.
130+
# A finite token must have max supply > 0.
131+
if token_params.supply_type == SupplyType.FINITE:
132+
if token_params.max_supply <= 0:
133+
raise ValueError("A finite supply token requires max_supply greater than zero 0")
134+
135+
# Ensure max supply is greater than initial supply
136+
if token_params.initial_supply > token_params.max_supply:
137+
raise ValueError(
138+
"Initial supply cannot exceed the defined max supply for a finite token"
139+
)
140+
115141
@dataclass
116142
class TokenParams:
117143
"""
@@ -124,6 +150,8 @@ class TokenParams:
124150
decimals (optional): The number of decimals for the token. This must be zero for NFTs.
125151
initial_supply (optional): The initial supply of the token.
126152
token_type (optional): The type of the token, defaulting to fungible.
153+
max_supply (optional): The maximum number of fungible tokens or NFT serial numbers that can be in circulation.
154+
supply_type (optional): The token supply status as finite or infinite.
127155
freeze_default (optional): An initial Freeze status for accounts associated to this token.
128156
"""
129157

@@ -133,6 +161,8 @@ class TokenParams:
133161
decimals: int = 0 # Default to zero decimals
134162
initial_supply: int = 0 # Default to zero initial supply
135163
token_type: TokenType = TokenType.FUNGIBLE_COMMON # Default to Fungible Common
164+
max_supply: int = 0 # Since defaulting to infinite
165+
supply_type: SupplyType = SupplyType.INFINITE # Default to infinite
136166
freeze_default: bool = False
137167

138168

@@ -192,6 +222,8 @@ def __init__(self, token_params=None, keys=None):
192222
decimals=0,
193223
initial_supply=0,
194224
token_type=TokenType.FUNGIBLE_COMMON,
225+
max_supply=0,
226+
supply_type=SupplyType.INFINITE,
195227
freeze_default=False
196228
)
197229

@@ -200,7 +232,6 @@ def __init__(self, token_params=None, keys=None):
200232
self._keys = keys if keys else TokenKeys()
201233

202234
self._default_transaction_fee = DEFAULT_TRANSACTION_FEE
203-
self._is_frozen = False
204235

205236
def set_token_params(self, token_params):
206237
"""
@@ -231,6 +262,11 @@ def set_token_symbol(self, symbol):
231262
self._token_params.token_symbol = symbol
232263
return self
233264

265+
def set_treasury_account_id(self, account_id):
266+
self._require_not_frozen()
267+
self._token_params.treasury_account_id = account_id
268+
return self
269+
234270
def set_decimals(self, decimals):
235271
self._require_not_frozen()
236272
self._token_params.decimals = decimals
@@ -245,11 +281,21 @@ def set_token_type(self, token_type):
245281
self._require_not_frozen()
246282
self._token_params.token_type = token_type
247283
return self
284+
285+
def set_max_supply(self, max_supply):
286+
self._require_not_frozen()
287+
self._token_params.max_supply = max_supply
288+
return self
248289

249-
def set_treasury_account_id(self, account_id):
290+
def set_supply_type(self, supply_type):
250291
self._require_not_frozen()
251-
self._token_params.treasury_account_id = account_id
292+
self._token_params.supply_type = supply_type
252293
return self
294+
295+
def set_freeze_default(self, freeze_default):
296+
self._require_not_frozen()
297+
self._token_params.freeze_default = freeze_default
298+
return self
253299

254300
def set_admin_key(self, key):
255301
self._require_not_frozen()
@@ -266,17 +312,6 @@ def set_freeze_key(self, key):
266312
self._keys.freeze_key = key
267313
return self
268314

269-
def freeze(self):
270-
"""Marks the transaction as frozen to prevent further modifications."""
271-
self._is_frozen = True
272-
273-
def _require_not_frozen(self):
274-
"""
275-
Helper method ensuring no changes are made after freeze() has been called.
276-
"""
277-
if self._is_frozen:
278-
raise ValueError("Transaction is frozen and cannot be modified.")
279-
280315
def build_transaction_body(self):
281316
"""
282317
Builds and returns the protobuf transaction body for token creation.
@@ -289,10 +324,10 @@ def build_transaction_body(self):
289324
"""
290325

291326
# Validate all token params
292-
TokenCreateValidator.validate_token_params(self._token_params)
327+
TokenCreateValidator._validate_token_params(self._token_params)
293328

294329
# Validate freeze status
295-
TokenCreateValidator.validate_token_freeze_status(self._keys, self._token_params)
330+
TokenCreateValidator._validate_token_freeze_status(self._keys, self._token_params)
296331

297332
admin_key_proto = None
298333
if self._keys.admin_key:
@@ -318,13 +353,24 @@ def build_transaction_body(self):
318353
else:
319354
token_type_value = self._token_params.token_type
320355

356+
# Ensure supply type is correctly set with default to infinite
357+
if self._token_params.supply_type is None:
358+
supply_type_value = 0 # default INFINITE
359+
elif isinstance(self._token_params.supply_type, SupplyType):
360+
supply_type_value = self._token_params.supply_type.value
361+
else:
362+
supply_type_value = self._token_params.supply_type
363+
321364
# Construct the TokenCreateTransactionBody
322365
token_create_body = token_create_pb2.TokenCreateTransactionBody(
323366
name=self._token_params.token_name,
324367
symbol=self._token_params.token_symbol,
325368
decimals=self._token_params.decimals,
326369
initialSupply=self._token_params.initial_supply,
327370
tokenType=token_type_value,
371+
supplyType=supply_type_value,
372+
maxSupply=self._token_params.max_supply,
373+
freezeDefault=self._token_params.freeze_default,
328374
treasury=self._token_params.treasury_account_id.to_proto(),
329375
adminKey=admin_key_proto,
330376
supplyKey=supply_key_proto,
@@ -358,25 +404,5 @@ def _execute_transaction(self, client, transaction_proto):
358404
raise Exception(f"Error during transaction submission: {error_code} ({error_message})")
359405

360406
receipt = self.get_receipt(client)
361-
362-
363-
364-
365-
366-
367-
368-
369-
370-
371-
372-
373-
374-
375-
376-
377-
378-
379-
380-
381407
return receipt
382408

test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
# Token-related imports
3838
from hiero_sdk_python.tokens.token_type import TokenType
39+
from hiero_sdk_python.tokens.supply_type import SupplyType
3940
from hiero_sdk_python.tokens.token_create_transaction import (
4041
TokenCreateTransaction,
4142
TokenParams,
@@ -125,6 +126,8 @@ def create_fungible_token(client, operator_id, admin_key, supply_key, freeze_key
125126
initial_supply=1000,
126127
treasury_account_id=operator_id,
127128
token_type=TokenType.FUNGIBLE_COMMON,
129+
supply_type=SupplyType.FINITE,
130+
max_supply=10000
128131
)
129132

130133
# Creating TokenKeys
@@ -170,6 +173,8 @@ def create_nft_token(client, operator_id, admin_key, supply_key, freeze_key):
170173
initial_supply=0,
171174
treasury_account_id=operator_id,
172175
token_type=TokenType.NON_FUNGIBLE_UNIQUE,
176+
supply_type=SupplyType.FINITE,
177+
max_supply=10_000
173178
)
174179

175180
# Creating TokenKeys

tests/test_supply_type.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
from hiero_sdk_python.tokens.supply_type import SupplyType
3+
4+
def test_members():
5+
assert SupplyType.INFINITE.value == 0
6+
assert SupplyType.FINITE.value == 1
7+
8+
def test_name():
9+
assert SupplyType.INFINITE.name == "INFINITE"
10+
assert SupplyType.FINITE.name == "FINITE"
11+
12+
def test_supply_type_enum_members():
13+
members = list(SupplyType)
14+
assert len(members) == 2
15+
assert SupplyType.INFINITE in members
16+
assert SupplyType.FINITE in members

0 commit comments

Comments
 (0)