-
Notifications
You must be signed in to change notification settings - Fork 86
feat: add TokenFeeScheduleUpdateTransaction class and tests #722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
exploreriii
merged 17 commits into
hiero-ledger:main
from
Akshat8510:feat/token-fee-schedule-update-v2
Nov 6, 2025
Merged
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
8cae478
feat: add TokenFeeScheduleUpdateTransaction class and tests
Akshat8510 a67d1f4
Update token_update_fee_schedule_fungible.py
Akshat8510 48cee14
Update token_update_fee_schedule_nft.py
Akshat8510 044880f
Merge branch 'main' into feat/token-fee-schedule-update-v2
Akshat8510 0f13d85
Merge branch 'main' into feat/token-fee-schedule-update-v2
Akshat8510 fbe9889
Update token_fee_schedule_update_transaction.py
Akshat8510 085dcee
Update token_update_fee_schedule_fungible.py
Akshat8510 cfa68c4
Update token_update_fee_schedule_nft.py
Akshat8510 2c63d1c
Update CHANGELOG.md
Akshat8510 527361c
Update test_token_fee_schedule_update_transaction_e2e.py
Akshat8510 d2dba17
Update token_update_fee_schedule_fungible.py
Akshat8510 f9684de
Update token_update_fee_schedule_nft.py
Akshat8510 e3decb9
Merge branch 'main' into feat/token-fee-schedule-update-v2
Akshat8510 6c3fe05
Update token_update_fee_schedule_nft.py
Akshat8510 43a6909
Merge branch 'main' into feat/token-fee-schedule-update-v2
Akshat8510 ce38e0c
Update token_update_fee_schedule_nft.py
Akshat8510 9198d81
Update token_update_fee_schedule_fungible.py
Akshat8510 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| """Example: Update Custom Fees for a Fungible Token""" | ||
| import os | ||
| import sys | ||
| from dotenv import load_dotenv | ||
|
|
||
| from hiero_sdk_python import Client, AccountId, PrivateKey, Network | ||
| from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction, TokenParams, TokenKeys | ||
| from hiero_sdk_python.tokens.token_type import TokenType | ||
| from hiero_sdk_python.tokens.supply_type import SupplyType | ||
| from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import TokenFeeScheduleUpdateTransaction | ||
| from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee | ||
| from hiero_sdk_python.response_code import ResponseCode | ||
| from hiero_sdk_python.query.token_info_query import TokenInfoQuery | ||
|
|
||
|
|
||
| def setup_client(): | ||
| """Initialize client and operator credentials from .env.""" | ||
| load_dotenv() | ||
| try: | ||
| client = Client(Network(os.getenv("NETWORK", "testnet"))) | ||
| operator_id = AccountId.from_string(os.getenv("OPERATOR_ID")) | ||
| operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY")) | ||
| client.set_operator(operator_id, operator_key) | ||
| print(f" Operator set: {operator_id}\n") | ||
| return client, operator_id, operator_key | ||
| except Exception as e: | ||
| print(f" Error setting up client: {e}") | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| def create_fungible_token(client, operator_id, fee_schedule_key): | ||
| """Create a fungible token with only a fee schedule key.""" | ||
| print(" Creating fungible token...") | ||
| token_params = TokenParams( | ||
| token_name="Fungible Fee Example", | ||
| token_symbol="FFE", | ||
| treasury_account_id=operator_id, | ||
| initial_supply=1000, | ||
| decimals=2, | ||
| token_type=TokenType.FUNGIBLE_COMMON, | ||
| supply_type=SupplyType.FINITE, | ||
| max_supply=2000, | ||
| custom_fees=[], # No custom fees at creation | ||
| ) | ||
|
|
||
| keys = TokenKeys( | ||
| fee_schedule_key=fee_schedule_key | ||
| ) | ||
|
|
||
| tx = TokenCreateTransaction(token_params=token_params, keys=keys) | ||
|
|
||
| tx.freeze_with(client) | ||
| receipt = tx.execute(client) | ||
|
|
||
| if receipt.status != ResponseCode.SUCCESS: | ||
| print(f" Token creation failed: {ResponseCode(receipt.status).name}\n") | ||
| client.close() | ||
| sys.exit(1) | ||
|
|
||
| token_id = receipt.token_id | ||
| print(f" Token created successfully: {token_id}\n") | ||
| return token_id | ||
|
|
||
|
|
||
| def update_custom_fixed_fee(client, token_id, fee_schedule_key, treasury_account_id): | ||
| """Updates the token's fee schedule with a new fixed fee.""" | ||
| print(f" Updating custom fixed fee for token {token_id}...") | ||
| new_fees = [ | ||
| # Send the custom fee to the token's treasury account | ||
| CustomFixedFee(amount=150, fee_collector_account_id=treasury_account_id) | ||
| ] | ||
| print(f" Defined {len(new_fees)} new custom fees.\n") | ||
| tx = ( | ||
| TokenFeeScheduleUpdateTransaction() | ||
| .set_token_id(token_id) | ||
| .set_custom_fees(new_fees) | ||
| ) | ||
|
|
||
| # The transaction MUST be signed by the fee_schedule_key | ||
| tx.freeze_with(client).sign(fee_schedule_key) | ||
|
|
||
| try: | ||
| receipt = tx.execute(client) | ||
| if receipt.status != ResponseCode.SUCCESS: | ||
| print(f" Fee schedule update failed: {ResponseCode(receipt.status).name}\n") | ||
| else: | ||
| print(" Fee schedule updated successfully.\n") | ||
| except Exception as e: | ||
| print(f" Error during fee schedule update execution: {e}\n") | ||
|
|
||
|
|
||
| def query_token_info(client, token_id): | ||
| """Query token info to show the custom fees.""" | ||
| print(f"\nQuerying token info for {token_id}...\n") | ||
| try: | ||
| token_info = TokenInfoQuery(token_id=token_id).execute(client) | ||
| print("Token Info Retrieved Successfully!\n") | ||
|
|
||
| print(f"Name: {getattr(token_info, 'name', 'N/A')}") | ||
| print(f"Symbol: {getattr(token_info, 'symbol', 'N/A')}") | ||
| print(f"Total Supply: {getattr(token_info, 'total_supply', 'N/A')}") | ||
| print(f"Treasury: {getattr(token_info, 'treasury_account_id', 'N/A')}") | ||
| print(f"Decimals: {getattr(token_info, 'decimals', 'N/A')}") | ||
| print(f"Max Supply: {getattr(token_info, 'max_supply', 'N/A')}") | ||
| print() | ||
|
|
||
| custom_fees = getattr(token_info, "custom_fees", []) | ||
| if custom_fees: | ||
| print(f"Found {len(custom_fees)} custom fee(s):") | ||
| for i, fee in enumerate(custom_fees, 1): | ||
| print(f" Fee #{i}: {type(fee).__name__}") | ||
| print(f" Collector: {getattr(fee, 'fee_collector_account_id', 'N/A')}") | ||
| if isinstance(fee, CustomFixedFee): | ||
| print(f" Amount: {getattr(fee, 'amount', 'N/A')}") | ||
| else: | ||
| print("No custom fees defined for this token.\n") | ||
|
|
||
| except Exception as e: | ||
| print(f"Error querying token info: {e}") | ||
|
|
||
|
|
||
| def main(): | ||
| client, operator_id, operator_key = setup_client() | ||
| token_id = None | ||
| try: | ||
| fee_key = operator_key | ||
|
|
||
| token_id = create_fungible_token(client, operator_id, fee_key) | ||
|
|
||
| if token_id: | ||
| query_token_info(client, token_id) | ||
| # Pass the operator_id as the fee collector (which is also the treasury) | ||
| update_custom_fixed_fee(client, token_id, fee_key, operator_id) | ||
| query_token_info(client, token_id) | ||
|
|
||
| except Exception as e: | ||
| print(f" Error during token operations: {e}") | ||
| finally: | ||
| client.close() | ||
| print("\n Client closed. Example complete.") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| """Example: Update Custom Fees for an NFT""" | ||
| import os | ||
| import sys | ||
| from dotenv import load_dotenv | ||
|
|
||
| from hiero_sdk_python import Client, AccountId, PrivateKey, Network | ||
| from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction, TokenParams, TokenKeys | ||
| from hiero_sdk_python.tokens.token_type import TokenType | ||
| from hiero_sdk_python.tokens.supply_type import SupplyType | ||
| from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import TokenFeeScheduleUpdateTransaction | ||
| from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee | ||
| from hiero_sdk_python.response_code import ResponseCode | ||
| from hiero_sdk_python.query.token_info_query import TokenInfoQuery | ||
|
|
||
|
|
||
| def setup_client(): | ||
| """Initialize client and operator credentials from .env.""" | ||
| load_dotenv() | ||
| try: | ||
| client = Client(Network(os.getenv("NETWORK", "testnet"))) | ||
| operator_id = AccountId.from_string(os.getenv("OPERATOR_ID")) | ||
| operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY")) | ||
| client.set_operator(operator_id, operator_key) | ||
| print(f" Operator set: {operator_id}\n") | ||
| return client, operator_id, operator_key | ||
| except Exception as e: | ||
| print(f" Error setting up client: {e}") | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| def create_nft(client, operator_id, supply_key, fee_schedule_key): | ||
| """Create an NFT with supply and fee schedule keys.""" | ||
| print(" Creating NFT...") | ||
| token_params = TokenParams( | ||
| token_name="NFT Fee Example", | ||
| token_symbol="NFE", | ||
| treasury_account_id=operator_id, | ||
| initial_supply=0, | ||
| decimals=0, | ||
| token_type=TokenType.NON_FUNGIBLE_UNIQUE, | ||
| supply_type=SupplyType.FINITE, | ||
| max_supply=1000, | ||
| custom_fees=[], | ||
| ) | ||
|
|
||
| # A supply_key is REQUIRED for NFTs (to mint) | ||
| # A fee_schedule_key is required to update fees | ||
| keys = TokenKeys( | ||
| supply_key=supply_key, | ||
| fee_schedule_key=fee_schedule_key | ||
| ) | ||
|
|
||
| tx = TokenCreateTransaction(token_params=token_params, keys=keys) | ||
|
|
||
| # Freeze and execute the transaction (operator auto-signs) | ||
| tx.freeze_with(client) | ||
| receipt = tx.execute(client) | ||
|
|
||
| if receipt.status != ResponseCode.SUCCESS: | ||
| print(f" Token creation failed: {ResponseCode(receipt.status).name}\n") | ||
| client.close() | ||
| sys.exit(1) | ||
|
|
||
| token_id = receipt.token_id | ||
| print(f" Token created successfully: {token_id}\n") | ||
| return token_id | ||
|
|
||
|
|
||
| def update_custom_royalty_fee(client, token_id, fee_schedule_key, collector_account_id): | ||
| """Updates the token's fee schedule with a new royalty fee.""" | ||
| print(f" Updating custom royalty fee for token {token_id}...") | ||
| new_fees = [ | ||
| CustomRoyaltyFee( | ||
| numerator=5, | ||
| denominator=100, # 5% royalty | ||
| fee_collector_account_id=collector_account_id | ||
| ) | ||
| ] | ||
| print(f" Defined {len(new_fees)} new custom fees.\n") | ||
| tx = ( | ||
| TokenFeeScheduleUpdateTransaction() | ||
| .set_token_id(token_id) | ||
| .set_custom_fees(new_fees) | ||
| ) | ||
|
|
||
| tx.freeze_with(client).sign(fee_schedule_key) | ||
|
|
||
| try: | ||
| receipt = tx.execute(client) | ||
| if receipt.status != ResponseCode.SUCCESS: | ||
| print(f" Fee schedule update failed: {ResponseCode(receipt.status).name}\n") | ||
| else: | ||
| print(" Fee schedule updated successfully.\n") | ||
| except Exception as e: | ||
| print(f" Error during fee schedule update execution: {e}\n") | ||
|
|
||
Akshat8510 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def query_token_info(client, token_id): | ||
| """Query token info and verify updated custom fees.""" | ||
| print(f"\nQuerying token info for {token_id}...\n") | ||
| try: | ||
| token_info = TokenInfoQuery(token_id=token_id).execute(client) | ||
| print("Token Info Retrieved Successfully!\n") | ||
|
|
||
| print(f"Name: {getattr(token_info, 'name', 'N/A')}") | ||
| print(f"Symbol: {getattr(token_info, 'symbol', 'N/A')}") | ||
| print(f"Total Supply: {getattr(token_info, 'total_supply', 'N/A')}") | ||
| print(f"Treasury: {getattr(token_info, 'treasury_account_id', 'N/A')}") | ||
| print(f"Decimals: {getattr(token_info, 'decimals', 'N/A')}") | ||
| print(f"Max Supply: {getattr(token_info, 'max_supply', 'N/A')}") | ||
| print() | ||
|
|
||
| custom_fees = getattr(token_info, "custom_fees", []) | ||
| if custom_fees: | ||
| print(f"Found {len(custom_fees)} custom fee(s):") | ||
| for i, fee in enumerate(custom_fees, 1): | ||
| print(f" Fee #{i}: {type(fee).__name__}") | ||
| print(f" Collector: {getattr(fee, 'fee_collector_account_id', 'N/A')}") | ||
| if isinstance(fee, CustomRoyaltyFee): | ||
| print(f" Royalty: {fee.numerator}/{fee.denominator}") | ||
| else: | ||
| print(f" Amount: {getattr(fee, 'amount', 'N/A')}") | ||
| else: | ||
| print("No custom fees defined for this token.\n") | ||
|
|
||
| except Exception as e: | ||
| print(f"Error querying token info: {e}") | ||
|
|
||
Akshat8510 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def main(): | ||
| client, operator_id, operator_key = setup_client() | ||
| token_id = None | ||
| try: | ||
| # Use operator key as both supply and fee key | ||
| supply_key = operator_key | ||
| fee_key = operator_key | ||
|
|
||
| token_id = create_nft(client, operator_id, supply_key, fee_key) | ||
|
|
||
| if token_id: | ||
| query_token_info(client, token_id) | ||
| update_custom_royalty_fee(client, token_id, fee_key, operator_id) | ||
| query_token_info(client, token_id) | ||
|
|
||
| except Exception as e: | ||
| print(f" Error during token operations: {e}") | ||
| finally: | ||
| client.close() | ||
| print("\n Client closed. Example complete.") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
Akshat8510 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| main() | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.