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,401 @@
1
+ """
2
+ Overwatch node extrinsics for HTCLI.
3
+
4
+ Provides extrinsic operations for overwatch nodes including registration,
5
+ removal, staking, and peer ID management.
6
+ """
7
+
8
+ from typing import Any, Optional
9
+
10
+ from substrateinterface import Keypair, SubstrateInterface
11
+
12
+ from ...utils.logging import get_logger
13
+ from .base import BaseExtrinsicClient, get_user_friendly_error
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ class OverwatchExtrinsics(BaseExtrinsicClient):
19
+ """Client for overwatch node-related extrinsics."""
20
+
21
+ def __init__(self, substrate: Optional[SubstrateInterface] = None):
22
+ """Initialize the overwatch client."""
23
+ super().__init__(substrate)
24
+
25
+ # ============================================================================
26
+ # NODE REGISTRATION & REMOVAL
27
+ # ============================================================================
28
+
29
+ def register_overwatch_node(
30
+ self, hotkey: str, stake_to_be_added: int, keypair: Keypair
31
+ ) -> dict[str, Any]:
32
+ """
33
+ Register a new overwatch node.
34
+
35
+ Args:
36
+ hotkey: The hotkey account ID for the overwatch node
37
+ stake_to_be_added: Initial stake amount in wei
38
+ keypair: Coldkey keypair for signing the transaction
39
+
40
+ Returns:
41
+ Dictionary with operation result
42
+ """
43
+ try:
44
+ logger.info(
45
+ f"Registering overwatch node with hotkey {hotkey[:16]}... "
46
+ f"and stake {stake_to_be_added / 1e18:.4f} TENSOR"
47
+ )
48
+
49
+ call = self.substrate.compose_call(
50
+ call_module="Network",
51
+ call_function="register_overwatch_node",
52
+ call_params={
53
+ "hotkey": hotkey,
54
+ "stake_to_be_added": stake_to_be_added,
55
+ },
56
+ )
57
+
58
+ result = self._submit_extrinsic(call, keypair)
59
+
60
+ if result["success"]:
61
+ # Query the latest TotalOverwatchNodeUids to get the newly assigned ID
62
+ # Note: The registration code doesn't emit an event with the ID,
63
+ # so we query storage directly. The new node got the latest UID.
64
+ overwatch_node_id = None
65
+ try:
66
+ total_uids_result = self.substrate.query(
67
+ module="Network",
68
+ storage_function="TotalOverwatchNodeUids",
69
+ )
70
+ if total_uids_result and total_uids_result.value:
71
+ overwatch_node_id = total_uids_result.value
72
+ except Exception as query_error:
73
+ logger.warning(f"Could not query overwatch node ID: {query_error}")
74
+
75
+ return {
76
+ "success": True,
77
+ "transaction_hash": result.get("extrinsic_hash"),
78
+ "block_hash": result.get("block_hash"),
79
+ "block_number": result.get("block_number"),
80
+ "overwatch_node_id": overwatch_node_id,
81
+ "hotkey": hotkey,
82
+ "stake_added": stake_to_be_added,
83
+ "message": f"Successfully registered overwatch node with {stake_to_be_added / 1e18:.4f} TENSOR stake",
84
+ }
85
+
86
+ else:
87
+ error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
88
+ return {
89
+ "success": False,
90
+ "error": error_msg,
91
+ "message": f"Failed to register overwatch node: {error_msg}",
92
+ }
93
+
94
+ except Exception as e:
95
+ logger.error(f"Error registering overwatch node: {e}")
96
+ return {
97
+ "success": False,
98
+ "error": str(e),
99
+ "message": f"Error registering overwatch node: {str(e)}",
100
+ }
101
+
102
+ def remove_overwatch_node(
103
+ self, overwatch_node_id: int, keypair: Keypair
104
+ ) -> dict[str, Any]:
105
+ """
106
+ Remove an overwatch node (owner only).
107
+
108
+ Args:
109
+ overwatch_node_id: The overwatch node ID to remove
110
+ keypair: Coldkey keypair for signing (must be node owner)
111
+
112
+ Returns:
113
+ Dictionary with operation result
114
+ """
115
+ try:
116
+ logger.info(f"Removing overwatch node {overwatch_node_id}")
117
+
118
+ call = self.substrate.compose_call(
119
+ call_module="Network",
120
+ call_function="remove_overwatch_node",
121
+ call_params={
122
+ "overwatch_node_id": overwatch_node_id,
123
+ },
124
+ )
125
+
126
+ result = self._submit_extrinsic(call, keypair)
127
+
128
+ if result["success"]:
129
+ return {
130
+ "success": True,
131
+ "transaction_hash": result.get("extrinsic_hash"),
132
+ "block_hash": result.get("block_hash"),
133
+ "block_number": result.get("block_number"),
134
+ "overwatch_node_id": overwatch_node_id,
135
+ "message": f"Successfully removed overwatch node {overwatch_node_id}",
136
+ }
137
+ else:
138
+ error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
139
+ return {
140
+ "success": False,
141
+ "error": error_msg,
142
+ "message": f"Failed to remove overwatch node: {error_msg}",
143
+ }
144
+
145
+ except Exception as e:
146
+ logger.error(f"Error removing overwatch node: {e}")
147
+ return {
148
+ "success": False,
149
+ "error": str(e),
150
+ "message": f"Error removing overwatch node: {str(e)}",
151
+ }
152
+
153
+ def anyone_remove_overwatch_node(
154
+ self, overwatch_node_id: int, keypair: Keypair
155
+ ) -> dict[str, Any]:
156
+ """
157
+ Remove an unqualified overwatch node (anyone can call).
158
+
159
+ This can only be called if the overwatch node is no longer qualified.
160
+
161
+ Args:
162
+ overwatch_node_id: The overwatch node ID to remove
163
+ keypair: Any keypair for signing the transaction
164
+
165
+ Returns:
166
+ Dictionary with operation result
167
+ """
168
+ try:
169
+ logger.info(f"Attempting to remove unqualified overwatch node {overwatch_node_id}")
170
+
171
+ call = self.substrate.compose_call(
172
+ call_module="Network",
173
+ call_function="anyone_remove_overwatch_node",
174
+ call_params={
175
+ "overwatch_node_id": overwatch_node_id,
176
+ },
177
+ )
178
+
179
+ result = self._submit_extrinsic(call, keypair)
180
+
181
+ if result["success"]:
182
+ return {
183
+ "success": True,
184
+ "transaction_hash": result.get("extrinsic_hash"),
185
+ "block_hash": result.get("block_hash"),
186
+ "block_number": result.get("block_number"),
187
+ "overwatch_node_id": overwatch_node_id,
188
+ "message": f"Successfully removed unqualified overwatch node {overwatch_node_id}",
189
+ }
190
+ else:
191
+ error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
192
+ return {
193
+ "success": False,
194
+ "error": error_msg,
195
+ "message": f"Failed to remove overwatch node: {error_msg}",
196
+ }
197
+
198
+ except Exception as e:
199
+ logger.error(f"Error removing overwatch node: {e}")
200
+ return {
201
+ "success": False,
202
+ "error": str(e),
203
+ "message": f"Error removing overwatch node: {str(e)}",
204
+ }
205
+
206
+ # ============================================================================
207
+ # PEER ID MANAGEMENT
208
+ # ============================================================================
209
+
210
+ def set_overwatch_node_peer_id(
211
+ self,
212
+ subnet_id: int,
213
+ overwatch_node_id: int,
214
+ peer_id: str,
215
+ keypair: Keypair,
216
+ ) -> dict[str, Any]:
217
+ """
218
+ Set the peer ID for an overwatch node on a specific subnet.
219
+
220
+ Args:
221
+ subnet_id: The subnet ID
222
+ overwatch_node_id: The overwatch node ID
223
+ peer_id: The peer ID to set
224
+ keypair: Keypair for signing (must be node owner)
225
+
226
+ Returns:
227
+ Dictionary with operation result
228
+ """
229
+ try:
230
+ logger.info(
231
+ f"Setting peer ID for overwatch node {overwatch_node_id} "
232
+ f"on subnet {subnet_id}"
233
+ )
234
+
235
+ # Pass peer_id as string - substrate-interface handles encoding
236
+ # (Same pattern as node.py for subnet node peer IDs)
237
+ call = self.substrate.compose_call(
238
+ call_module="Network",
239
+ call_function="set_overwatch_node_peer_id",
240
+ call_params={
241
+ "subnet_id": subnet_id,
242
+ "overwatch_node_id": overwatch_node_id,
243
+ "peer_id": peer_id, # String, not encoded bytes
244
+ },
245
+ )
246
+
247
+ result = self._submit_extrinsic(call, keypair)
248
+
249
+ if result["success"]:
250
+ return {
251
+ "success": True,
252
+ "transaction_hash": result.get("extrinsic_hash"),
253
+ "block_hash": result.get("block_hash"),
254
+ "block_number": result.get("block_number"),
255
+ "subnet_id": subnet_id,
256
+ "overwatch_node_id": overwatch_node_id,
257
+ "peer_id": peer_id,
258
+ "message": f"Successfully set peer ID for overwatch node {overwatch_node_id} on subnet {subnet_id}",
259
+ }
260
+ else:
261
+ error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
262
+ return {
263
+ "success": False,
264
+ "error": error_msg,
265
+ "message": f"Failed to set peer ID: {error_msg}",
266
+ }
267
+
268
+ except Exception as e:
269
+ logger.error(f"Error setting overwatch node peer ID: {e}")
270
+ return {
271
+ "success": False,
272
+ "error": str(e),
273
+ "message": f"Error setting peer ID: {str(e)}",
274
+ }
275
+
276
+ # ============================================================================
277
+ # STAKING
278
+ # ============================================================================
279
+
280
+ def add_to_overwatch_stake(
281
+ self,
282
+ overwatch_node_id: int,
283
+ hotkey: str,
284
+ stake_to_be_added: int,
285
+ keypair: Keypair,
286
+ ) -> dict[str, Any]:
287
+ """
288
+ Add stake to an overwatch node.
289
+
290
+ Args:
291
+ overwatch_node_id: The overwatch node ID
292
+ hotkey: The hotkey of the overwatch node
293
+ stake_to_be_added: Amount to stake in wei
294
+ keypair: Coldkey keypair for signing (must own the hotkey)
295
+
296
+ Returns:
297
+ Dictionary with operation result
298
+ """
299
+ try:
300
+ logger.info(
301
+ f"Adding {stake_to_be_added / 1e18:.4f} TENSOR stake "
302
+ f"to overwatch node {overwatch_node_id}"
303
+ )
304
+
305
+ call = self.substrate.compose_call(
306
+ call_module="Network",
307
+ call_function="add_to_overwatch_stake",
308
+ call_params={
309
+ "overwatch_node_id": overwatch_node_id,
310
+ "hotkey": hotkey,
311
+ "stake_to_be_added": stake_to_be_added,
312
+ },
313
+ )
314
+
315
+ result = self._submit_extrinsic(call, keypair)
316
+
317
+ if result["success"]:
318
+ return {
319
+ "success": True,
320
+ "transaction_hash": result.get("extrinsic_hash"),
321
+ "block_hash": result.get("block_hash"),
322
+ "block_number": result.get("block_number"),
323
+ "overwatch_node_id": overwatch_node_id,
324
+ "hotkey": hotkey,
325
+ "stake_added": stake_to_be_added,
326
+ "message": f"Successfully added {stake_to_be_added / 1e18:.4f} TENSOR to overwatch stake",
327
+ }
328
+ else:
329
+ error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
330
+ return {
331
+ "success": False,
332
+ "error": error_msg,
333
+ "message": f"Failed to add overwatch stake: {error_msg}",
334
+ }
335
+
336
+ except Exception as e:
337
+ logger.error(f"Error adding overwatch stake: {e}")
338
+ return {
339
+ "success": False,
340
+ "error": str(e),
341
+ "message": f"Error adding overwatch stake: {str(e)}",
342
+ }
343
+
344
+ def remove_overwatch_stake(
345
+ self, hotkey: str, stake_to_be_removed: int, keypair: Keypair
346
+ ) -> dict[str, Any]:
347
+ """
348
+ Remove stake from an overwatch node.
349
+
350
+ Creates an unbonding entry that will be claimable after the unbonding period.
351
+
352
+ Args:
353
+ hotkey: The hotkey of the overwatch node
354
+ stake_to_be_removed: Amount to unstake in wei
355
+ keypair: Coldkey keypair for signing (must own the hotkey)
356
+
357
+ Returns:
358
+ Dictionary with operation result
359
+ """
360
+ try:
361
+ logger.info(
362
+ f"Removing {stake_to_be_removed / 1e18:.4f} TENSOR stake "
363
+ f"from overwatch node with hotkey {hotkey[:16]}..."
364
+ )
365
+
366
+ call = self.substrate.compose_call(
367
+ call_module="Network",
368
+ call_function="remove_overwatch_stake",
369
+ call_params={
370
+ "hotkey": hotkey,
371
+ "stake_to_be_removed": stake_to_be_removed,
372
+ },
373
+ )
374
+
375
+ result = self._submit_extrinsic(call, keypair)
376
+
377
+ if result["success"]:
378
+ return {
379
+ "success": True,
380
+ "transaction_hash": result.get("extrinsic_hash"),
381
+ "block_hash": result.get("block_hash"),
382
+ "block_number": result.get("block_number"),
383
+ "hotkey": hotkey,
384
+ "stake_removed": stake_to_be_removed,
385
+ "message": f"Successfully removed {stake_to_be_removed / 1e18:.4f} TENSOR from overwatch stake (will unbond after waiting period)",
386
+ }
387
+ else:
388
+ error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
389
+ return {
390
+ "success": False,
391
+ "error": error_msg,
392
+ "message": f"Failed to remove overwatch stake: {error_msg}",
393
+ }
394
+
395
+ except Exception as e:
396
+ logger.error(f"Error removing overwatch stake: {e}")
397
+ return {
398
+ "success": False,
399
+ "error": str(e),
400
+ "message": f"Error removing overwatch stake: {str(e)}",
401
+ }