htcli 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. htcli-1.1.0.dist-info/METADATA +509 -0
  2. htcli-1.1.0.dist-info/RECORD +140 -0
  3. htcli-1.1.0.dist-info/WHEEL +4 -0
  4. htcli-1.1.0.dist-info/entry_points.txt +2 -0
  5. htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/htcli/__init__.py +5 -0
  8. src/htcli/client/__init__.py +338 -0
  9. src/htcli/client/extrinsics/__init__.py +26 -0
  10. src/htcli/client/extrinsics/base.py +487 -0
  11. src/htcli/client/extrinsics/consensus.py +79 -0
  12. src/htcli/client/extrinsics/governance.py +714 -0
  13. src/htcli/client/extrinsics/identity.py +490 -0
  14. src/htcli/client/extrinsics/node.py +1054 -0
  15. src/htcli/client/extrinsics/overwatch.py +401 -0
  16. src/htcli/client/extrinsics/staking.py +1504 -0
  17. src/htcli/client/extrinsics/subnet.py +2218 -0
  18. src/htcli/client/extrinsics/validator.py +203 -0
  19. src/htcli/client/extrinsics/wallet.py +323 -0
  20. src/htcli/client/offchain/__init__.py +10 -0
  21. src/htcli/client/offchain/backup.py +385 -0
  22. src/htcli/client/offchain/config.py +541 -0
  23. src/htcli/client/offchain/wallet.py +839 -0
  24. src/htcli/client/rpc/__init__.py +20 -0
  25. src/htcli/client/rpc/chain.py +568 -0
  26. src/htcli/client/rpc/node.py +783 -0
  27. src/htcli/client/rpc/overwatch.py +680 -0
  28. src/htcli/client/rpc/staking.py +216 -0
  29. src/htcli/client/rpc/subnet.py +2104 -0
  30. src/htcli/client/rpc/wallet.py +912 -0
  31. src/htcli/commands/__init__.py +31 -0
  32. src/htcli/commands/chain/__init__.py +66 -0
  33. src/htcli/commands/chain/display.py +204 -0
  34. src/htcli/commands/chain/handlers.py +260 -0
  35. src/htcli/commands/config/__init__.py +158 -0
  36. src/htcli/commands/config/display.py +353 -0
  37. src/htcli/commands/config/handlers.py +347 -0
  38. src/htcli/commands/config/prompts.py +357 -0
  39. src/htcli/commands/consensus/__init__.py +61 -0
  40. src/htcli/commands/consensus/handlers.py +100 -0
  41. src/htcli/commands/governance/__init__.py +49 -0
  42. src/htcli/commands/governance/handlers.py +81 -0
  43. src/htcli/commands/node/__init__.py +304 -0
  44. src/htcli/commands/node/display.py +749 -0
  45. src/htcli/commands/node/error_handling.py +470 -0
  46. src/htcli/commands/node/handlers.py +844 -0
  47. src/htcli/commands/node/prompts.py +346 -0
  48. src/htcli/commands/overwatch/__init__.py +219 -0
  49. src/htcli/commands/overwatch/display.py +396 -0
  50. src/htcli/commands/overwatch/error_handling.py +276 -0
  51. src/htcli/commands/overwatch/handlers.py +443 -0
  52. src/htcli/commands/overwatch/prompts.py +359 -0
  53. src/htcli/commands/stake/__init__.py +736 -0
  54. src/htcli/commands/stake/display.py +1103 -0
  55. src/htcli/commands/stake/error_handling.py +425 -0
  56. src/htcli/commands/stake/handlers.py +1902 -0
  57. src/htcli/commands/stake/prompts.py +1080 -0
  58. src/htcli/commands/subnet/__init__.py +639 -0
  59. src/htcli/commands/subnet/display.py +801 -0
  60. src/htcli/commands/subnet/error_handling.py +524 -0
  61. src/htcli/commands/subnet/handlers.py +2855 -0
  62. src/htcli/commands/subnet/prompts.py +1225 -0
  63. src/htcli/commands/validator/__init__.py +192 -0
  64. src/htcli/commands/validator/display.py +54 -0
  65. src/htcli/commands/validator/handlers.py +340 -0
  66. src/htcli/commands/wallet/__init__.py +546 -0
  67. src/htcli/commands/wallet/display.py +806 -0
  68. src/htcli/commands/wallet/error_handling.py +210 -0
  69. src/htcli/commands/wallet/handlers.py +3040 -0
  70. src/htcli/commands/wallet/prompts.py +1518 -0
  71. src/htcli/config.py +184 -0
  72. src/htcli/dependencies.py +186 -0
  73. src/htcli/errors/__init__.py +63 -0
  74. src/htcli/errors/base.py +141 -0
  75. src/htcli/errors/display.py +20 -0
  76. src/htcli/errors/handlers.py +710 -0
  77. src/htcli/main.py +343 -0
  78. src/htcli/models/__init__.py +21 -0
  79. src/htcli/models/enums/enum_types.py +35 -0
  80. src/htcli/models/errors.py +103 -0
  81. src/htcli/models/requests/__init__.py +197 -0
  82. src/htcli/models/requests/config.py +70 -0
  83. src/htcli/models/requests/consensus.py +19 -0
  84. src/htcli/models/requests/governance.py +38 -0
  85. src/htcli/models/requests/identity.py +51 -0
  86. src/htcli/models/requests/key.py +22 -0
  87. src/htcli/models/requests/node.py +91 -0
  88. src/htcli/models/requests/overwatch.py +64 -0
  89. src/htcli/models/requests/staking.py +580 -0
  90. src/htcli/models/requests/subnet.py +195 -0
  91. src/htcli/models/requests/validator.py +139 -0
  92. src/htcli/models/requests/wallet.py +118 -0
  93. src/htcli/models/responses/__init__.py +147 -0
  94. src/htcli/models/responses/base.py +18 -0
  95. src/htcli/models/responses/chain.py +39 -0
  96. src/htcli/models/responses/config.py +58 -0
  97. src/htcli/models/responses/identity.py +102 -0
  98. src/htcli/models/responses/overwatch.py +51 -0
  99. src/htcli/models/responses/staking.py +502 -0
  100. src/htcli/models/responses/subnet.py +856 -0
  101. src/htcli/models/responses/wallet.py +185 -0
  102. src/htcli/ui/__init__.py +87 -0
  103. src/htcli/ui/colors.py +309 -0
  104. src/htcli/ui/components/__init__.py +60 -0
  105. src/htcli/ui/components/panels.py +174 -0
  106. src/htcli/ui/components/progress.py +166 -0
  107. src/htcli/ui/components/spinners.py +92 -0
  108. src/htcli/ui/components/tables.py +809 -0
  109. src/htcli/ui/components/trees.py +721 -0
  110. src/htcli/ui/display.py +336 -0
  111. src/htcli/ui/prompts.py +870 -0
  112. src/htcli/utils/__init__.py +76 -0
  113. src/htcli/utils/blockchain/__init__.py +75 -0
  114. src/htcli/utils/blockchain/formatting.py +368 -0
  115. src/htcli/utils/blockchain/patches.py +286 -0
  116. src/htcli/utils/blockchain/peer_id.py +186 -0
  117. src/htcli/utils/blockchain/staking.py +448 -0
  118. src/htcli/utils/blockchain/type_registry.py +1373 -0
  119. src/htcli/utils/blockchain/validation.py +179 -0
  120. src/htcli/utils/cache.py +613 -0
  121. src/htcli/utils/constants.py +38 -0
  122. src/htcli/utils/legacy/__init__.py +12 -0
  123. src/htcli/utils/legacy/colors.py +311 -0
  124. src/htcli/utils/legacy/crypto.py +1176 -0
  125. src/htcli/utils/legacy/formatting.py +452 -0
  126. src/htcli/utils/legacy/interactive.py +306 -0
  127. src/htcli/utils/legacy/subnet_manifest.py +265 -0
  128. src/htcli/utils/legacy/validation.py +488 -0
  129. src/htcli/utils/logging.py +183 -0
  130. src/htcli/utils/network/__init__.py +20 -0
  131. src/htcli/utils/network/subnet.py +344 -0
  132. src/htcli/utils/prompts.py +27 -0
  133. src/htcli/utils/scale_codec.py +155 -0
  134. src/htcli/utils/validation/__init__.py +57 -0
  135. src/htcli/utils/validation/prompt_validators.py +267 -0
  136. src/htcli/utils/wallet/__init__.py +65 -0
  137. src/htcli/utils/wallet/auth.py +151 -0
  138. src/htcli/utils/wallet/core.py +1069 -0
  139. src/htcli/utils/wallet/crypto.py +1615 -0
  140. src/htcli/utils/wallet/migration.py +159 -0
