Skip to content

Commit 39954c2

Browse files
authored
feat: add TokenUpdateTransaction (#121)
* feat: add metadata field in TokenInfo Signed-off-by: dosi <dosi.kolev@limechain.tech> * test: add metadata testing in TokenInfo unit tests Signed-off-by: dosi <dosi.kolev@limechain.tech> * feat: add TokenKeyValidation class Signed-off-by: dosi <dosi.kolev@limechain.tech> * feat: add TokenUpdateTransaction Signed-off-by: dosi <dosi.kolev@limechain.tech> * test: add integration tests Signed-off-by: dosi <dosi.kolev@limechain.tech> * test: add unit tests Signed-off-by: dosi <dosi.kolev@limechain.tech> * docs: add examples for TokenUpdateTransaction Signed-off-by: dosi <dosi.kolev@limechain.tech> * docs: update examples README Signed-off-by: dosi <dosi.kolev@limechain.tech> * chore: add TokenUpdateTransaction to __init__.py Signed-off-by: dosi <dosi.kolev@limechain.tech> * style: fix type annotation spacing to follow PEP 8 Signed-off-by: dosi <dosi.kolev@limechain.tech> * fix: remove Codacy warning for examples Signed-off-by: dosi <dosi.kolev@limechain.tech> * refactor: improve key conversion logic in TokenUpdateTransaction Signed-off-by: dosi <dosi.kolev@limechain.tech> * docs: address PR comments and update token update examples Signed-off-by: dosi <dosi.kolev@limechain.tech> * feat: set default transaction fee (2 HBAR) for token update transactions and remove manual fee setting from tests/examples Signed-off-by: dosi <dosi.kolev@limechain.tech> * refactor: rename proto conversion methods to internal in TokenKeyValidation Signed-off-by: dosi <dosi.kolev@limechain.tech> * refactor: remove type hints and add _set_keys_to_proto() in TokenUpdateTransaction Signed-off-by: dosi <dosi.kolev@limechain.tech> * test: update token update transaction tests in response to PR review Signed-off-by: dosi <dosi.kolev@limechain.tech> * test: add pause key update verification in token update integration test Signed-off-by: dosi <dosi.kolev@limechain.tech> * refactor: rename proto conversion methods to be private Signed-off-by: dosi <dosi.kolev@limechain.tech> * refactor: make TokenInfo dataclass Signed-off-by: dosi <dosi.kolev@limechain.tech> --------- Signed-off-by: dosi <dosi.kolev@limechain.tech>
1 parent f534a78 commit 39954c2

File tree

11 files changed

+1705
-34
lines changed

11 files changed

+1705
-34
lines changed

examples/README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ You can choose either syntax or even mix both styles in your projects.
3232
- [Token Update NFTs](#token-update-nfts)
3333
- [Pausing a Token](#pausing-a-token)
3434
- [Token Grant KYC](#token-grant-kyc)
35+
- [Updating a Token](#updating-a-token)
3536
- [Querying NFT Info](#querying-nft-info)
3637
- [Querying Fungible Token Info](#querying-fungible-token-info)
3738
- [HBAR Transactions](#hbar-transactions)
@@ -568,6 +569,7 @@ transaction = TokenGrantKycTransaction(
568569
569570
transaction.sign(kyc_key) # KYC key is required for granting KYC approval
570571
transaction.execute(client)
572+
571573
```
572574
#### Method Chaining:
573575
```
@@ -578,8 +580,55 @@ transaction.execute(client)
578580
.freeze_with(client)
579581
.sign(kyc_key) # KYC key is required for granting KYC approval
580582
)
581-
582583
transaction.execute(client)
584+
585+
```
586+
587+
### Updating a Token
588+
589+
#### Pythonic Syntax:
590+
```
591+
transaction = TokenUpdateTransaction(
592+
token_id=token_id,
593+
token_params=TokenUpdateParams(
594+
token_name="UpdateToken",
595+
token_symbol="UPD",
596+
token_memo="Updated memo",
597+
metadata="Updated metadata",
598+
treasury_account_id=new_account_id
599+
),
600+
token_keys=TokenUpdateKeys(
601+
admin_key=new_admin_key,
602+
freeze_key=new_freeze_key, # freeze_key can sign a transaction that changes only the Freeze Key
603+
metadata_key=new_metadata_key, # metadata_key can sign a transaction that changes only the metadata
604+
supply_key=new_supply_key # supply_key can sign a transaction that changes only the Supply Key
605+
),
606+
token_key_verification_mode=TokenKeyValidation.FULL_VALIDATION # Default value. Also, it can be NO_VALIDATION
607+
).freeze_with(client)
608+
transaction.sign(new_account_id_private_key) # If a new treasury account is set, the new treasury key is required to sign.
609+
transaction.sign(new_admin_key) # Updating the admin key requires the new admin key to sign.
610+
transaction.execute(client)
611+
```
612+
613+
#### Method Chaining:
614+
```
615+
transaction = (
616+
TokenCreateTransaction() # no params => uses default placeholders which are next overwritten.
617+
.set_token_name("UpdateToken")
618+
.set_token_symbol("UPD")
619+
.set_token_memo("Updated memo")
620+
.set_metadata("Updated metadata)
621+
.set_treasury_account_id(new_account_id)
622+
.set_admin_key(new_admin_key)
623+
.set_supply_key(new_supply_key)
624+
.set_freeze_key(new_freeze_key)
625+
.set_metadata_key(new_metadata_key)
626+
.freeze_with(client)
627+
)
628+
629+
transaction.sign(new_account_id_private_key) # If a new treasury account is set, the new treasury key is required to sign.
630+
transaction.sign(new_admin_key) # Updating the admin key requires the new admin key to sign.
631+
transaction.execute(client)
583632
```
584633

585634
### Querying NFT Info

examples/token_update_fungible.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import os
2+
import sys
3+
from dotenv import load_dotenv
4+
5+
from hiero_sdk_python import (
6+
Client,
7+
AccountId,
8+
PrivateKey,
9+
Network,
10+
)
11+
from hiero_sdk_python.hapi.services.basic_types_pb2 import TokenType
12+
from hiero_sdk_python.query.token_info_query import TokenInfoQuery
13+
from hiero_sdk_python.response_code import ResponseCode
14+
from hiero_sdk_python.tokens.supply_type import SupplyType
15+
from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction
16+
from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateTransaction
17+
18+
load_dotenv()
19+
20+
def setup_client():
21+
"""Initialize and set up the client with operator account"""
22+
network = Network(network='testnet')
23+
client = Client(network)
24+
25+
operator_id = AccountId.from_string(os.getenv('OPERATOR_ID'))
26+
operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY'))
27+
client.set_operator(operator_id, operator_key)
28+
29+
return client, operator_id, operator_key
30+
31+
def create_fungible_token(client, operator_id, operator_key, metadata_key):
32+
"""
33+
Create a fungible token
34+
35+
If we want to update metadata later using TokenUpdateTransaction:
36+
1. Set a metadata_key and sign the update transaction with it, or
37+
2. Sign the update transaction with the admin_key
38+
39+
Note: If no Admin Key was assigned during token creation (immutable token),
40+
token updates will fail with TOKEN_IS_IMMUTABLE.
41+
"""
42+
receipt = (
43+
TokenCreateTransaction()
44+
.set_token_name("MyExampleFT")
45+
.set_token_symbol("EXFT")
46+
.set_decimals(2)
47+
.set_initial_supply(100)
48+
.set_treasury_account_id(operator_id)
49+
.set_token_type(TokenType.FUNGIBLE_COMMON)
50+
.set_supply_type(SupplyType.FINITE)
51+
.set_max_supply(1000)
52+
.set_admin_key(operator_key)
53+
.set_supply_key(operator_key)
54+
.set_freeze_key(operator_key)
55+
.set_metadata_key(metadata_key)
56+
.execute(client)
57+
)
58+
59+
# Check if token creation was successful
60+
if receipt.status != ResponseCode.SUCCESS:
61+
print(f"Fungible token creation failed with status: {ResponseCode.get_name(receipt.status)}")
62+
sys.exit(1)
63+
64+
# Get token ID from receipt
65+
token_id = receipt.tokenId
66+
print(f"Fungible token created with ID: {token_id}")
67+
68+
return token_id
69+
70+
def get_token_info(client, token_id):
71+
"""Get information about a fungible token"""
72+
info = (
73+
TokenInfoQuery()
74+
.set_token_id(token_id)
75+
.execute(client)
76+
)
77+
78+
return info
79+
80+
def update_token_data(client, token_id, update_metadata, update_token_name, update_token_symbol, update_token_memo):
81+
"""Update metadata for a fungible token"""
82+
receipt = (
83+
TokenUpdateTransaction()
84+
.set_token_id(token_id)
85+
.set_metadata(update_metadata)
86+
.set_token_name(update_token_name)
87+
.set_token_symbol(update_token_symbol)
88+
.set_token_memo(update_token_memo)
89+
.execute(client)
90+
)
91+
92+
if receipt.status != ResponseCode.SUCCESS:
93+
print(f"Token metadata update failed with status: {ResponseCode.get_name(receipt.status)}")
94+
sys.exit(1)
95+
96+
print(f"Successfully updated token data")
97+
98+
def token_update_fungible():
99+
"""
100+
Demonstrates the fungible token update functionality by:
101+
1. Setting up client with operator account
102+
2. Creating a fungible token with metadata key
103+
3. Checking the current token info
104+
4. Updating the token's metadata, name, symbol and memo
105+
5. Verifying the updated token info
106+
"""
107+
client, operator_id, operator_key = setup_client()
108+
109+
# Create metadata key
110+
metadata_private_key = PrivateKey.generate_ed25519()
111+
112+
token_id = create_fungible_token(client, operator_id, operator_key, metadata_private_key)
113+
114+
print("\nToken info before update:")
115+
token_info = get_token_info(client, token_id)
116+
print(token_info)
117+
118+
# New data to update the fungible token
119+
update_metadata = b"Updated metadata"
120+
update_token_name = "Updated Token"
121+
update_token_symbol = "UPD"
122+
update_token_memo = "Updated memo"
123+
124+
update_token_data(client, token_id, update_metadata, update_token_name, update_token_symbol, update_token_memo)
125+
126+
print("\nToken info after update:")
127+
token_info = get_token_info(client, token_id)
128+
print(token_info)
129+
130+
if __name__ == "__main__":
131+
token_update_fungible()

examples/token_update_key.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
2+
import os
3+
import sys
4+
from dotenv import load_dotenv
5+
6+
from hiero_sdk_python import (
7+
Client,
8+
AccountId,
9+
PrivateKey,
10+
Network,
11+
)
12+
from hiero_sdk_python.hapi.services.basic_types_pb2 import TokenType
13+
from hiero_sdk_python.query.token_info_query import TokenInfoQuery
14+
from hiero_sdk_python.response_code import ResponseCode
15+
from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation
16+
from hiero_sdk_python.tokens.supply_type import SupplyType
17+
from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction
18+
from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateTransaction
19+
20+
load_dotenv()
21+
22+
def setup_client():
23+
"""Initialize and set up the client with operator account"""
24+
network = Network(network='testnet')
25+
client = Client(network)
26+
27+
operator_id = AccountId.from_string(os.getenv('OPERATOR_ID'))
28+
operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY'))
29+
client.set_operator(operator_id, operator_key)
30+
31+
return client, operator_id
32+
33+
def create_fungible_token(client, operator_id, admin_key, wipe_key):
34+
"""Create a fungible token"""
35+
receipt = (
36+
TokenCreateTransaction()
37+
.set_token_name("MyExampleFT")
38+
.set_token_symbol("EXFT")
39+
.set_decimals(2)
40+
.set_initial_supply(100)
41+
.set_treasury_account_id(operator_id)
42+
.set_token_type(TokenType.FUNGIBLE_COMMON)
43+
.set_supply_type(SupplyType.FINITE)
44+
.set_max_supply(1000)
45+
.set_admin_key(admin_key)
46+
.set_wipe_key(wipe_key)
47+
.freeze_with(client)
48+
.sign(admin_key)
49+
.execute(client)
50+
)
51+
52+
# Check if token creation was successful
53+
if receipt.status != ResponseCode.SUCCESS:
54+
print(f"Fungible token creation failed with status: {ResponseCode.get_name(receipt.status)}")
55+
sys.exit(1)
56+
57+
# Get token ID from receipt
58+
token_id = receipt.tokenId
59+
print(f"Fungible token created with ID: {token_id}")
60+
61+
return token_id
62+
63+
def get_token_info(client, token_id):
64+
"""Get information about a fungible token"""
65+
info = (
66+
TokenInfoQuery()
67+
.set_token_id(token_id)
68+
.execute(client)
69+
)
70+
71+
return info
72+
73+
def update_wipe_key_full_validation(client, token_id, old_wipe_key):
74+
"""
75+
Update token wipe key with full validation mode.
76+
77+
This demonstrates using FULL_VALIDATION mode (default) which requires both old and new key signatures.
78+
This ensures that there cannot be an accidental update to a public key for which the user does not possess
79+
the private key.
80+
Although only private keys can be set currently, the validation provides additional safety by requiring
81+
signatures from both keys.
82+
"""
83+
# Generate new wipe key
84+
new_wipe_key = PrivateKey.generate_ed25519()
85+
86+
receipt = (
87+
TokenUpdateTransaction()
88+
.set_token_id(token_id)
89+
.set_wipe_key(new_wipe_key)
90+
.set_key_verification_mode(TokenKeyValidation.FULL_VALIDATION)
91+
.freeze_with(client)
92+
.sign(new_wipe_key)
93+
.sign(old_wipe_key)
94+
.execute(client)
95+
)
96+
97+
if receipt.status != ResponseCode.SUCCESS:
98+
print(f"Token update failed with status: {ResponseCode.get_name(receipt.status)}")
99+
sys.exit(1)
100+
101+
print(f"Successfully updated wipe key")
102+
103+
# Query token info to verify wipe key update
104+
info = get_token_info(client, token_id)
105+
print(f"Token's wipe key after update: {info.wipeKey}")
106+
107+
def token_update_key():
108+
"""
109+
Demonstrates updating keys on a fungible token by:
110+
1. Setting up client with operator account
111+
2. Creating a fungible token with admin and wipe keys
112+
3. Checking the current token info and key values
113+
4. Updating the wipe key with full validation
114+
"""
115+
client, operator_id = setup_client()
116+
117+
admin_key = PrivateKey.generate_ed25519()
118+
wipe_key = PrivateKey.generate_ed25519()
119+
120+
token_id = create_fungible_token(client, operator_id, admin_key, wipe_key)
121+
122+
print("\nToken info before update:")
123+
token_info = get_token_info(client, token_id)
124+
125+
print(f"Token's wipe key after creation: {token_info.wipeKey}")
126+
print(f"Token's admin key after creation: {token_info.adminKey}")
127+
128+
update_wipe_key_full_validation(client, token_id, wipe_key)
129+
130+
if __name__ == "__main__":
131+
token_update_key()

0 commit comments

Comments
 (0)