|
| 1 | +""" |
| 2 | +This example creates a fungible token with on-ledger metadata using Hiero SDK Python. |
| 3 | +
|
| 4 | +It demonstrates: |
| 5 | +1. Creating a token with on-ledger metadata. |
| 6 | +2. Attempting to update metadata WITHOUT a metadata_key: expected to fail. |
| 7 | +3. Attempting to update metadata WITH a metadata_key: expected to succed. |
| 8 | +4. Demonstrates that metadata longer than 100 bytes is rejected. |
| 9 | +
|
| 10 | +Required environment variables: |
| 11 | +- OPERATOR_ID, OPERATOR_KEY |
| 12 | +
|
| 13 | +Usage: |
| 14 | +uv run examples/token_create_transaction_token_metadata.py |
| 15 | +python examples/token_create_transaction_token_metadata.py |
| 16 | +""" |
| 17 | + |
| 18 | +import os |
| 19 | +import sys |
| 20 | +from dotenv import load_dotenv |
| 21 | +from hiero_sdk_python import ( |
| 22 | + Client, |
| 23 | + AccountId, |
| 24 | + PrivateKey, |
| 25 | + TokenCreateTransaction, |
| 26 | + TokenUpdateTransaction, |
| 27 | + Network, |
| 28 | + TokenType, |
| 29 | + SupplyType, |
| 30 | +) |
| 31 | +from hiero_sdk_python.query.token_info_query import TokenInfoQuery |
| 32 | +from hiero_sdk_python.response_code import ResponseCode |
| 33 | + |
| 34 | +load_dotenv() |
| 35 | +network_name = os.getenv("NETWORK", "testnet").lower() |
| 36 | + |
| 37 | + |
| 38 | +def setup_client(): |
| 39 | + """Initialize and set up the client with operator account""" |
| 40 | + network = Network(network_name) |
| 41 | + print(f"Connecting to Hedera {network_name} network!") |
| 42 | + client = Client(network) |
| 43 | + |
| 44 | + try: |
| 45 | + operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", "")) |
| 46 | + operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", "")) |
| 47 | + client.set_operator(operator_id, operator_key) |
| 48 | + print(f"Client set up with operator id {client.operator_account_id}") |
| 49 | + return client, operator_id, operator_key |
| 50 | + |
| 51 | + except (TypeError, ValueError): |
| 52 | + print("Error: Please check OPERATOR_ID and OPERATOR_KEY in your .env file.") |
| 53 | + sys.exit(1) |
| 54 | + |
| 55 | + |
| 56 | +def generate_metadata_key(): |
| 57 | + """Generate a new metadata key for the token.""" |
| 58 | + print("\nGenerating a new metadata key for the token...") |
| 59 | + metadata_key = PrivateKey.generate_ed25519() |
| 60 | + print("✅ Metadata key generated successfully.") |
| 61 | + return metadata_key |
| 62 | + |
| 63 | + |
| 64 | +def create_token_without_metadata_key(client, operator_key, operator_id): |
| 65 | + """ |
| 66 | + Creating token with on-ledger metadata WITHOUT metadata_key (max 100 bytes) |
| 67 | + """ |
| 68 | + print("\nCreating token WITHOUT metadata_key") |
| 69 | + |
| 70 | + metadata = b"Initial on-ledger metadata" # < 100 bytes |
| 71 | + |
| 72 | + try: |
| 73 | + transaction = ( |
| 74 | + TokenCreateTransaction() |
| 75 | + .set_token_name("MetadataToken_NoKey") |
| 76 | + .set_token_symbol("MDN") |
| 77 | + .set_treasury_account_id(operator_id) |
| 78 | + .set_token_type(TokenType.FUNGIBLE_COMMON) |
| 79 | + .set_supply_type(SupplyType.INFINITE) |
| 80 | + .set_initial_supply(10) |
| 81 | + .set_metadata(metadata) |
| 82 | + .freeze_with(client) |
| 83 | + ) |
| 84 | + |
| 85 | + # Sign + execute |
| 86 | + transaction.sign(operator_key) |
| 87 | + receipt = transaction.execute(client) |
| 88 | + except Exception as e: |
| 89 | + print(f"❌ Error while building create transaction: {e}") |
| 90 | + sys.exit(1) |
| 91 | + |
| 92 | + token_id = receipt.token_id |
| 93 | + print(f"✅ Token {token_id} successfully created without metadata_key") |
| 94 | + return token_id |
| 95 | + |
| 96 | + |
| 97 | +def try_update_metadata_without_key(client, operator_key, token_id): |
| 98 | + print(f"\nAttempting token {token_id} metadata update WITHOUT metadata_key...") |
| 99 | + updated_metadata = b"updated metadata (without metadata_key)" |
| 100 | + try: |
| 101 | + update_transaction = ( |
| 102 | + TokenUpdateTransaction() |
| 103 | + .set_token_id(token_id) |
| 104 | + .set_metadata(updated_metadata) |
| 105 | + .freeze_with(client) |
| 106 | + ) |
| 107 | + update_transaction.sign(operator_key) |
| 108 | + receipt = update_transaction.execute(client) |
| 109 | + status = ResponseCode(receipt.status).name |
| 110 | + |
| 111 | + if receipt.status == ResponseCode.SUCCESS: |
| 112 | + print( |
| 113 | + f"❌ Unexpected SUCCESS. Status: {receipt.status}" |
| 114 | + "(this should normally fail when metadata_key is missing)" |
| 115 | + ) |
| 116 | + sys.exit(1) |
| 117 | + else: |
| 118 | + print(f"✅ Expected failure: metadata update rejected -> status={status}") |
| 119 | + |
| 120 | + except Exception as e: |
| 121 | + print(f"Failed: {e}") |
| 122 | + |
| 123 | + |
| 124 | +def create_token_with_metadata_key(client, metadata_key, operator_id, operator_key): |
| 125 | + """ |
| 126 | + Create token with metadata_key and on-ledger metadata (max 100 bytes) |
| 127 | + """ |
| 128 | + metadata = b"Example on-ledger token metadata" |
| 129 | + |
| 130 | + print("\nCreating token with metadata and metadata_key...") |
| 131 | + try: |
| 132 | + transaction = ( |
| 133 | + TokenCreateTransaction() |
| 134 | + .set_token_name("Metadata Fungible Token") |
| 135 | + .set_token_symbol("MFT") |
| 136 | + .set_decimals(2) |
| 137 | + .set_initial_supply(1000) |
| 138 | + .set_treasury_account_id(operator_id) |
| 139 | + .set_token_type(TokenType.FUNGIBLE_COMMON) |
| 140 | + .set_supply_type(SupplyType.INFINITE) |
| 141 | + .set_metadata_key(metadata_key) |
| 142 | + .set_metadata(metadata) |
| 143 | + .freeze_with(client) |
| 144 | + ) |
| 145 | + |
| 146 | + transaction.sign(operator_key) |
| 147 | + transaction.sign(metadata_key) |
| 148 | + receipt = transaction.execute(client) |
| 149 | + except Exception as e: |
| 150 | + print(f"❌ Error while creating transaction: {e}") |
| 151 | + sys.exit(1) |
| 152 | + |
| 153 | + if receipt.status != ResponseCode.SUCCESS: |
| 154 | + print( |
| 155 | + f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}" |
| 156 | + ) |
| 157 | + sys.exit(1) |
| 158 | + |
| 159 | + token_id = receipt.token_id |
| 160 | + print(f"✅ Token {token_id} created with metadat_key: {metadata_key.public_key()}") |
| 161 | + print(f"Metadata: {metadata!r}") |
| 162 | + return token_id, metadata_key |
| 163 | + |
| 164 | + |
| 165 | +def update_metadata_with_key(client, token_id, metadata_key, operator_key): |
| 166 | + """ |
| 167 | + Update token metadata with metadata_key |
| 168 | + """ |
| 169 | + print(f"\nUpdating token {token_id} metadata WITH metadata_key...") |
| 170 | + updated_metadata = b"Updated metadata (with key)" |
| 171 | + |
| 172 | + try: |
| 173 | + update_transaction = ( |
| 174 | + TokenUpdateTransaction() |
| 175 | + .set_token_id(token_id) |
| 176 | + .set_metadata(updated_metadata) |
| 177 | + .freeze_with(client) |
| 178 | + .sign(metadata_key) |
| 179 | + ) |
| 180 | + receipt = update_transaction.execute(client) |
| 181 | + if receipt.status != ResponseCode.SUCCESS: |
| 182 | + print( |
| 183 | + f"❌ Token update failed with status: {ResponseCode(receipt.status).name}" |
| 184 | + ) |
| 185 | + sys.exit(1) |
| 186 | + except Exception as e: |
| 187 | + print(f"Error while freezing update transaction: {e}") |
| 188 | + sys.exit(1) |
| 189 | + |
| 190 | + print(f"✅ Token {token_id} metadata successfully updated") |
| 191 | + print(f"Updated metadata: {updated_metadata}") |
| 192 | + |
| 193 | + |
| 194 | +def demonstrate_metadata_length_validation(client, operator_key, operator_id): |
| 195 | + """ |
| 196 | + Demonstrate that metadata longer than 100 bytes trigger a ValueError |
| 197 | + in the TokenCreateTransaction.set_metadata() validation. |
| 198 | + """ |
| 199 | + print("\nDemonstrating metadata length validation (> 100 bytes)...") |
| 200 | + too_long_metadata = b"x" * 101 |
| 201 | + |
| 202 | + try: |
| 203 | + transaction = ( |
| 204 | + TokenCreateTransaction() |
| 205 | + .set_token_name("TooLongMetadataToken") |
| 206 | + .set_token_symbol("TLM") |
| 207 | + .set_treasury_account_id(operator_id) |
| 208 | + .set_metadata(too_long_metadata) |
| 209 | + ) |
| 210 | + |
| 211 | + transaction.sign(operator_key) |
| 212 | + receipt = transaction.execute(client) |
| 213 | + if receipt.status == ResponseCode.SUCCESS: |
| 214 | + print(f"❌ Unexpected success for this operation!") |
| 215 | + else: |
| 216 | + print( |
| 217 | + "Error: Expected ValueError for metadata > 100 bytes, but none was raised." |
| 218 | + ) |
| 219 | + |
| 220 | + sys.exit(1) |
| 221 | + except ValueError as exc: |
| 222 | + print("Expected error raised for metadata > 100 bytes") |
| 223 | + print(f"✅ Error raised: {exc}") |
| 224 | + |
| 225 | + |
| 226 | +def create_token_with_metadata(): |
| 227 | + """ |
| 228 | + Main function to create and update fungible token with metadata with two scenarios: |
| 229 | + - create token WITHOUT metadata_key (expected to fail) |
| 230 | + - create token WITH metadat_key (expected to succed) |
| 231 | + and validate metadata length |
| 232 | + """ |
| 233 | + client, operator_id, operator_key = setup_client() |
| 234 | + metadata_key = generate_metadata_key() |
| 235 | + |
| 236 | + token_a = create_token_without_metadata_key(client, operator_key, operator_id) |
| 237 | + try_update_metadata_without_key(client, operator_key, token_a) |
| 238 | + |
| 239 | + token_b, metadata_key = create_token_with_metadata_key( |
| 240 | + client, metadata_key, operator_id, operator_key |
| 241 | + ) |
| 242 | + update_metadata_with_key(client, token_b, metadata_key, operator_key) |
| 243 | + |
| 244 | + demonstrate_metadata_length_validation(client, operator_key, operator_id) |
| 245 | + |
| 246 | + |
| 247 | +if __name__ == "__main__": |
| 248 | + create_token_with_metadata() |
0 commit comments