Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
597909f
chore: refactor token deletion process
MonaaEid Sep 12, 2025
9e8f045
chore: refactor token_dissociate example for clarity
MonaaEid Sep 12, 2025
50e20a9
chore: refactor token freeze example functions
MonaaEid Sep 12, 2025
c410a17
chore: refactor token minting functions for clarity
MonaaEid Sep 12, 2025
fe0cacc
chore: refactor NFT minting script for clarity and structure
MonaaEid Sep 12, 2025
3828ac2
chore: refactor token unfreeze example
MonaaEid Sep 12, 2025
4902c80
chore: refactor topic_create example
MonaaEid Sep 12, 2025
fde86c7
chore: update changelog with token example refactor (#370)
MonaaEid Sep 13, 2025
0735c7a
Merge branch 'main' into chore/refactor-token-examples
MonaaEid Sep 13, 2025
27fca40
chore: fix docstring formatting and cleanup code
MonaaEid Sep 13, 2025
5d1cb90
chore: refactor token handling for NFT and fungible tokens
MonaaEid Sep 13, 2025
ee81a3c
chore: refactor token freeze to demonstrate freezing and improve docu…
MonaaEid Sep 13, 2025
e1af1c6
chore: refactor supply key generation and improve comments
MonaaEid Sep 13, 2025
b0ef1e0
chore: refactor NFT minting and improve documentation
MonaaEid Sep 13, 2025
b8447fc
chore: refactor freeze key generation and improve documentation
MonaaEid Sep 13, 2025
b223320
chore: fix operator key retrieval from environment variable
MonaaEid Sep 13, 2025
1f355e9
chore: update token_delete.py
MonaaEid Sep 15, 2025
ab3563f
chore: add verifying steps and update the documentation
MonaaEid Sep 15, 2025
31c1ef5
chore: enhance error handling for token transfer verification.
MonaaEid Sep 15, 2025
c094f2b
chore: simplify status name retrieval for token freeze
MonaaEid Sep 15, 2025
771d61f
chore: enhance token minting with supply key verification
MonaaEid Sep 15, 2025
8f337f6
chore: enhance documentation and confirm total supply before and afte…
MonaaEid Sep 15, 2025
b677a75
chore: add a test to verify unfreeze
MonaaEid Sep 15, 2025
6820caa
chore: refactor setup_client and update create_topic docstring
MonaaEid Sep 15, 2025
2c10bfd
chore: refactor token freeze/unfreeze functions
MonaaEid Oct 1, 2025
febad94
chore: refactor token freeze functions and improve error handling
MonaaEid Oct 1, 2025
d01e1cd
chore: refactor token_dissociate.py for better clarity
MonaaEid Oct 1, 2025
270517b
chore: refactor token management functions and main flow
MonaaEid Oct 1, 2025
1877fad
chore: refactor token minting function and improve logging
MonaaEid Oct 1, 2025
566d4e3
chore: refactor NFT minting function and improve error handling
MonaaEid Oct 1, 2025
37c7b5b
chore: add verification function for token dissociation
MonaaEid Oct 24, 2025
1720a87
chore: refactor verification for token dissociation
MonaaEid Oct 24, 2025
5e4aa16
chore: refactor token freeze functionality and verification
MonaaEid Oct 24, 2025
4a52b5c
chore: revise CHANGELOG.md for version updates and details
MonaaEid Oct 24, 2025
f7b5355
Merge branch 'main' into chore/refactor-token-examples
MonaaEid Oct 24, 2025
fc17745
chore: enhance client setup
MonaaEid Oct 27, 2025
7bf3f25
chore: update supply key generation to use environment variable
MonaaEid Oct 27, 2025
d1dc519
chore: refactor token minting functions for clarity
MonaaEid Oct 27, 2025
6de8df4
chore: update changelog with token example refactor (#370)
MonaaEid Oct 27, 2025
8685cd0
Merge branch 'main' into chore/refactor-token-examples
MonaaEid Oct 27, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Update protobuf dependency from 5.28.1 to 5.29.1
- Update grpcio dependency from 1.68.1 to 1.71.2
- Updated `rebasing.md` with clarification on using `git reset --soft HEAD~<n>` where `<n>` specifies the number of commits to rewind.
- Refactored token-related example scripts (`token_delete.py`, `token_dissociate.py`, etc.) for improved readability and modularity. [#370]
- Calls in examples for PrivateKey.from_string_ed25519(os.getenv('OPERATOR_KEY')) to PrivateKey.from_string(os.getenv('OPERATOR_KEY')) to enable general key types

### Fixed
Expand Down
66 changes: 43 additions & 23 deletions examples/token_delete.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
uv run examples/token_delete.py
python examples/token_delete.py
# uv run examples/token_delete.py
# python examples/token_delete.py

"""
A full example that creates a token and then immediately deletes it.
"""

import os
import sys
from dotenv import load_dotenv
Expand All @@ -19,37 +21,38 @@
# Load environment variables from .env file
load_dotenv()


def create_and_delete_token():
"""
A full example that creates a token and then immediately deletes it.
"""
# 1. Setup Client
# =================================================================
def setup_client():
"""Setup Client """
print("Connecting to Hedera testnet...")
client = Client(Network(network='testnet'))

# Get the operator account from the .env file
try:
operator_id = AccountId.from_string(os.getenv('OPERATOR_ID'))
# NOTE: Assumes your operator key is a raw Ed25519 key
operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY'))

except (TypeError, ValueError):
print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
sys.exit(1)

# Set the operator (payer) account for the client
client.set_operator(operator_id, operator_key)
print(f"Using operator account: {operator_id}")
return client, operator_id, operator_key

def generate_admin_key():
"""Generate a new admin key within the script:
This key will be used to create the token with admin privileges
"""

# 2. Generate a new admin key within the script
# =================================================================
print("\nGenerating a new admin key for the token...")
admin_key = PrivateKey.generate_ed25519()
print("Admin key generated successfully.")
return admin_key

def create_new_token(client, operator_id, operator_key, admin_key):
""" Create the Token"""
token_id_to_delete = None

# 3. Create the Token
# =================================================================
try:
print("\nSTEP 1: Creating a new token...")
create_tx = (
Expand All @@ -67,14 +70,18 @@ def create_and_delete_token():
create_receipt = create_tx.execute(client)
token_id_to_delete = create_receipt.token_id
print(f"✅ Success! Created token with ID: {token_id_to_delete}")
return token_id_to_delete

except Exception as e:
except (ValueError, TypeError) as e:
print(f"❌ Error creating token: {e}")
sys.exit(1)


# 4. Delete the Token
# =================================================================
def delete_token(admin_key, token_id_to_delete, client, operator_key):
"""
Delete the Token we just created
"""

try:
print(f"\nSTEP 2: Deleting token {token_id_to_delete}...")
delete_tx = (
Expand All @@ -85,13 +92,26 @@ def create_and_delete_token():
.sign(admin_key) # Sign with the same admin key used to create it
)

delete_receipt = delete_tx.execute(client)
print(f"✅ Success! Token deleted.")
delete_tx.execute(client)
print("✅ Success! Token deleted.")

except Exception as e:
except (ValueError, TypeError) as e:
print(f"❌ Error deleting token: {e}")
sys.exit(1)

def main():
"""
1. Call create_new_token() to create a new token and get its admin key, token ID, client, and operator key.
2. Build a TokenDeleteTransaction using the token ID.
3. Freeze the transaction with the client.
4. Sign the transaction with both the operator key and the admin key.
5. Execute the transaction to delete the token.
6. Print the result or handle any errors.
"""
client, operator_id, operator_key = setup_client()
admin_key = generate_admin_key()
token_id_to_delete = create_new_token(client, operator_id, operator_key, admin_key)
delete_token(admin_key, token_id_to_delete, client, operator_key)

if __name__ == "__main__":
create_and_delete_token()
main()
149 changes: 105 additions & 44 deletions examples/token_dissociate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# uv run examples/token_dissociate.py
# python examples/token_dissociate.py
"""
uv run examples/token_dissociate.py
python examples/token_dissociate.py

A full example that creates an account, two tokens, associates them,
and finally dissociates them.
"""
import os
import sys
Expand All @@ -17,36 +18,36 @@
TokenCreateTransaction,
TokenAssociateTransaction,
TokenDissociateTransaction,
TokenType,
AccountInfoQuery,
ResponseCode,
)

# Load environment variables from .env file
load_dotenv()


def token_dissociate():
"""
A full example that creates an account, two tokens, associates them,
and finally dissociates them.
"""
# 1. Setup Client
# =================================================================
def setup_client():
"""Setup Client"""
print("Connecting to Hedera testnet...")
client = Client(Network(network='testnet'))

try:
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)
return client, operator_id, operator_key

except (TypeError, ValueError):
print("❌ Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.")
sys.exit(1)

print(f"Using operator account: {operator_id}")

# 2. Create a new account to associate/dissociate with
# =================================================================
def create_new_account(client, operator_id, operator_key):
"""Create a new account to associate/dissociate with tokens"""
print("\nSTEP 1: Creating a new account...")
recipient_key = PrivateKey.generate("ed25519")
recipient_key = PrivateKey.generate_ed25519()

try:
# Build the transaction
tx = (
Expand All @@ -59,65 +60,125 @@ def token_dissociate():
receipt = tx.freeze_with(client).sign(operator_key).execute(client)
recipient_id = receipt.account_id
print(f"✅ Success! Created new account with ID: {recipient_id}")
except Exception as e:
return client, operator_key, recipient_id, recipient_key, operator_id

except (ValueError, RuntimeError) as e:
print(f"❌ Error creating new account: {e}")
sys.exit(1)

# 3. Create two new tokens
# =================================================================
def create_token(client, operator_key, recipient_id, recipient_key, operator_id):
"""Create two new tokens (one NFT and one fungible) for demonstration purposes. """
print("\nSTEP 2: Creating two new tokens...")
try:
# Create First Token
tx1 = TokenCreateTransaction().set_token_name("First Token").set_token_symbol("TKA").set_initial_supply(1).set_treasury_account_id(operator_id)
receipt1 = tx1.freeze_with(client).sign(operator_key).execute(client)
token_id_1 = receipt1.token_id

# Create Second Token
tx2 = TokenCreateTransaction().set_token_name("Second Token").set_token_symbol("TKB").set_initial_supply(1).set_treasury_account_id(operator_id)
receipt2 = tx2.freeze_with(client).sign(operator_key).execute(client)
token_id_2 = receipt2.token_id

print(f"✅ Success! Created tokens: {token_id_1} and {token_id_2}")
except Exception as e:
# Generate supply key for NFT
supply_key = PrivateKey.generate_ed25519()
# Create NFT Token
nft_tx = (
TokenCreateTransaction()
.set_token_name("NFT Token")
.set_token_symbol("NFTK")
.set_token_type(TokenType.NON_FUNGIBLE_UNIQUE)
.set_initial_supply(0)
.set_treasury_account_id(operator_id)
.set_supply_key(supply_key)
)
nft_receipt = nft_tx.freeze_with(client).sign(operator_key).execute(client)
nft_token_id = nft_receipt.token_id

# Create Fungible Token
fungible_tx = (
TokenCreateTransaction()
.set_token_name("Fungible Token")
.set_token_symbol("FTK")
.set_initial_supply(1)
.set_treasury_account_id(operator_id)
)
fungible_receipt = fungible_tx.freeze_with(client).sign(operator_key).execute(client)
fungible_token_id = fungible_receipt.token_id

print(f"✅ Success! Created NFT token: {nft_token_id} and fungible token: {fungible_token_id}")
return client, nft_token_id, fungible_token_id, recipient_id, recipient_key

except (ValueError, RuntimeError) as e:
print(f"❌ Error creating tokens: {e}")
sys.exit(1)

# 4. Associate the tokens with the new account
# =================================================================
print(f"\nSTEP 3: Associating tokens with account {recipient_id}...")
def token_associate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key):
"""
Associate the tokens with the new account.

Note: Tokens must be associated with an account before they can be used or dissociated.
Association is a prerequisite for holding, transferring, or later dissociating tokens.
"""

print(f"\nSTEP 3: Associating NFT and fungible tokens with account {recipient_id}...")
print("Note: Tokens must be associated with an account before they can be used or dissociated.")
try:
receipt = (
TokenAssociateTransaction()
.set_account_id(recipient_id)
.add_token_id(token_id_1)
.add_token_id(token_id_2)
.add_token_id(nft_token_id)
.add_token_id(fungible_token_id)
.freeze_with(client)
.sign(recipient_key) # Recipient must sign to approve
.execute(client)
)
print(f"✅ Success! Token association complete. Status: {receipt.status}")
except Exception as e:
return client, nft_token_id, fungible_token_id, recipient_id, recipient_key
except (ValueError, RuntimeError) as e:
print(f"❌ Error associating tokens: {e}")
sys.exit(1)

# 5. Dissociate the tokens from the new account
# =================================================================
print(f"\nSTEP 4: Dissociating tokens from account {recipient_id}...")
def token_dissociate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key):
"""
Dissociate the tokens from the new account.

Why dissociate?
- To remove unwanted tokens from your account
- To reduce account costs (some tokens may incur fees)
- For security or privacy reasons
- To comply with business or regulatory requirements
"""

print(f"\nSTEP 4: Dissociating NFT and fungible tokens from account {recipient_id}...")
try:
receipt = (
TokenDissociateTransaction()
.set_account_id(recipient_id)
.add_token_id(token_id_1)
.add_token_id(token_id_2)
.add_token_id(nft_token_id)
.add_token_id(fungible_token_id)
.freeze_with(client)
.sign(recipient_key) # Recipient must sign to approve
.execute(client)
)
print(f"✅ Success! Token dissociation complete.")
except Exception as e:
print(f"✅ Success! Token dissociation complete for both NFT and fungible tokens, Status: {ResponseCode(receipt.status).name}")

# Optional: Verify dissociation
print("\nVerifying token dissociation...")
info = AccountInfoQuery().set_account_id(recipient_id).execute(client)
associated_tokens = [rel.token_id for rel in getattr(info, 'token_relationships', [])]
if nft_token_id not in associated_tokens and fungible_token_id not in associated_tokens:
print("✅ Verified: Both tokens are dissociated from the account.")
else:
print("❌ Verification failed: Some tokens are still associated.")

except (ValueError, RuntimeError) as e:
print(f"❌ Error dissociating tokens: {e}")
sys.exit(1)

def main():
"""
1-create new account
2-create two tokens
3-associate the tokens with the new account
4-dissociate the tokens from the new account
5-verify dissociation
"""
client, operator_id, operator_key = setup_client()
client, operator_key, recipient_id, recipient_key, operator_id =create_new_account(client, operator_id, operator_key)
client, nft_token_id, fungible_token_id, recipient_id, recipient_key = create_token(client, operator_key, recipient_id, recipient_key, operator_id)
token_associate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key)
token_dissociate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key)


if __name__ == "__main__":
token_dissociate()
main()
Loading