@@ -0,0 +1,1902 @@
1
+ """
2
+ Stake command handlers for RPC-based operations and extrinsic operations.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from ...dependencies import get_client
8
+ from ...errors.handlers import handle_and_display_error
9
+ from ...ui.components import HTCLILoadingContext
10
+ from ...utils import retrieve_wallet_with_validation
11
+ from .display import (
12
+ display_claim_result,
13
+ display_coldkey_stakes_rpc,
14
+ display_delegate_donate_result,
15
+ display_delegate_stake_result,
16
+ display_delegate_stakes_rpc,
17
+ display_delegate_swap_result,
18
+ display_delegate_transfer_result,
19
+ display_node_delegate_donate_result,
20
+ display_node_delegate_stake_result,
21
+ display_node_delegate_stakes_rpc,
22
+ display_node_delegate_swap_result,
23
+ display_node_delegate_transfer_result,
24
+ display_proof_of_stake_rpc,
25
+ display_stake_add_result,
26
+ display_stake_remove_result,
27
+ display_swap_node_to_subnet_result,
28
+ display_swap_queue_update_result,
29
+ display_swap_subnet_to_node_result,
30
+ display_swap_subnet_to_validator_result,
31
+ display_swap_validator_to_subnet_result,
32
+ display_validator_delegate_donate_result,
33
+ display_validator_delegate_stake_result,
34
+ display_validator_delegate_swap_result,
35
+ display_validator_delegate_transfer_result,
36
+ )
37
+ from .error_handling import handle_stake_error
38
+ from .prompts import (
39
+ prompt_delegate_donate,
40
+ prompt_delegate_stake_add,
41
+ prompt_delegate_stake_remove,
42
+ prompt_delegate_swap,
43
+ prompt_delegate_transfer,
44
+ prompt_node_delegate_donate,
45
+ prompt_node_delegate_stake_add,
46
+ prompt_node_delegate_stake_remove,
47
+ prompt_node_delegate_swap,
48
+ prompt_node_delegate_transfer,
49
+ prompt_stake_add,
50
+ prompt_stake_claim,
51
+ prompt_stake_remove,
52
+ prompt_swap_node_to_subnet,
53
+ prompt_swap_queue_update,
54
+ prompt_swap_subnet_to_node,
55
+ prompt_swap_subnet_to_validator,
56
+ prompt_swap_validator_to_subnet,
57
+ prompt_validator_delegate_donate,
58
+ prompt_validator_delegate_stake_add,
59
+ prompt_validator_delegate_stake_remove,
60
+ prompt_validator_delegate_swap,
61
+ prompt_validator_delegate_transfer,
62
+ )
63
+
64
+
65
+ def get_coldkey_stakes_handler(coldkey: Optional[str]):
66
+ """Handle getting coldkey stakes using RPC.
67
+
68
+ Fetches all stake types:
69
+ - Direct stakes (from nodes owned by the coldkey)
70
+ - Delegate stakes (subnet delegate stakes)
71
+ - Node delegate stakes (node delegate stakes)
72
+ """
73
+ try:
74
+ if coldkey is None:
75
+ from ...utils.prompts import string_prompt
76
+
77
+ coldkey = string_prompt("Enter coldkey address")
78
+
79
+ try:
80
+ from ...utils.wallet.core import resolve_coldkey_address
81
+
82
+ resolved_coldkey = resolve_coldkey_address(coldkey)
83
+ except ValueError as e:
84
+ from ...ui.display import print_error
85
+
86
+ print_error(str(e))
87
+ return
88
+
89
+ client = get_client()
90
+
91
+ with HTCLILoadingContext(
92
+ f"Fetching all stakes for coldkey {resolved_coldkey}..."
93
+ ):
94
+ # Fetch all three types of stakes
95
+ direct_stakes_result = client.rpc.wallet.network_get_coldkey_stakes(
96
+ resolved_coldkey
97
+ )
98
+ delegate_stakes_result = client.rpc.wallet.network_get_delegate_stakes(
99
+ resolved_coldkey
100
+ )
101
+ node_delegate_stakes_result = (
102
+ client.rpc.wallet.network_get_node_delegate_stakes(resolved_coldkey)
103
+ )
104
+
105
+ # Display all stake types
106
+ has_any_stakes = False
107
+
108
+ # Display direct stakes
109
+ if direct_stakes_result.success and direct_stakes_result.data:
110
+ display_coldkey_stakes_rpc(direct_stakes_result.data, resolved_coldkey)
111
+ has_any_stakes = True
112
+
113
+ # Display delegate stakes
114
+ if delegate_stakes_result.success and delegate_stakes_result.data:
115
+ display_delegate_stakes_rpc(delegate_stakes_result.data, resolved_coldkey)
116
+ has_any_stakes = True
117
+
118
+ # Display node delegate stakes
119
+ if node_delegate_stakes_result.success and node_delegate_stakes_result.data:
120
+ display_node_delegate_stakes_rpc(
121
+ node_delegate_stakes_result.data, resolved_coldkey
122
+ )
123
+ has_any_stakes = True
124
+
125
+ # If no stakes found in any category
126
+ if not has_any_stakes:
127
+ from ...ui.colors import info
128
+ from ...ui.display import HTCLIConsole
129
+
130
+ console = HTCLIConsole()
131
+ console.print(info(f"No stakes found for coldkey {resolved_coldkey}"))
132
+ console.print(
133
+ info(
134
+ "Checked: direct stakes, delegate stakes, and node delegate stakes"
135
+ )
136
+ )
137
+
138
+ except Exception as e:
139
+ error_msg = str(e)
140
+ if not handle_stake_error(
141
+ error_msg,
142
+ None,
143
+ None,
144
+ "swap queue update",
145
+ client if "client" in locals() else None,
146
+ ):
147
+ handle_and_display_error(e)
148
+
149
+
150
+ def get_delegate_stakes_handler(account: Optional[str]):
151
+ """Handle getting delegate stakes using RPC."""
152
+ try:
153
+ if account is None:
154
+ from ...utils.prompts import string_prompt
155
+
156
+ account = string_prompt("Enter account address")
157
+
158
+ try:
159
+ from ...utils.wallet.core import resolve_coldkey_address
160
+
161
+ account = resolve_coldkey_address(account)
162
+ except ValueError as e:
163
+ from ...ui.display import print_error
164
+
165
+ print_error(str(e))
166
+ return
167
+
168
+ client = get_client()
169
+
170
+ with HTCLILoadingContext(f"Fetching delegate stakes for account {account}..."):
171
+ result = client.rpc.wallet.network_get_delegate_stakes(account)
172
+
173
+ if result.success:
174
+ display_delegate_stakes_rpc(result.data, account)
175
+ else:
176
+ from ...ui.display import print_error
177
+
178
+ print_error(f"Failed to get delegate stakes: {result.message}")
179
+
180
+ except Exception as e:
181
+ error_msg = str(e)
182
+ if not handle_stake_error(
183
+ error_msg,
184
+ None,
185
+ None,
186
+ "swap queue update",
187
+ client if "client" in locals() else None,
188
+ ):
189
+ handle_and_display_error(e)
190
+
191
+
192
+ def get_node_delegate_stakes_handler(account: Optional[str]):
193
+ """Handle getting node delegate stakes using RPC."""
194
+ try:
195
+ if account is None:
196
+ from ...utils.prompts import string_prompt
197
+
198
+ account = string_prompt("Enter account address")
199
+
200
+ try:
201
+ from ...utils.wallet.core import resolve_coldkey_address
202
+
203
+ account = resolve_coldkey_address(account)
204
+ except ValueError as e:
205
+ from ...ui.display import print_error
206
+
207
+ print_error(str(e))
208
+ return
209
+
210
+ client = get_client()
211
+
212
+ with HTCLILoadingContext(
213
+ f"Fetching node delegate stakes for account {account}..."
214
+ ):
215
+ result = client.rpc.wallet.network_get_node_delegate_stakes(account)
216
+
217
+ if result.success:
218
+ display_node_delegate_stakes_rpc(result.data, account)
219
+ else:
220
+ from ...ui.display import print_error
221
+
222
+ print_error(f"Failed to get node delegate stakes: {result.message}")
223
+
224
+ except Exception as e:
225
+ error_msg = str(e)
226
+ if not handle_stake_error(
227
+ error_msg,
228
+ None,
229
+ None,
230
+ "swap queue update",
231
+ client if "client" in locals() else None,
232
+ ):
233
+ handle_and_display_error(e)
234
+
235
+
236
+ def verify_proof_of_stake_handler(
237
+ subnet_id: Optional[int], peer_id: Optional[str], min_class: Optional[int]
238
+ ):
239
+ """Handle checking proof of stake using RPC."""
240
+ try:
241
+ if subnet_id is None:
242
+ from ...utils.prompts import integer_prompt
243
+
244
+ subnet_id = integer_prompt("Enter subnet ID")
245
+
246
+ if peer_id is None:
247
+ from ...utils.prompts import string_prompt
248
+
249
+ peer_id = string_prompt("Enter peer ID")
250
+
251
+ if min_class is None:
252
+ from ...utils.prompts import integer_prompt
253
+
254
+ min_class = integer_prompt("Enter minimum class (0-3)")
255
+
256
+ client = get_client()
257
+
258
+ # Convert peer_id to bytes for RPC
259
+ if isinstance(peer_id, str):
260
+ # Convert string to bytes
261
+ peer_id_bytes = peer_id.encode("utf-8")
262
+ else:
263
+ # Already bytes
264
+ peer_id_bytes = peer_id
265
+
266
+ with HTCLILoadingContext(f"Verifying proof of stake for peer {peer_id}..."):
267
+ result = client.rpc.subnet.network_proof_of_stake(
268
+ subnet_id, peer_id_bytes, min_class
269
+ )
270
+
271
+ if result.success:
272
+ display_proof_of_stake_rpc(result.data, subnet_id, peer_id, min_class)
273
+ else:
274
+ from ...ui.display import print_error
275
+
276
+ print_error(f"Failed to verify proof of stake: {result.message}")
277
+
278
+ except Exception as e:
279
+ error_msg = str(e)
280
+ if not handle_stake_error(
281
+ error_msg,
282
+ None,
283
+ None,
284
+ "swap queue update",
285
+ client if "client" in locals() else None,
286
+ ):
287
+ handle_and_display_error(e)
288
+
289
+
290
+ # ============================================================================
291
+ # EXTRINSIC HANDLERS - Write Operations
292
+ # ============================================================================
293
+
294
+
295
+ def add_stake_handler(
296
+ subnet_id: Optional[int] = None,
297
+ subnet_node_id: Optional[int] = None,
298
+ hotkey_address: Optional[str] = None,
299
+ stake_amount: Optional[float] = None,
300
+ coldkey: Optional[str] = None,
301
+ ):
302
+ """Handle adding stake to a node."""
303
+ try:
304
+ # STEP 1: Collect parameters via prompts
305
+ request = prompt_stake_add(
306
+ subnet_id=subnet_id,
307
+ node_id=subnet_node_id,
308
+ hotkey=hotkey_address, # Pass hotkey_address as hotkey parameter
309
+ amount=stake_amount,
310
+ )
311
+
312
+ # STEP 2: Get signing wallet
313
+ if coldkey:
314
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
315
+
316
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
317
+ else:
318
+ wallet_name, keypair = retrieve_wallet_with_validation(
319
+ wallet_type="coldkey", purpose="sign the stake transaction"
320
+ )
321
+
322
+ # STEP 3: Execute via client
323
+ client = get_client()
324
+ with HTCLILoadingContext("Adding stake to node..."):
325
+ response = client.extrinsics.staking.add_stake(request, keypair)
326
+
327
+ # Check for errors in result
328
+ if isinstance(response, dict) and not response.get("success", False):
329
+ error_msg = response.get("error", "Stake add failed")
330
+ if handle_stake_error(
331
+ error_msg,
332
+ request.subnet_id,
333
+ request.subnet_node_id,
334
+ "add stake",
335
+ client,
336
+ ):
337
+ return
338
+
339
+ # STEP 4: Display result
340
+ display_stake_add_result(response)
341
+
342
+ except Exception as e:
343
+ error_msg = str(e)
344
+ subnet_id_for_error = None
345
+ node_id_for_error = None
346
+ client_for_error = None
347
+ try:
348
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
349
+ node_id_for_error = (
350
+ request.subnet_node_id if "request" in locals() else None
351
+ )
352
+ client_for_error = client if "client" in locals() else None
353
+ except (AttributeError, NameError):
354
+ pass
355
+
356
+ if not handle_stake_error(
357
+ error_msg,
358
+ subnet_id_for_error,
359
+ node_id_for_error,
360
+ "add stake",
361
+ client_for_error,
362
+ ):
363
+ handle_and_display_error(e)
364
+
365
+
366
+ def remove_stake_handler(
367
+ subnet_id: Optional[int] = None,
368
+ hotkey: Optional[str] = None,
369
+ stake_amount: Optional[float] = None,
370
+ coldkey: Optional[str] = None,
371
+ ):
372
+ """Handle removing stake from a node."""
373
+ try:
374
+ # STEP 1: Resolve coldkey FIRST so we can use it for hotkey disambiguation
375
+ if coldkey:
376
+ from ...ui.display import print_error
377
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
378
+
379
+ try:
380
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey.strip())
381
+ except ValueError as e:
382
+ print_error(f"Failed to resolve coldkey '{coldkey}': {str(e)}")
383
+ print_error("Please provide a valid coldkey wallet name or address")
384
+ raise
385
+ except Exception as e:
386
+ raise RuntimeError(
387
+ f"Unexpected error resolving coldkey '{coldkey}': {str(e)}"
388
+ ) from e
389
+ else:
390
+ wallet_name, keypair = retrieve_wallet_with_validation(
391
+ wallet_type="coldkey", purpose="sign the unstake transaction"
392
+ )
393
+
394
+ # STEP 2: Collect parameters (with coldkey context for hotkey disambiguation)
395
+ request = prompt_stake_remove(
396
+ subnet_id=subnet_id,
397
+ hotkey=hotkey,
398
+ amount=stake_amount,
399
+ coldkey_name=wallet_name, # Pass coldkey name for hotkey disambiguation
400
+ )
401
+
402
+ # STEP 3: Execute
403
+ client = get_client()
404
+ with HTCLILoadingContext("Removing stake from node..."):
405
+ response = client.extrinsics.staking.remove_stake(request, keypair)
406
+
407
+ # Check for errors in result
408
+ if isinstance(response, dict) and not response.get("success", False):
409
+ error_msg = response.get("error", "Stake remove failed")
410
+ if handle_stake_error(
411
+ error_msg, request.subnet_id, None, "remove stake", client
412
+ ):
413
+ return
414
+
415
+ # STEP 4: Display
416
+ display_stake_remove_result(response)
417
+
418
+ except Exception as e:
419
+ error_msg = str(e)
420
+ subnet_id_for_error = None
421
+ client_for_error = None
422
+ try:
423
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
424
+ client_for_error = client if "client" in locals() else None
425
+ except (AttributeError, NameError):
426
+ pass
427
+
428
+ if not handle_stake_error(
429
+ error_msg, subnet_id_for_error, None, "remove stake", client_for_error
430
+ ):
431
+ handle_and_display_error(e)
432
+
433
+
434
+ def claim_handler(coldkey: Optional[str] = None, yes: bool = False):
435
+ """Handle claiming unbonded tokens."""
436
+ try:
437
+ # STEP 1: Prompt for confirmation (unless --yes flag is set)
438
+ if not yes:
439
+ prompt_stake_claim()
440
+
441
+ # STEP 2: Get signing wallet
442
+ if coldkey:
443
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
444
+
445
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
446
+ else:
447
+ wallet_name, keypair = retrieve_wallet_with_validation(
448
+ wallet_type="coldkey", purpose="sign the claim transaction"
449
+ )
450
+
451
+ # STEP 3: Execute
452
+ client = get_client()
453
+ with HTCLILoadingContext("Claiming unbonded tokens..."):
454
+ response = client.extrinsics.staking.claim_unbondings(keypair)
455
+
456
+ # Check for errors in result
457
+ if isinstance(response, dict) and not response.get("success", False):
458
+ error_msg = response.get("error", "Claim failed")
459
+ if handle_stake_error(error_msg, None, None, "claim", client):
460
+ return
461
+
462
+ # STEP 4: Display
463
+ display_claim_result(response)
464
+
465
+ except Exception as e:
466
+ error_msg = str(e)
467
+ client_for_error = None
468
+ try:
469
+ client_for_error = client if "client" in locals() else None
470
+ except NameError:
471
+ pass
472
+
473
+ if not handle_stake_error(error_msg, None, None, "claim", client_for_error):
474
+ handle_and_display_error(e)
475
+
476
+
477
+ def delegate_add_handler(
478
+ subnet_id: Optional[int] = None,
479
+ node_id: Optional[int] = None,
480
+ stake_amount: Optional[float] = None,
481
+ coldkey: Optional[str] = None,
482
+ validator_id: Optional[int] = None,
483
+ ):
484
+ """Handle adding delegate stake to a subnet, validator, or specific node.
485
+
486
+ Routes to validator delegate if validator_id is provided, node delegate if
487
+ node_id is provided, otherwise subnet delegate.
488
+ """
489
+ try:
490
+ from ...ui.prompts import confirm_prompt
491
+
492
+ if validator_id is not None:
493
+ request = prompt_validator_delegate_stake_add(
494
+ validator_id=validator_id,
495
+ stake_amount=stake_amount,
496
+ )
497
+
498
+ if coldkey:
499
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
500
+
501
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
502
+ else:
503
+ wallet_name, keypair = retrieve_wallet_with_validation(
504
+ wallet_type="coldkey",
505
+ purpose="sign the validator delegate stake transaction",
506
+ )
507
+
508
+ client = get_client()
509
+ with HTCLILoadingContext("Adding delegate stake to validator..."):
510
+ response = client.extrinsics.staking.add_to_validator_delegate_stake(
511
+ request, keypair
512
+ )
513
+
514
+ if isinstance(response, dict) and not response.get("success", False):
515
+ error_msg = response.get("error", "Validator delegate stake add failed")
516
+ if handle_stake_error(
517
+ error_msg, None, None, "add validator delegate stake", client
518
+ ):
519
+ return
520
+
521
+ display_validator_delegate_stake_result(response, operation="add")
522
+ return
523
+
524
+ # Determine if this is node-level or subnet-level delegation
525
+ is_node_delegation = node_id is not None
526
+
527
+ # If no node_id provided via CLI, ask if user wants node-level delegation
528
+ if not is_node_delegation and subnet_id is not None:
529
+ want_node = confirm_prompt(
530
+ "Do you want to delegate to a specific node?", default=False
531
+ )
532
+ if want_node:
533
+ is_node_delegation = True
534
+
535
+ if is_node_delegation:
536
+ # NODE DELEGATE PATH
537
+ request = prompt_node_delegate_stake_add(
538
+ subnet_id=subnet_id,
539
+ subnet_node_id=node_id,
540
+ stake_amount=stake_amount,
541
+ )
542
+
543
+ if coldkey:
544
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
545
+
546
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
547
+ else:
548
+ wallet_name, keypair = retrieve_wallet_with_validation(
549
+ wallet_type="coldkey",
550
+ purpose="sign the node delegate stake transaction",
551
+ )
552
+
553
+ client = get_client()
554
+ with HTCLILoadingContext("Adding delegate stake to node..."):
555
+ response = client.extrinsics.staking.add_to_node_delegate_stake(
556
+ request, keypair
557
+ )
558
+
559
+ if isinstance(response, dict) and not response.get("success", False):
560
+ error_msg = response.get("error", "Node delegate stake add failed")
561
+ if handle_stake_error(
562
+ error_msg,
563
+ request.subnet_id,
564
+ request.subnet_node_id,
565
+ "add node delegate stake",
566
+ client,
567
+ ):
568
+ return
569
+
570
+ display_node_delegate_stake_result(response, operation="add")
571
+ else:
572
+ # SUBNET DELEGATE PATH
573
+ request = prompt_delegate_stake_add(
574
+ subnet_id=subnet_id,
575
+ stake_amount=stake_amount,
576
+ )
577
+
578
+ if coldkey:
579
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
580
+
581
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
582
+ else:
583
+ wallet_name, keypair = retrieve_wallet_with_validation(
584
+ wallet_type="coldkey", purpose="sign the delegate stake transaction"
585
+ )
586
+
587
+ client = get_client()
588
+ with HTCLILoadingContext("Adding delegate stake to subnet..."):
589
+ response = client.extrinsics.staking.add_to_delegate_stake(
590
+ request, keypair
591
+ )
592
+
593
+ if isinstance(response, dict) and not response.get("success", False):
594
+ error_msg = response.get("error", "Delegate stake add failed")
595
+ if handle_stake_error(
596
+ error_msg, request.subnet_id, None, "add delegate stake", client
597
+ ):
598
+ return
599
+
600
+ display_delegate_stake_result(response, operation="add")
601
+
602
+ except Exception as e:
603
+ error_msg = str(e)
604
+ subnet_id_for_error = None
605
+ node_id_for_error = None
606
+ client_for_error = None
607
+ try:
608
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
609
+ node_id_for_error = (
610
+ getattr(request, "subnet_node_id", None)
611
+ if "request" in locals()
612
+ else None
613
+ )
614
+ client_for_error = client if "client" in locals() else None
615
+ except (AttributeError, NameError):
616
+ pass
617
+
618
+ if not handle_stake_error(
619
+ error_msg,
620
+ subnet_id_for_error,
621
+ node_id_for_error,
622
+ "add delegate stake",
623
+ client_for_error,
624
+ ):
625
+ handle_and_display_error(e)
626
+
627
+
628
+ def delegate_remove_handler(
629
+ subnet_id: Optional[int] = None,
630
+ node_id: Optional[int] = None,
631
+ shares_to_remove: Optional[int] = None,
632
+ coldkey: Optional[str] = None,
633
+ validator_id: Optional[int] = None,
634
+ ):
635
+ """Handle removing delegate stake from a subnet, validator, or specific node.
636
+
637
+ Routes to validator delegate if validator_id is provided, node delegate if
638
+ node_id is provided, otherwise subnet delegate.
639
+ """
640
+ try:
641
+ from ...ui.prompts import confirm_prompt
642
+
643
+ if validator_id is not None:
644
+ request = prompt_validator_delegate_stake_remove(
645
+ validator_id=validator_id,
646
+ shares_to_remove=shares_to_remove,
647
+ )
648
+
649
+ if coldkey:
650
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
651
+
652
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
653
+ else:
654
+ wallet_name, keypair = retrieve_wallet_with_validation(
655
+ wallet_type="coldkey",
656
+ purpose="sign the validator delegate unstake transaction",
657
+ )
658
+
659
+ client = get_client()
660
+ with HTCLILoadingContext("Removing delegate stake from validator..."):
661
+ response = client.extrinsics.staking.remove_validator_delegate_stake(
662
+ request, keypair
663
+ )
664
+
665
+ if isinstance(response, dict) and not response.get("success", False):
666
+ error_msg = response.get(
667
+ "error", "Validator delegate stake remove failed"
668
+ )
669
+ if handle_stake_error(
670
+ error_msg, None, None, "remove validator delegate stake", client
671
+ ):
672
+ return
673
+
674
+ display_validator_delegate_stake_result(response, operation="remove")
675
+ return
676
+
677
+ # Determine if this is node-level or subnet-level removal
678
+ is_node_delegation = node_id is not None
679
+
680
+ # If no node_id provided via CLI, ask if user wants node-level removal
681
+ if not is_node_delegation and subnet_id is not None:
682
+ want_node = confirm_prompt(
683
+ "Do you want to remove from a specific node?", default=False
684
+ )
685
+ if want_node:
686
+ is_node_delegation = True
687
+
688
+ if is_node_delegation:
689
+ # NODE DELEGATE PATH
690
+ request = prompt_node_delegate_stake_remove(
691
+ subnet_id=subnet_id,
692
+ subnet_node_id=node_id,
693
+ shares_to_remove=shares_to_remove,
694
+ )
695
+
696
+ if coldkey:
697
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
698
+
699
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
700
+ else:
701
+ wallet_name, keypair = retrieve_wallet_with_validation(
702
+ wallet_type="coldkey",
703
+ purpose="sign the node delegate unstake transaction",
704
+ )
705
+
706
+ client = get_client()
707
+ with HTCLILoadingContext("Removing delegate stake from node..."):
708
+ response = client.extrinsics.staking.remove_node_delegate_stake(
709
+ request, keypair
710
+ )
711
+
712
+ if isinstance(response, dict) and not response.get("success", False):
713
+ error_msg = response.get("error", "Node delegate stake remove failed")
714
+ if handle_stake_error(
715
+ error_msg,
716
+ request.subnet_id,
717
+ request.subnet_node_id,
718
+ "remove node delegate stake",
719
+ client,
720
+ ):
721
+ return
722
+
723
+ display_node_delegate_stake_result(response, operation="remove")
724
+ else:
725
+ # SUBNET DELEGATE PATH
726
+ request = prompt_delegate_stake_remove(
727
+ subnet_id=subnet_id,
728
+ shares_to_remove=shares_to_remove,
729
+ )
730
+
731
+ if coldkey:
732
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
733
+
734
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
735
+ else:
736
+ wallet_name, keypair = retrieve_wallet_with_validation(
737
+ wallet_type="coldkey",
738
+ purpose="sign the delegate unstake transaction",
739
+ )
740
+
741
+ client = get_client()
742
+ with HTCLILoadingContext("Removing delegate stake from subnet..."):
743
+ response = client.extrinsics.staking.remove_delegate_stake(
744
+ request, keypair
745
+ )
746
+
747
+ if isinstance(response, dict) and not response.get("success", False):
748
+ error_msg = response.get("error", "Delegate stake remove failed")
749
+ if handle_stake_error(
750
+ error_msg, request.subnet_id, None, "remove delegate stake", client
751
+ ):
752
+ return
753
+
754
+ display_delegate_stake_result(response, operation="remove")
755
+
756
+ except Exception as e:
757
+ error_msg = str(e)
758
+ subnet_id_for_error = None
759
+ node_id_for_error = None
760
+ client_for_error = None
761
+ try:
762
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
763
+ node_id_for_error = (
764
+ getattr(request, "subnet_node_id", None)
765
+ if "request" in locals()
766
+ else None
767
+ )
768
+ client_for_error = client if "client" in locals() else None
769
+ except (AttributeError, NameError):
770
+ pass
771
+
772
+ if not handle_stake_error(
773
+ error_msg,
774
+ subnet_id_for_error,
775
+ node_id_for_error,
776
+ "remove delegate stake",
777
+ client_for_error,
778
+ ):
779
+ handle_and_display_error(e)
780
+
781
+
782
+ def node_delegate_add_handler(
783
+ subnet_id: Optional[int] = None,
784
+ subnet_node_id: Optional[int] = None,
785
+ stake_amount: Optional[float] = None,
786
+ coldkey: Optional[str] = None,
787
+ ):
788
+ """Handle adding delegate stake to a specific node."""
789
+ try:
790
+ # STEP 1: Collect parameters
791
+ request = prompt_node_delegate_stake_add(
792
+ subnet_id=subnet_id,
793
+ subnet_node_id=subnet_node_id,
794
+ stake_amount=stake_amount,
795
+ )
796
+
797
+ # STEP 2: Get signing wallet (use coldkey if provided)
798
+ if coldkey:
799
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
800
+
801
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
802
+ else:
803
+ wallet_name, keypair = retrieve_wallet_with_validation(
804
+ wallet_type="coldkey",
805
+ purpose="sign the node delegate stake transaction",
806
+ )
807
+
808
+ # STEP 3: Execute
809
+ client = get_client()
810
+ with HTCLILoadingContext("Adding delegate stake to node..."):
811
+ response = client.extrinsics.staking.add_to_node_delegate_stake(
812
+ request, keypair
813
+ )
814
+
815
+ # Check for errors in result
816
+ if isinstance(response, dict) and not response.get("success", False):
817
+ error_msg = response.get("error", "Node delegate stake add failed")
818
+ if handle_stake_error(
819
+ error_msg,
820
+ request.subnet_id,
821
+ request.subnet_node_id,
822
+ "add node delegate stake",
823
+ client,
824
+ ):
825
+ return
826
+
827
+ # STEP 4: Display
828
+ display_node_delegate_stake_result(response, operation="add")
829
+
830
+ except Exception as e:
831
+ error_msg = str(e)
832
+ subnet_id_for_error = None
833
+ node_id_for_error = None
834
+ client_for_error = None
835
+ try:
836
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
837
+ node_id_for_error = (
838
+ request.subnet_node_id if "request" in locals() else None
839
+ )
840
+ client_for_error = client if "client" in locals() else None
841
+ except (AttributeError, NameError):
842
+ pass
843
+
844
+ if not handle_stake_error(
845
+ error_msg,
846
+ subnet_id_for_error,
847
+ node_id_for_error,
848
+ "add node delegate stake",
849
+ client_for_error,
850
+ ):
851
+ handle_and_display_error(e)
852
+
853
+
854
+ def node_delegate_remove_handler(
855
+ subnet_id: Optional[int] = None,
856
+ subnet_node_id: Optional[int] = None,
857
+ shares_to_remove: Optional[int] = None,
858
+ ):
859
+ """Handle removing delegate stake from a specific node."""
860
+ try:
861
+ # STEP 1: Collect parameters
862
+ request = prompt_node_delegate_stake_remove(
863
+ subnet_id=subnet_id,
864
+ subnet_node_id=subnet_node_id,
865
+ shares_to_remove=shares_to_remove,
866
+ )
867
+
868
+ # STEP 2: Get signing wallet
869
+ wallet_name, keypair = retrieve_wallet_with_validation(
870
+ wallet_type="coldkey", purpose="sign the node delegate unstake transaction"
871
+ )
872
+
873
+ # STEP 3: Execute
874
+ client = get_client()
875
+ with HTCLILoadingContext("Removing delegate stake from node..."):
876
+ response = client.extrinsics.staking.remove_node_delegate_stake(
877
+ request, keypair
878
+ )
879
+
880
+ # Check for errors in result
881
+ if isinstance(response, dict) and not response.get("success", False):
882
+ error_msg = response.get("error", "Node delegate stake remove failed")
883
+ if handle_stake_error(
884
+ error_msg,
885
+ request.subnet_id,
886
+ request.subnet_node_id,
887
+ "remove node delegate stake",
888
+ client,
889
+ ):
890
+ return
891
+
892
+ # STEP 4: Display
893
+ display_node_delegate_stake_result(response, operation="remove")
894
+
895
+ except Exception as e:
896
+ error_msg = str(e)
897
+ subnet_id_for_error = None
898
+ node_id_for_error = None
899
+ client_for_error = None
900
+ try:
901
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
902
+ node_id_for_error = (
903
+ request.subnet_node_id if "request" in locals() else None
904
+ )
905
+ client_for_error = client if "client" in locals() else None
906
+ except (AttributeError, NameError):
907
+ pass
908
+
909
+ if not handle_stake_error(
910
+ error_msg,
911
+ subnet_id_for_error,
912
+ node_id_for_error,
913
+ "remove node delegate stake",
914
+ client_for_error,
915
+ ):
916
+ handle_and_display_error(e)
917
+
918
+
919
+ def node_delegate_swap_handler(
920
+ from_subnet_id: Optional[int] = None,
921
+ from_subnet_node_id: Optional[int] = None,
922
+ to_subnet_id: Optional[int] = None,
923
+ to_subnet_node_id: Optional[int] = None,
924
+ shares_to_swap: Optional[int] = None,
925
+ coldkey: Optional[str] = None,
926
+ ):
927
+ """Handle swapping node delegate shares between nodes."""
928
+ try:
929
+ request = prompt_node_delegate_swap(
930
+ from_subnet_id=from_subnet_id,
931
+ from_subnet_node_id=from_subnet_node_id,
932
+ to_subnet_id=to_subnet_id,
933
+ to_subnet_node_id=to_subnet_node_id,
934
+ shares_to_swap=shares_to_swap,
935
+ )
936
+
937
+ if coldkey:
938
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
939
+
940
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
941
+ else:
942
+ wallet_name, keypair = retrieve_wallet_with_validation(
943
+ wallet_type="coldkey", purpose="sign the node delegate swap transaction"
944
+ )
945
+
946
+ client = get_client()
947
+ with HTCLILoadingContext("Submitting node delegate swap..."):
948
+ response = client.extrinsics.staking.swap_node_delegate_stake(
949
+ request, keypair
950
+ )
951
+
952
+ # Check for errors in result
953
+ if isinstance(response, dict) and not response.get("success", False):
954
+ error_msg = response.get("error", "Node delegate swap failed")
955
+ if handle_stake_error(
956
+ error_msg,
957
+ request.to_subnet_id,
958
+ request.to_subnet_node_id,
959
+ "swap node delegate stake",
960
+ client,
961
+ ):
962
+ return
963
+
964
+ display_node_delegate_swap_result(response)
965
+
966
+ except Exception as e:
967
+ error_msg = str(e)
968
+ subnet_id_for_error = None
969
+ node_id_for_error = None
970
+ client_for_error = None
971
+ try:
972
+ subnet_id_for_error = (
973
+ request.to_subnet_id if "request" in locals() else None
974
+ )
975
+ node_id_for_error = (
976
+ request.to_subnet_node_id if "request" in locals() else None
977
+ )
978
+ client_for_error = client if "client" in locals() else None
979
+ except (AttributeError, NameError):
980
+ pass
981
+
982
+ if not handle_stake_error(
983
+ error_msg,
984
+ subnet_id_for_error,
985
+ node_id_for_error,
986
+ "swap node delegate stake",
987
+ client_for_error,
988
+ ):
989
+ handle_and_display_error(e)
990
+
991
+
992
+ def node_delegate_transfer_handler(
993
+ subnet_id: Optional[int] = None,
994
+ subnet_node_id: Optional[int] = None,
995
+ to_account: Optional[str] = None,
996
+ shares_to_transfer: Optional[int] = None,
997
+ coldkey: Optional[str] = None,
998
+ ):
999
+ """Handle transferring node delegate shares."""
1000
+ try:
1001
+ request = prompt_node_delegate_transfer(
1002
+ subnet_id=subnet_id,
1003
+ subnet_node_id=subnet_node_id,
1004
+ to_account=to_account,
1005
+ shares_to_transfer=shares_to_transfer,
1006
+ )
1007
+
1008
+ if coldkey:
1009
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1010
+
1011
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1012
+ else:
1013
+ wallet_name, keypair = retrieve_wallet_with_validation(
1014
+ wallet_type="coldkey",
1015
+ purpose="sign the node delegate transfer transaction",
1016
+ )
1017
+
1018
+ client = get_client()
1019
+ with HTCLILoadingContext("Transferring node delegate shares..."):
1020
+ response = client.extrinsics.staking.transfer_node_delegate_stake(
1021
+ request, keypair
1022
+ )
1023
+
1024
+ # Check for errors in result
1025
+ if isinstance(response, dict) and not response.get("success", False):
1026
+ error_msg = response.get("error", "Node delegate transfer failed")
1027
+ if handle_stake_error(
1028
+ error_msg,
1029
+ request.subnet_id,
1030
+ request.subnet_node_id,
1031
+ "transfer node delegate stake",
1032
+ client,
1033
+ ):
1034
+ return
1035
+
1036
+ display_node_delegate_transfer_result(response)
1037
+
1038
+ except Exception as e:
1039
+ error_msg = str(e)
1040
+ subnet_id_for_error = None
1041
+ node_id_for_error = None
1042
+ client_for_error = None
1043
+ try:
1044
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
1045
+ node_id_for_error = (
1046
+ request.subnet_node_id if "request" in locals() else None
1047
+ )
1048
+ client_for_error = client if "client" in locals() else None
1049
+ except (AttributeError, NameError):
1050
+ pass
1051
+
1052
+ if not handle_stake_error(
1053
+ error_msg,
1054
+ subnet_id_for_error,
1055
+ node_id_for_error,
1056
+ "transfer node delegate stake",
1057
+ client_for_error,
1058
+ ):
1059
+ handle_and_display_error(e)
1060
+
1061
+
1062
+ def node_delegate_donate_handler(
1063
+ subnet_id: Optional[int] = None,
1064
+ subnet_node_id: Optional[int] = None,
1065
+ amount: Optional[float] = None,
1066
+ coldkey: Optional[str] = None,
1067
+ ):
1068
+ """Handle donating to node delegate stake pool."""
1069
+ try:
1070
+ request = prompt_node_delegate_donate(
1071
+ subnet_id=subnet_id,
1072
+ subnet_node_id=subnet_node_id,
1073
+ amount=amount,
1074
+ )
1075
+
1076
+ if coldkey:
1077
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1078
+
1079
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1080
+ else:
1081
+ wallet_name, keypair = retrieve_wallet_with_validation(
1082
+ wallet_type="coldkey",
1083
+ purpose="sign the node delegate donation transaction",
1084
+ )
1085
+
1086
+ client = get_client()
1087
+ with HTCLILoadingContext("Submitting node delegate donation..."):
1088
+ response = client.extrinsics.staking.donate_node_delegate_stake(
1089
+ request, keypair
1090
+ )
1091
+
1092
+ # Check for errors in result
1093
+ if isinstance(response, dict) and not response.get("success", False):
1094
+ error_msg = response.get("error", "Node delegate donation failed")
1095
+ if handle_stake_error(
1096
+ error_msg,
1097
+ request.subnet_id,
1098
+ request.subnet_node_id,
1099
+ "donate node delegate stake",
1100
+ client,
1101
+ ):
1102
+ return
1103
+
1104
+ display_node_delegate_donate_result(response)
1105
+
1106
+ except Exception as e:
1107
+ error_msg = str(e)
1108
+ subnet_id_for_error = None
1109
+ node_id_for_error = None
1110
+ client_for_error = None
1111
+ try:
1112
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
1113
+ node_id_for_error = (
1114
+ request.subnet_node_id if "request" in locals() else None
1115
+ )
1116
+ client_for_error = client if "client" in locals() else None
1117
+ except (AttributeError, NameError):
1118
+ pass
1119
+
1120
+ if not handle_stake_error(
1121
+ error_msg,
1122
+ subnet_id_for_error,
1123
+ node_id_for_error,
1124
+ "donate node delegate stake",
1125
+ client_for_error,
1126
+ ):
1127
+ handle_and_display_error(e)
1128
+
1129
+
1130
+ def swap_node_to_subnet_handler(
1131
+ from_subnet_id: Optional[int] = None,
1132
+ from_subnet_node_id: Optional[int] = None,
1133
+ to_subnet_id: Optional[int] = None,
1134
+ shares_to_swap: Optional[int] = None,
1135
+ coldkey: Optional[str] = None,
1136
+ ):
1137
+ """Handle swap from node delegate stake to subnet delegate stake."""
1138
+ try:
1139
+ request = prompt_swap_node_to_subnet(
1140
+ from_subnet_id=from_subnet_id,
1141
+ from_subnet_node_id=from_subnet_node_id,
1142
+ to_subnet_id=to_subnet_id,
1143
+ shares_to_swap=shares_to_swap,
1144
+ )
1145
+
1146
+ if coldkey:
1147
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1148
+
1149
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1150
+ else:
1151
+ wallet_name, keypair = retrieve_wallet_with_validation(
1152
+ wallet_type="coldkey", purpose="sign the swap transaction"
1153
+ )
1154
+
1155
+ client = get_client()
1156
+ with HTCLILoadingContext("Submitting node → subnet swap..."):
1157
+ response = client.extrinsics.staking.swap_from_node_to_subnet(
1158
+ request, keypair
1159
+ )
1160
+
1161
+ # Check for errors in result
1162
+ if isinstance(response, dict) and not response.get("success", False):
1163
+ error_msg = response.get("error", "Node to subnet swap failed")
1164
+ if handle_stake_error(
1165
+ error_msg, request.to_subnet_id, None, "swap node to subnet", client
1166
+ ):
1167
+ return
1168
+
1169
+ display_swap_node_to_subnet_result(response)
1170
+
1171
+ except Exception as e:
1172
+ error_msg = str(e)
1173
+ subnet_id_for_error = None
1174
+ client_for_error = None
1175
+ try:
1176
+ subnet_id_for_error = (
1177
+ request.to_subnet_id if "request" in locals() else None
1178
+ )
1179
+ client_for_error = client if "client" in locals() else None
1180
+ except (AttributeError, NameError):
1181
+ pass
1182
+
1183
+ if not handle_stake_error(
1184
+ error_msg,
1185
+ subnet_id_for_error,
1186
+ None,
1187
+ "swap node to subnet",
1188
+ client_for_error,
1189
+ ):
1190
+ handle_and_display_error(e)
1191
+
1192
+
1193
+ def swap_subnet_to_node_handler(
1194
+ from_subnet_id: Optional[int] = None,
1195
+ to_subnet_id: Optional[int] = None,
1196
+ to_subnet_node_id: Optional[int] = None,
1197
+ shares_to_swap: Optional[int] = None,
1198
+ coldkey: Optional[str] = None,
1199
+ ):
1200
+ """Handle swap from subnet delegate stake to node delegate stake."""
1201
+ try:
1202
+ request = prompt_swap_subnet_to_node(
1203
+ from_subnet_id=from_subnet_id,
1204
+ to_subnet_id=to_subnet_id,
1205
+ to_subnet_node_id=to_subnet_node_id,
1206
+ shares_to_swap=shares_to_swap,
1207
+ )
1208
+
1209
+ if coldkey:
1210
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1211
+
1212
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1213
+ else:
1214
+ wallet_name, keypair = retrieve_wallet_with_validation(
1215
+ wallet_type="coldkey", purpose="sign the swap transaction"
1216
+ )
1217
+
1218
+ client = get_client()
1219
+ with HTCLILoadingContext("Submitting subnet → node swap..."):
1220
+ response = client.extrinsics.staking.swap_from_subnet_to_node(
1221
+ request, keypair
1222
+ )
1223
+
1224
+ # Check for errors in result
1225
+ if isinstance(response, dict) and not response.get("success", False):
1226
+ error_msg = response.get("error", "Subnet to node swap failed")
1227
+ if handle_stake_error(
1228
+ error_msg,
1229
+ request.to_subnet_id,
1230
+ request.to_subnet_node_id,
1231
+ "swap subnet to node",
1232
+ client,
1233
+ ):
1234
+ return
1235
+
1236
+ display_swap_subnet_to_node_result(response)
1237
+
1238
+ except Exception as e:
1239
+ error_msg = str(e)
1240
+ subnet_id_for_error = None
1241
+ node_id_for_error = None
1242
+ client_for_error = None
1243
+ try:
1244
+ subnet_id_for_error = (
1245
+ request.to_subnet_id if "request" in locals() else None
1246
+ )
1247
+ node_id_for_error = (
1248
+ request.to_subnet_node_id if "request" in locals() else None
1249
+ )
1250
+ client_for_error = client if "client" in locals() else None
1251
+ except (AttributeError, NameError):
1252
+ pass
1253
+
1254
+ if not handle_stake_error(
1255
+ error_msg,
1256
+ subnet_id_for_error,
1257
+ node_id_for_error,
1258
+ "swap subnet to node",
1259
+ client_for_error,
1260
+ ):
1261
+ handle_and_display_error(e)
1262
+
1263
+
1264
+ def swap_validator_to_subnet_handler(
1265
+ from_validator_id: Optional[int] = None,
1266
+ to_subnet_id: Optional[int] = None,
1267
+ shares_to_swap: Optional[int] = None,
1268
+ coldkey: Optional[str] = None,
1269
+ ):
1270
+ """Handle swap from validator delegate stake to subnet delegate stake."""
1271
+ try:
1272
+ request = prompt_swap_validator_to_subnet(
1273
+ from_validator_id=from_validator_id,
1274
+ to_subnet_id=to_subnet_id,
1275
+ shares_to_swap=shares_to_swap,
1276
+ )
1277
+
1278
+ if coldkey:
1279
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1280
+
1281
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1282
+ else:
1283
+ wallet_name, keypair = retrieve_wallet_with_validation(
1284
+ wallet_type="coldkey",
1285
+ purpose="sign the validator to subnet swap transaction",
1286
+ )
1287
+
1288
+ client = get_client()
1289
+ with HTCLILoadingContext("Submitting validator → subnet swap..."):
1290
+ response = client.extrinsics.staking.swap_from_validator_to_subnet(
1291
+ request, keypair
1292
+ )
1293
+
1294
+ if isinstance(response, dict) and not response.get("success", False):
1295
+ error_msg = response.get("error", "Validator to subnet swap failed")
1296
+ if handle_stake_error(
1297
+ error_msg,
1298
+ request.to_subnet_id,
1299
+ None,
1300
+ "swap validator to subnet",
1301
+ client,
1302
+ ):
1303
+ return
1304
+
1305
+ display_swap_validator_to_subnet_result(response)
1306
+
1307
+ except Exception as e:
1308
+ error_msg = str(e)
1309
+ subnet_id_for_error = None
1310
+ client_for_error = None
1311
+ try:
1312
+ subnet_id_for_error = (
1313
+ request.to_subnet_id if "request" in locals() else None
1314
+ )
1315
+ client_for_error = client if "client" in locals() else None
1316
+ except (AttributeError, NameError):
1317
+ pass
1318
+
1319
+ if not handle_stake_error(
1320
+ error_msg,
1321
+ subnet_id_for_error,
1322
+ None,
1323
+ "swap validator to subnet",
1324
+ client_for_error,
1325
+ ):
1326
+ handle_and_display_error(e)
1327
+
1328
+
1329
+ def swap_subnet_to_validator_handler(
1330
+ from_subnet_id: Optional[int] = None,
1331
+ to_validator_id: Optional[int] = None,
1332
+ shares_to_swap: Optional[int] = None,
1333
+ coldkey: Optional[str] = None,
1334
+ ):
1335
+ """Handle swap from subnet delegate stake to validator delegate stake."""
1336
+ try:
1337
+ request = prompt_swap_subnet_to_validator(
1338
+ from_subnet_id=from_subnet_id,
1339
+ to_validator_id=to_validator_id,
1340
+ shares_to_swap=shares_to_swap,
1341
+ )
1342
+
1343
+ if coldkey:
1344
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1345
+
1346
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1347
+ else:
1348
+ wallet_name, keypair = retrieve_wallet_with_validation(
1349
+ wallet_type="coldkey",
1350
+ purpose="sign the subnet to validator swap transaction",
1351
+ )
1352
+
1353
+ client = get_client()
1354
+ with HTCLILoadingContext("Submitting subnet → validator swap..."):
1355
+ response = client.extrinsics.staking.swap_from_subnet_to_validator(
1356
+ request, keypair
1357
+ )
1358
+
1359
+ if isinstance(response, dict) and not response.get("success", False):
1360
+ error_msg = response.get("error", "Subnet to validator swap failed")
1361
+ if handle_stake_error(
1362
+ error_msg,
1363
+ request.from_subnet_id,
1364
+ None,
1365
+ "swap subnet to validator",
1366
+ client,
1367
+ ):
1368
+ return
1369
+
1370
+ display_swap_subnet_to_validator_result(response)
1371
+
1372
+ except Exception as e:
1373
+ error_msg = str(e)
1374
+ subnet_id_for_error = None
1375
+ client_for_error = None
1376
+ try:
1377
+ subnet_id_for_error = (
1378
+ request.from_subnet_id if "request" in locals() else None
1379
+ )
1380
+ client_for_error = client if "client" in locals() else None
1381
+ except (AttributeError, NameError):
1382
+ pass
1383
+
1384
+ if not handle_stake_error(
1385
+ error_msg,
1386
+ subnet_id_for_error,
1387
+ None,
1388
+ "swap subnet to validator",
1389
+ client_for_error,
1390
+ ):
1391
+ handle_and_display_error(e)
1392
+
1393
+
1394
+ def swap_queue_update_handler(
1395
+ queue_id: Optional[int] = None,
1396
+ new_call_json: Optional[str] = None,
1397
+ coldkey: Optional[str] = None,
1398
+ ):
1399
+ """Handle updating a queued swap entry."""
1400
+ try:
1401
+ request = prompt_swap_queue_update(
1402
+ queue_id=queue_id,
1403
+ new_call_json=new_call_json,
1404
+ )
1405
+
1406
+ if coldkey:
1407
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1408
+
1409
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1410
+ else:
1411
+ wallet_name, keypair = retrieve_wallet_with_validation(
1412
+ wallet_type="coldkey", purpose="sign the swap queue update"
1413
+ )
1414
+
1415
+ client = get_client()
1416
+ with HTCLILoadingContext("Updating swap queue entry..."):
1417
+ response = client.extrinsics.staking.update_swap_queue(request, keypair)
1418
+
1419
+ display_swap_queue_update_result(response)
1420
+
1421
+ except Exception as e:
1422
+ error_msg = str(e)
1423
+ if not handle_stake_error(
1424
+ error_msg,
1425
+ None,
1426
+ None,
1427
+ "swap queue update",
1428
+ client if "client" in locals() else None,
1429
+ ):
1430
+ handle_and_display_error(e)
1431
+
1432
+
1433
+ def delegate_swap_handler(
1434
+ from_subnet_id: Optional[int] = None,
1435
+ from_node_id: Optional[int] = None,
1436
+ to_subnet_id: Optional[int] = None,
1437
+ to_node_id: Optional[int] = None,
1438
+ shares: Optional[int] = None,
1439
+ coldkey: Optional[str] = None,
1440
+ from_validator_id: Optional[int] = None,
1441
+ to_validator_id: Optional[int] = None,
1442
+ ):
1443
+ """Handle swapping delegate stake between subnets, validators, or nodes.
1444
+
1445
+ Routes to validator delegate swap if validator IDs are provided, node delegate
1446
+ swap if node IDs are provided, otherwise subnet delegate swap.
1447
+ """
1448
+ try:
1449
+ from ...ui.prompts import confirm_prompt
1450
+
1451
+ if from_validator_id is not None or to_validator_id is not None:
1452
+ request = prompt_validator_delegate_swap(
1453
+ from_validator_id=from_validator_id,
1454
+ to_validator_id=to_validator_id,
1455
+ shares_to_swap=shares,
1456
+ )
1457
+
1458
+ if coldkey:
1459
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1460
+
1461
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1462
+ else:
1463
+ wallet_name, keypair = retrieve_wallet_with_validation(
1464
+ wallet_type="coldkey",
1465
+ purpose="sign the validator delegate swap transaction",
1466
+ )
1467
+
1468
+ client = get_client()
1469
+ with HTCLILoadingContext("Swapping validator delegate stake..."):
1470
+ response = client.extrinsics.staking.swap_validator_delegate_stake(
1471
+ request, keypair
1472
+ )
1473
+
1474
+ if isinstance(response, dict) and not response.get("success", False):
1475
+ error_msg = response.get("error", "Validator delegate swap failed")
1476
+ if handle_stake_error(
1477
+ error_msg, None, None, "swap validator delegate stake", client
1478
+ ):
1479
+ return
1480
+
1481
+ display_validator_delegate_swap_result(response)
1482
+ return
1483
+
1484
+ # Determine if this is node-level or subnet-level swap
1485
+ is_node_swap = from_node_id is not None or to_node_id is not None
1486
+
1487
+ # If no node IDs provided via CLI, ask if user wants node-level swap
1488
+ if not is_node_swap and from_subnet_id is not None:
1489
+ want_node = confirm_prompt(
1490
+ "Do you want to swap between specific nodes?", default=False
1491
+ )
1492
+ if want_node:
1493
+ is_node_swap = True
1494
+
1495
+ if is_node_swap:
1496
+ # NODE DELEGATE SWAP PATH
1497
+ request = prompt_node_delegate_swap(
1498
+ from_subnet_id=from_subnet_id,
1499
+ from_subnet_node_id=from_node_id,
1500
+ to_subnet_id=to_subnet_id,
1501
+ to_subnet_node_id=to_node_id,
1502
+ shares_to_swap=shares,
1503
+ )
1504
+
1505
+ if coldkey:
1506
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1507
+
1508
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1509
+ else:
1510
+ wallet_name, keypair = retrieve_wallet_with_validation(
1511
+ wallet_type="coldkey",
1512
+ purpose="sign the node delegate swap transaction",
1513
+ )
1514
+
1515
+ client = get_client()
1516
+ with HTCLILoadingContext("Swapping node delegate stake..."):
1517
+ response = client.extrinsics.staking.swap_node_delegate_stake(
1518
+ request, keypair
1519
+ )
1520
+
1521
+ if isinstance(response, dict) and not response.get("success", False):
1522
+ error_msg = response.get("error", "Node delegate swap failed")
1523
+ if handle_stake_error(
1524
+ error_msg,
1525
+ request.to_subnet_id,
1526
+ request.to_subnet_node_id,
1527
+ "swap node delegate stake",
1528
+ client,
1529
+ ):
1530
+ return
1531
+
1532
+ display_node_delegate_swap_result(response)
1533
+ else:
1534
+ # SUBNET DELEGATE SWAP PATH
1535
+ request = prompt_delegate_swap(
1536
+ from_subnet_id=from_subnet_id,
1537
+ to_subnet_id=to_subnet_id,
1538
+ shares=shares,
1539
+ )
1540
+
1541
+ if coldkey:
1542
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1543
+
1544
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1545
+ else:
1546
+ wallet_name, keypair = retrieve_wallet_with_validation(
1547
+ wallet_type="coldkey", purpose="sign the delegate swap transaction"
1548
+ )
1549
+
1550
+ client = get_client()
1551
+ with HTCLILoadingContext("Swapping delegate stake between subnets..."):
1552
+ response = client.extrinsics.staking.swap_delegate_stake(
1553
+ request, keypair
1554
+ )
1555
+
1556
+ if isinstance(response, dict) and not response.get("success", False):
1557
+ error_msg = response.get("error", "Delegate swap failed")
1558
+ if handle_stake_error(
1559
+ error_msg, request.to_subnet_id, None, "swap delegate stake", client
1560
+ ):
1561
+ return
1562
+
1563
+ display_delegate_swap_result(response)
1564
+
1565
+ except Exception as e:
1566
+ error_msg = str(e)
1567
+ subnet_id_for_error = None
1568
+ node_id_for_error = None
1569
+ client_for_error = None
1570
+ try:
1571
+ subnet_id_for_error = (
1572
+ request.to_subnet_id if "request" in locals() else None
1573
+ )
1574
+ node_id_for_error = (
1575
+ getattr(request, "to_subnet_node_id", None)
1576
+ if "request" in locals()
1577
+ else None
1578
+ )
1579
+ client_for_error = client if "client" in locals() else None
1580
+ except (AttributeError, NameError):
1581
+ pass
1582
+
1583
+ if not handle_stake_error(
1584
+ error_msg,
1585
+ subnet_id_for_error,
1586
+ node_id_for_error,
1587
+ "swap delegate stake",
1588
+ client_for_error,
1589
+ ):
1590
+ handle_and_display_error(e)
1591
+
1592
+
1593
+ def delegate_transfer_handler(
1594
+ subnet_id: Optional[int] = None,
1595
+ node_id: Optional[int] = None,
1596
+ to_account: Optional[str] = None,
1597
+ shares: Optional[int] = None,
1598
+ coldkey: Optional[str] = None,
1599
+ validator_id: Optional[int] = None,
1600
+ ):
1601
+ """Handle transferring delegate stake to another account.
1602
+
1603
+ Routes to validator delegate if validator_id is provided, node delegate if
1604
+ node_id is provided, otherwise subnet delegate.
1605
+ """
1606
+ try:
1607
+ from ...ui.prompts import confirm_prompt
1608
+
1609
+ if validator_id is not None:
1610
+ request = prompt_validator_delegate_transfer(
1611
+ validator_id=validator_id,
1612
+ to_account=to_account,
1613
+ shares_to_transfer=shares,
1614
+ )
1615
+
1616
+ if coldkey:
1617
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1618
+
1619
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1620
+ else:
1621
+ wallet_name, keypair = retrieve_wallet_with_validation(
1622
+ wallet_type="coldkey",
1623
+ purpose="sign the validator delegate transfer transaction",
1624
+ )
1625
+
1626
+ client = get_client()
1627
+ with HTCLILoadingContext("Transferring validator delegate stake..."):
1628
+ response = client.extrinsics.staking.transfer_validator_delegate_stake(
1629
+ request, keypair
1630
+ )
1631
+
1632
+ if isinstance(response, dict) and not response.get("success", False):
1633
+ error_msg = response.get("error", "Validator delegate transfer failed")
1634
+ if handle_stake_error(
1635
+ error_msg, None, None, "transfer validator delegate stake", client
1636
+ ):
1637
+ return
1638
+
1639
+ display_validator_delegate_transfer_result(response)
1640
+ return
1641
+
1642
+ # Determine if this is node-level or subnet-level transfer
1643
+ is_node_transfer = node_id is not None
1644
+
1645
+ # If no node_id provided via CLI, ask if user wants node-level transfer
1646
+ if not is_node_transfer and subnet_id is not None:
1647
+ want_node = confirm_prompt(
1648
+ "Do you want to transfer from a specific node?", default=False
1649
+ )
1650
+ if want_node:
1651
+ is_node_transfer = True
1652
+
1653
+ if is_node_transfer:
1654
+ # NODE DELEGATE TRANSFER PATH
1655
+ request = prompt_node_delegate_transfer(
1656
+ subnet_id=subnet_id,
1657
+ subnet_node_id=node_id,
1658
+ to_account=to_account,
1659
+ shares_to_transfer=shares,
1660
+ )
1661
+
1662
+ if coldkey:
1663
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1664
+
1665
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1666
+ else:
1667
+ wallet_name, keypair = retrieve_wallet_with_validation(
1668
+ wallet_type="coldkey",
1669
+ purpose="sign the node delegate transfer transaction",
1670
+ )
1671
+
1672
+ client = get_client()
1673
+ with HTCLILoadingContext("Transferring node delegate stake..."):
1674
+ response = client.extrinsics.staking.transfer_node_delegate_stake(
1675
+ request, keypair
1676
+ )
1677
+
1678
+ if isinstance(response, dict) and not response.get("success", False):
1679
+ error_msg = response.get("error", "Node delegate transfer failed")
1680
+ if handle_stake_error(
1681
+ error_msg,
1682
+ request.subnet_id,
1683
+ request.subnet_node_id,
1684
+ "transfer node delegate stake",
1685
+ client,
1686
+ ):
1687
+ return
1688
+
1689
+ display_node_delegate_transfer_result(response)
1690
+ else:
1691
+ # SUBNET DELEGATE TRANSFER PATH
1692
+ request = prompt_delegate_transfer(
1693
+ subnet_id=subnet_id,
1694
+ to_account=to_account,
1695
+ shares=shares, # For subnet transfer
1696
+ )
1697
+
1698
+ if coldkey:
1699
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1700
+
1701
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1702
+ else:
1703
+ wallet_name, keypair = retrieve_wallet_with_validation(
1704
+ wallet_type="coldkey",
1705
+ purpose="sign the delegate transfer transaction",
1706
+ )
1707
+
1708
+ client = get_client()
1709
+ with HTCLILoadingContext("Transferring delegate stake..."):
1710
+ response = client.extrinsics.staking.transfer_delegate_stake(
1711
+ request, keypair
1712
+ )
1713
+
1714
+ if isinstance(response, dict) and not response.get("success", False):
1715
+ error_msg = response.get("error", "Delegate transfer failed")
1716
+ if handle_stake_error(
1717
+ error_msg,
1718
+ request.subnet_id,
1719
+ None,
1720
+ "transfer delegate stake",
1721
+ client,
1722
+ ):
1723
+ return
1724
+
1725
+ display_delegate_transfer_result(response)
1726
+
1727
+ except Exception as e:
1728
+ error_msg = str(e)
1729
+ subnet_id_for_error = None
1730
+ node_id_for_error = None
1731
+ client_for_error = None
1732
+ try:
1733
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
1734
+ node_id_for_error = (
1735
+ getattr(request, "subnet_node_id", None)
1736
+ if "request" in locals()
1737
+ else None
1738
+ )
1739
+ client_for_error = client if "client" in locals() else None
1740
+ except (AttributeError, NameError):
1741
+ pass
1742
+
1743
+ if not handle_stake_error(
1744
+ error_msg,
1745
+ subnet_id_for_error,
1746
+ node_id_for_error,
1747
+ "transfer delegate stake",
1748
+ client_for_error,
1749
+ ):
1750
+ handle_and_display_error(e)
1751
+
1752
+
1753
+ def delegate_donate_handler(
1754
+ subnet_id: Optional[int] = None,
1755
+ node_id: Optional[int] = None,
1756
+ amount: Optional[float] = None,
1757
+ coldkey: Optional[str] = None,
1758
+ validator_id: Optional[int] = None,
1759
+ ):
1760
+ """Handle donating delegate stake to subnet treasury, validator, or node pool.
1761
+
1762
+ Routes to validator delegate if validator_id is provided, node delegate if
1763
+ node_id is provided, otherwise subnet delegate.
1764
+ """
1765
+ try:
1766
+ from ...ui.prompts import confirm_prompt
1767
+
1768
+ if validator_id is not None:
1769
+ request = prompt_validator_delegate_donate(
1770
+ validator_id=validator_id,
1771
+ amount=amount,
1772
+ )
1773
+
1774
+ if coldkey:
1775
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1776
+
1777
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1778
+ else:
1779
+ wallet_name, keypair = retrieve_wallet_with_validation(
1780
+ wallet_type="coldkey",
1781
+ purpose="sign the validator delegate donation transaction",
1782
+ )
1783
+
1784
+ client = get_client()
1785
+ with HTCLILoadingContext("Donating to validator delegate pool..."):
1786
+ response = client.extrinsics.staking.donate_validator_delegate_stake(
1787
+ request, keypair
1788
+ )
1789
+
1790
+ if isinstance(response, dict) and not response.get("success", False):
1791
+ error_msg = response.get("error", "Validator delegate donation failed")
1792
+ if handle_stake_error(
1793
+ error_msg, None, None, "donate validator delegate stake", client
1794
+ ):
1795
+ return
1796
+
1797
+ display_validator_delegate_donate_result(response)
1798
+ return
1799
+
1800
+ # Determine if this is node-level or subnet-level donation
1801
+ is_node_donation = node_id is not None
1802
+
1803
+ # If no node_id provided via CLI, ask if user wants node-level donation
1804
+ if not is_node_donation and subnet_id is not None:
1805
+ want_node = confirm_prompt(
1806
+ "Do you want to donate to a specific node?", default=False
1807
+ )
1808
+ if want_node:
1809
+ is_node_donation = True
1810
+
1811
+ if is_node_donation:
1812
+ # NODE DELEGATE DONATE PATH
1813
+ request = prompt_node_delegate_donate(
1814
+ subnet_id=subnet_id,
1815
+ subnet_node_id=node_id,
1816
+ amount=amount,
1817
+ )
1818
+
1819
+ if coldkey:
1820
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1821
+
1822
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1823
+ else:
1824
+ wallet_name, keypair = retrieve_wallet_with_validation(
1825
+ wallet_type="coldkey",
1826
+ purpose="sign the node delegate donation transaction",
1827
+ )
1828
+
1829
+ client = get_client()
1830
+ with HTCLILoadingContext("Donating to node delegate pool..."):
1831
+ response = client.extrinsics.staking.donate_node_delegate_stake(
1832
+ request, keypair
1833
+ )
1834
+
1835
+ if isinstance(response, dict) and not response.get("success", False):
1836
+ error_msg = response.get("error", "Node delegate donation failed")
1837
+ if handle_stake_error(
1838
+ error_msg,
1839
+ request.subnet_id,
1840
+ request.subnet_node_id,
1841
+ "donate node delegate stake",
1842
+ client,
1843
+ ):
1844
+ return
1845
+
1846
+ display_node_delegate_donate_result(response)
1847
+ else:
1848
+ # SUBNET DELEGATE DONATE PATH
1849
+ request = prompt_delegate_donate(
1850
+ subnet_id=subnet_id,
1851
+ amount=amount,
1852
+ )
1853
+
1854
+ if coldkey:
1855
+ from ...utils.wallet.core import resolve_coldkey_and_get_keypair
1856
+
1857
+ wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
1858
+ else:
1859
+ wallet_name, keypair = retrieve_wallet_with_validation(
1860
+ wallet_type="coldkey",
1861
+ purpose="sign the delegate donation transaction",
1862
+ )
1863
+
1864
+ client = get_client()
1865
+ with HTCLILoadingContext("Donating delegate stake to subnet treasury..."):
1866
+ response = client.extrinsics.staking.donate_delegate_stake(
1867
+ request, keypair
1868
+ )
1869
+
1870
+ if isinstance(response, dict) and not response.get("success", False):
1871
+ error_msg = response.get("error", "Delegate donation failed")
1872
+ if handle_stake_error(
1873
+ error_msg, request.subnet_id, None, "donate delegate stake", client
1874
+ ):
1875
+ return
1876
+
1877
+ display_delegate_donate_result(response)
1878
+
1879
+ except Exception as e:
1880
+ error_msg = str(e)
1881
+ subnet_id_for_error = None
1882
+ node_id_for_error = None
1883
+ client_for_error = None
1884
+ try:
1885
+ subnet_id_for_error = request.subnet_id if "request" in locals() else None
1886
+ node_id_for_error = (
1887
+ getattr(request, "subnet_node_id", None)
1888
+ if "request" in locals()
1889
+ else None
1890
+ )
1891
+ client_for_error = client if "client" in locals() else None
1892
+ except (AttributeError, NameError):
1893
+ pass
1894
+
1895
+ if not handle_stake_error(
1896
+ error_msg,
1897
+ subnet_id_for_error,
1898
+ node_id_for_error,
1899
+ "donate delegate stake",
1900
+ client_for_error,
1901
+ ):
1902
+ handle_and_display_error(e)