Skip to content

Commit b5e6df5

Browse files
authored
feat: Add example for token wipe key usage (#831)
Signed-off-by: Adityarya11 <arya050411@gmail.com>
1 parent b0a7c9d commit b5e6df5

File tree

2 files changed

+352
-1
lines changed

2 files changed

+352
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
2727
- Added `examples/token_create_transaction_supply_key.py` to demonstrate token creation with and without a supply key.
2828
- Added BatchTransaction class
2929
- Add support for token metadata (bytes, max 100 bytes) in `TokenCreateTransaction`, including a new `set_metadata` setter, example, and tests. [#799]
30-
3130
- Added `examples/token_create_transaction_token_fee_schedule.py` to demonstrate creating tokens with custom fee schedules and the consequences of not having it.
31+
- Added `examples/token_create_transaction_wipe_key.py` to demonstrate token wiping and the role of the wipe key.
32+
3233

3334
### Changed
3435
- Upgraded step-security/harden-runner v2.13.2
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
"""
2+
This example demonstrates the Wipe Key privileges for token management using Hiero SDK Python.
3+
4+
It shows:
5+
1. Creating a FUNGIBLE token WITHOUT a wipe key.
6+
2. Attempting to wipe tokens (which fails because no wipe key exists).
7+
3. Creating a FUNGIBLE token WITH a wipe key.
8+
4. Associating and transferring tokens to a user account.
9+
5. Wiping tokens from that user's account using the wipe key.
10+
6. Verifying the total supply has decreased (effectively burning).
11+
7. Creating an NFT (Non-Fungible) token with a wipe key.
12+
8. Wiping a specific NFT Serial Number from a user account.
13+
14+
Required environment variables:
15+
- OPERATOR_ID, OPERATOR_KEY
16+
17+
Usage:
18+
uv run examples/token_create_transaction_wipe_key.py
19+
"""
20+
21+
import os
22+
import sys
23+
from dotenv import load_dotenv
24+
25+
from hiero_sdk_python import (
26+
Client,
27+
AccountId,
28+
PrivateKey,
29+
Network,
30+
TokenCreateTransaction,
31+
TokenWipeTransaction,
32+
TokenAssociateTransaction,
33+
TransferTransaction,
34+
AccountCreateTransaction,
35+
TokenInfoQuery,
36+
TokenMintTransaction,
37+
Hbar,
38+
NftId # <--- FIX 1: Added NftId import
39+
)
40+
41+
from hiero_sdk_python.response_code import ResponseCode
42+
from hiero_sdk_python.tokens.token_type import TokenType
43+
from hiero_sdk_python.tokens.supply_type import SupplyType
44+
45+
load_dotenv()
46+
network_name = os.getenv('NETWORK', 'testnet').lower()
47+
48+
def setup_client():
49+
"""
50+
Initialise and return a Hiero SDK client based on environment variables.
51+
"""
52+
network = Network(network_name)
53+
print(f"Connecting to Hedera {network_name} network")
54+
client = Client(network)
55+
56+
try:
57+
operator_id = AccountId.from_string(os.getenv('OPERATOR_ID', ''))
58+
operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY', ''))
59+
client.set_operator(operator_id, operator_key)
60+
print(f"Client set-up with operator id {client.operator_account_id}.")
61+
return client, operator_id, operator_key
62+
63+
except (TypeError, ValueError):
64+
print("Error: please check OPERATOR_ID and OPERATOR_KEY in you environment file.")
65+
sys.exit(1)
66+
67+
def create_recipient_account(client):
68+
"""
69+
Helper: Create a new account to hold tokens(wiped ones)
70+
"""
71+
private_key = PrivateKey.generate_ed25519()
72+
tx = (
73+
AccountCreateTransaction()
74+
.set_key(private_key.public_key())
75+
.set_initial_balance(Hbar(2))
76+
)
77+
receipt = tx.execute(client)
78+
if receipt.status != ResponseCode.SUCCESS:
79+
print(f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}")
80+
sys.exit(1)
81+
82+
print(f"✅ Account created: {receipt.account_id}")
83+
return receipt.account_id, private_key
84+
85+
def associate_and_transfer(client, token_id, recipient_id, recipient_key, amount):
86+
"""Helper: Associate token to recipient and transfer tokens to them"""
87+
88+
associate_tx = (
89+
TokenAssociateTransaction()
90+
.set_account_id(recipient_id)
91+
.add_token_id(token_id)
92+
.freeze_with(client)
93+
.sign(recipient_key)
94+
)
95+
receipt_associate = associate_tx.execute(client)
96+
97+
if receipt_associate.status != ResponseCode.SUCCESS:
98+
print(f"❌ Token association failed with status: {ResponseCode(receipt_associate.status).name}")
99+
sys.exit(1)
100+
print(f" --> Associated token {token_id} to account {recipient_id}.")
101+
102+
transfer_tx = (
103+
TransferTransaction()
104+
.add_token_transfer(token_id, client.operator_account_id, -amount)
105+
.add_token_transfer(token_id, recipient_id, amount)
106+
)
107+
108+
receipt_transfer = transfer_tx.execute(client)
109+
110+
if receipt_transfer.status != ResponseCode.SUCCESS:
111+
print(f"❌ Token transfer failed with status: {ResponseCode(receipt_transfer.status).name}")
112+
sys.exit(1)
113+
print(f" --> Transferred {amount} tokens to account {recipient_id}.")
114+
115+
116+
def create_token_no_wipe_key(client, operator_id, operator_key):
117+
"""Create a token WITHOUT a wipe key."""
118+
print("\n--- Scenario 1: Token WITHOUT Wipe Key ---")
119+
print("Creating token WITHOUT a wipe key...")
120+
121+
transaction = (
122+
TokenCreateTransaction()
123+
.set_token_name("No Wipe Token")
124+
.set_token_symbol("NWT")
125+
.set_decimals(0)
126+
.set_initial_supply(1000)
127+
.set_treasury_account_id(operator_id)
128+
# No wipe key set here
129+
.freeze_with(client)
130+
)
131+
transaction.sign(operator_key)
132+
133+
try:
134+
receipt = transaction.execute(client)
135+
if receipt.status != ResponseCode.SUCCESS:
136+
print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}")
137+
sys.exit(1)
138+
139+
print(f"✅ Token created: {receipt.token_id}")
140+
return receipt.token_id
141+
142+
except Exception as e:
143+
print(f"❌ Token creation failed with error: {e}")
144+
sys.exit(1)
145+
146+
147+
def demonstrate_wipe_fail(client, token_id, target_account_id):
148+
"""Attempt to wipe tokens when no wipe key exists. Should FAIL."""
149+
print(f"Attempting to wipe tokens from {target_account_id} (Should FAIL)...")
150+
151+
transaction = (
152+
TokenWipeTransaction()
153+
.set_token_id(token_id)
154+
.set_account_id(target_account_id)
155+
.set_amount(10)
156+
.freeze_with(client)
157+
)
158+
159+
try:
160+
# Since no wipe key exists on the token, Hiero will reject this.
161+
receipt = transaction.execute(client)
162+
if receipt.status == ResponseCode.TOKEN_HAS_NO_WIPE_KEY:
163+
print(f"✅ Wipe failed as expected! Token has no wipe key with status: {ResponseCode(receipt.status).name}.")
164+
else:
165+
print(f"❌ Wipe unexpectedly succeeded or failed with status: {ResponseCode(receipt.status).name}")
166+
167+
except Exception as e:
168+
print(f"✅ Wipe failed as expected with error: {e}")
169+
170+
171+
def create_token_with_wipe_key(client, operator_id, operator_key):
172+
"""Create a token WITH a wipe key."""
173+
print("\n--- Scenario 2: Token WITH Wipe Key ---")
174+
print("Creating token WITH a wipe key...")
175+
wipe_key = PrivateKey.generate_ed25519()
176+
177+
transaction = (
178+
TokenCreateTransaction()
179+
.set_token_name("With Wipe Token")
180+
.set_token_symbol("WWT")
181+
.set_decimals(0)
182+
.set_initial_supply(1000)
183+
.set_treasury_account_id(operator_id)
184+
.set_wipe_key(wipe_key) # Setting the wipe key
185+
.freeze_with(client)
186+
)
187+
transaction.sign(operator_key)
188+
189+
try:
190+
receipt = transaction.execute(client)
191+
if receipt.status != ResponseCode.SUCCESS:
192+
print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}")
193+
sys.exit(1)
194+
195+
print(f"✅ Token created: {receipt.token_id}")
196+
return receipt.token_id, wipe_key
197+
198+
except Exception as e:
199+
print(f"❌ Token creation failed with error: {e}")
200+
sys.exit(1)
201+
202+
def demonstrate_wipe_success(client, token_id, target_account_id, wipe_key):
203+
"""Wipe tokens using the valid wipe key."""
204+
print(f"Wiping 10 tokens from {target_account_id} using Wipe Key...")
205+
206+
transaction = (
207+
TokenWipeTransaction()
208+
.set_token_id(token_id)
209+
.set_account_id(target_account_id)
210+
.set_amount(10)
211+
.freeze_with(client)
212+
)
213+
214+
# Critical: Sign with the wipe key
215+
transaction.sign(wipe_key)
216+
217+
receipt = transaction.execute(client)
218+
if receipt.status != ResponseCode.SUCCESS:
219+
print(f"❌ Wipe failed with status: {ResponseCode(receipt.status).name}")
220+
return False
221+
222+
print(f"✅ Wipe Successful!")
223+
return True
224+
225+
226+
def verify_supply(client, token_id):
227+
"""Check the total supply to verify burn occurred."""
228+
info = TokenInfoQuery().set_token_id(token_id).execute(client)
229+
print(f" -> New Total Supply: {info.total_supply} (Should be 990)")
230+
231+
232+
def demonstrate_nft_wipe_scenario(client, operator_id, operator_key, user_id, user_key):
233+
"""
234+
Scenario 3: Create an NFT, Mint it, Transfer it, and then Wipe it.
235+
This demonstrates that Wipe Key works for NON_FUNGIBLE_UNIQUE tokens as well.
236+
"""
237+
print("\n--- Scenario 3: NFT Wipe with Wipe Key ---")
238+
print("Creating an NFT Collection with a Wipe Key...")
239+
240+
wipe_key = PrivateKey.generate_ed25519()
241+
supply_key = PrivateKey.generate_ed25519() # Needed to mint the NFT first
242+
243+
# 1. Create the NFT Token
244+
transaction = (
245+
TokenCreateTransaction()
246+
.set_token_name("Wipeable NFT")
247+
.set_token_symbol("W-NFT")
248+
.set_token_type(TokenType.NON_FUNGIBLE_UNIQUE)
249+
.set_initial_supply(0)
250+
.set_treasury_account_id(operator_id)
251+
.set_admin_key(operator_key)
252+
.set_supply_key(supply_key) # Required to mint
253+
.set_wipe_key(wipe_key) # Required to wipe
254+
.freeze_with(client)
255+
)
256+
transaction.sign(operator_key)
257+
258+
receipt = transaction.execute(client)
259+
nft_token_id = receipt.token_id
260+
print(f"✅ NFT Token created: {nft_token_id}")
261+
262+
# 2. Mint an NFT (Serial #1)
263+
print("Minting NFT Serial #1...")
264+
mint_tx = (
265+
TokenMintTransaction()
266+
.set_token_id(nft_token_id)
267+
.set_metadata([b"Metadata for NFT 1"])
268+
.freeze_with(client)
269+
)
270+
mint_tx.sign(supply_key)
271+
mint_receipt = mint_tx.execute(client)
272+
serial_number = mint_receipt.serial_numbers[0]
273+
print(f"✅ Minted NFT Serial: {serial_number}")
274+
275+
# 3. Associate User and Transfer NFT to them
276+
print(f"Transferring NFT #{serial_number} to user {user_id}...")
277+
278+
# Associate
279+
associate_tx = (
280+
TokenAssociateTransaction()
281+
.set_account_id(user_id)
282+
.add_token_id(nft_token_id)
283+
.freeze_with(client)
284+
.sign(user_key)
285+
).execute(client)
286+
287+
# Transfer
288+
# FIX 2: Use NftId(token_id, serial_number)
289+
transfer_tx = (
290+
TransferTransaction()
291+
.add_nft_transfer(NftId(nft_token_id, serial_number), operator_id, user_id)
292+
.execute(client)
293+
)
294+
print(f"✅ Transfer complete.")
295+
296+
# 4. Wipe the NFT from the User
297+
print(f"Attempting to WIPE NFT #{serial_number} from user {user_id}...")
298+
299+
wipe_tx = (
300+
TokenWipeTransaction()
301+
.set_token_id(nft_token_id)
302+
.set_account_id(user_id)
303+
.set_serial([serial_number])
304+
.freeze_with(client)
305+
)
306+
wipe_tx.sign(wipe_key) # Sign with Wipe Key
307+
308+
wipe_receipt = wipe_tx.execute(client)
309+
310+
if wipe_receipt.status == ResponseCode.SUCCESS:
311+
print("✅ NFT Wipe Successful! The NFT has been effectively burned from the user's account.")
312+
else:
313+
print(f"❌ NFT Wipe Failed: {ResponseCode(wipe_receipt.status).name}")
314+
315+
316+
def main():
317+
"""
318+
Main execution flow:
319+
1. Setup Client
320+
2. Create a recipient account
321+
3. Scenario 1: Fail to wipe without key (Fungible)
322+
4. Scenario 2: Successfully wipe with key (Fungible)
323+
5. Scenario 3: Successfully wipe with key (NFT)
324+
"""
325+
client, operator_id, operator_key = setup_client()
326+
327+
# Create a generic user to hold tokens
328+
print("\nCreating a user account to hold tokens...")
329+
user_id, user_key = create_recipient_account(client)
330+
print(f"User created: {user_id}")
331+
332+
# --- Scenario 1: No Wipe Key (Fungible) ---
333+
token_id_no_key = create_token_no_wipe_key(client, operator_id, operator_key)
334+
associate_and_transfer(client, token_id_no_key, user_id, user_key, 50)
335+
demonstrate_wipe_fail(client, token_id_no_key, user_id)
336+
337+
# --- Scenario 2: With Wipe Key (Fungible) ---
338+
token_id_with_key, wipe_key = create_token_with_wipe_key(client, operator_id, operator_key)
339+
associate_and_transfer(client, token_id_with_key, user_id, user_key, 50)
340+
341+
if demonstrate_wipe_success(client, token_id_with_key, user_id, wipe_key):
342+
verify_supply(client, token_id_with_key)
343+
344+
# --- Scenario 3: NFT Wipe (Non-Fungible) ---
345+
demonstrate_nft_wipe_scenario(client, operator_id, operator_key, user_id, user_key)
346+
347+
print("\n🎉 Wipe key demonstration completed!")
348+
349+
if __name__ == "__main__":
350+
main()

0 commit comments

Comments
 (0)