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,359 @@
1
+ """
2
+ Overwatch command prompting logic.
3
+
4
+ Handles user interaction and input validation for overwatch operations.
5
+ Uses HTCLI UI components for proper validation.
6
+ """
7
+
8
+ from typing import Optional, Tuple
9
+
10
+ from ...dependencies import get_client
11
+ from ...ui.colors import info
12
+ from ...ui.display import HTCLIConsole, print_error
13
+ from ...ui.prompts import HTCLIPrompt, amount_prompt
14
+ from ...utils.blockchain.peer_id import validate_peer_id_format
15
+ from ...utils.wallet.crypto import format_address_display
16
+
17
+ console = HTCLIConsole()
18
+ prompt = HTCLIPrompt()
19
+
20
+
21
+ def prompt_overwatch_node_id() -> int:
22
+ """Prompt for overwatch node ID."""
23
+ return prompt.integer_prompt(
24
+ "Enter the Overwatch Node ID",
25
+ min_value=1,
26
+ )
27
+
28
+
29
+ def prompt_overwatch_register(
30
+ hotkey: Optional[str] = None,
31
+ stake_amount: Optional[float] = None,
32
+ coldkey_name: Optional[str] = None,
33
+ ) -> Tuple[str, int]:
34
+ """
35
+ Collect overwatch node registration parameters from user.
36
+
37
+ Returns:
38
+ Tuple of (hotkey_address, stake_amount_in_wei)
39
+ """
40
+ console.print(info("Overwatch Node Registration"))
41
+ console.print("Register a new overwatch node on the network.\n")
42
+
43
+ client = get_client()
44
+
45
+ # Get coldkey address if provided (for hotkey disambiguation)
46
+ coldkey_address = None
47
+ if coldkey_name:
48
+ try:
49
+ from ...utils.wallet.crypto import get_wallet_info_by_name
50
+ coldkey_info = get_wallet_info_by_name(coldkey_name, is_hotkey=False)
51
+ coldkey_address = coldkey_info.get("evm_address") or coldkey_info.get("ss58_address") or coldkey_info.get("address")
52
+ except Exception:
53
+ # Coldkey not found yet, will be resolved later - that's okay
54
+ pass
55
+
56
+ # Get hotkey - try to resolve first with coldkey context
57
+ if hotkey is not None:
58
+ try:
59
+ hotkey = client.offchain.wallet.resolve_hotkey_address(
60
+ hotkey,
61
+ owner_coldkey_name=coldkey_name,
62
+ owner_address=coldkey_address,
63
+ )
64
+ console.print(f"[htcli.success]✅ Using hotkey: {format_address_display(hotkey, truncate=False)}[/]")
65
+ except (ValueError, Exception) as e:
66
+ print_error(f"Invalid hotkey: {str(e)}")
67
+ hotkey = None
68
+
69
+ # Get hotkey if not provided or resolution failed
70
+ if hotkey is None:
71
+ from ...utils import retrieve_wallet_with_validation
72
+ from ...utils.wallet.crypto import get_wallet_info_by_name
73
+
74
+ console.print(info("\nHotkey Selection (used for overwatch node):"))
75
+ hotkey_wallet, hotkey_keypair = retrieve_wallet_with_validation(
76
+ wallet_type="hotkey", purpose="register the overwatch node"
77
+ )
78
+ # Get wallet info to retrieve the EVM address with coldkey context
79
+ hotkey_wallet_info = get_wallet_info_by_name(
80
+ hotkey_wallet,
81
+ is_hotkey=True,
82
+ owner_coldkey_name=coldkey_name,
83
+ owner_address=coldkey_address,
84
+ )
85
+ hotkey = (
86
+ hotkey_wallet_info.get("evm_address")
87
+ or hotkey_wallet_info.get("address")
88
+ or hotkey_wallet_info.get("ss58_address")
89
+ )
90
+ if not hotkey:
91
+ raise ValueError(f"Could not determine address for hotkey wallet '{hotkey_wallet}'")
92
+ console.print(f"[htcli.success]✅ Using hotkey: {format_address_display(hotkey, truncate=False)}[/]")
93
+
94
+ # Get stake amount - query blockchain for actual minimum
95
+ if stake_amount is None:
96
+ # Query the actual minimum stake balance from the blockchain
97
+ min_stake_wei = 0
98
+ try:
99
+ min_stake_result = client.substrate.query(
100
+ module="Network", storage_function="OverwatchMinStakeBalance"
101
+ )
102
+ min_stake_wei = min_stake_result.value if min_stake_result and min_stake_result.value else 0
103
+ except Exception:
104
+ # Fallback to a reasonable default if query fails
105
+ min_stake_wei = 0
106
+
107
+ # Convert wei to TENSOR for the prompt
108
+ # Default fallback is 100 TENSOR (matches blockchain's DefaultOverwatchMinStakeBalance)
109
+ min_stake_tensor = min_stake_wei / 1e18 if min_stake_wei > 0 else 100.0
110
+
111
+ console.print(f"[htcli.info]ℹ️ Minimum stake required: {min_stake_tensor:,.2f}[/]")
112
+
113
+ stake_amount = amount_prompt(
114
+ "Enter initial stake amount",
115
+ currency="TENSOR",
116
+ min_amount=min_stake_tensor,
117
+ )
118
+
119
+ # Convert TENSOR to WEI
120
+ stake_amount_wei = int(stake_amount * 1e18)
121
+
122
+ return hotkey, stake_amount_wei
123
+
124
+
125
+ def prompt_overwatch_remove() -> int:
126
+ """
127
+ Collect overwatch node removal parameters from user.
128
+
129
+ Returns:
130
+ Overwatch node ID to remove
131
+ """
132
+ console.print(info("Overwatch Node Removal"))
133
+ console.print("Remove your overwatch node from the network.\n")
134
+
135
+ return prompt.integer_prompt(
136
+ "Enter the Overwatch Node ID to remove",
137
+ min_value=1,
138
+ )
139
+
140
+
141
+ def prompt_overwatch_peer_id(
142
+ subnet_id: Optional[int] = None,
143
+ overwatch_node_id: Optional[int] = None,
144
+ peer_id: Optional[str] = None,
145
+ ) -> Tuple[int, int, str]:
146
+ """
147
+ Collect parameters for setting overwatch node peer ID.
148
+
149
+ Returns:
150
+ Tuple of (subnet_id, overwatch_node_id, peer_id)
151
+ """
152
+ console.print(info("Set Overwatch Peer ID"))
153
+ console.print("Set your peer ID for a specific subnet.\n")
154
+
155
+ # Get subnet ID
156
+ if subnet_id is None:
157
+ subnet_id = prompt.integer_prompt(
158
+ "Enter the Subnet ID",
159
+ min_value=0,
160
+ )
161
+
162
+ # Get overwatch node ID
163
+ if overwatch_node_id is None:
164
+ overwatch_node_id = prompt.integer_prompt(
165
+ "Enter your Overwatch Node ID",
166
+ min_value=1,
167
+ )
168
+
169
+ # Validate peer_id if provided
170
+ if peer_id is not None:
171
+ if not peer_id or not isinstance(peer_id, str):
172
+ print_error("Invalid peer ID: must be a non-empty string")
173
+ peer_id = None
174
+ elif not validate_peer_id_format(peer_id):
175
+ print_error(
176
+ "Invalid peer ID format: must be a valid base58-encoded multihash "
177
+ "(e.g., starting with 'Qm', '12D3KooW', or '1')"
178
+ )
179
+ peer_id = None
180
+
181
+ # Get peer ID
182
+ if peer_id is None:
183
+ peer_id = prompt.text_prompt(
184
+ "Enter the Peer ID for this subnet",
185
+ validator=lambda x: (
186
+ True
187
+ if validate_peer_id_format(x)
188
+ else "Invalid peer ID format. Must be a valid base58-encoded multihash."
189
+ ),
190
+ )
191
+
192
+ return subnet_id, overwatch_node_id, peer_id
193
+
194
+
195
+ def prompt_overwatch_add_stake(
196
+ overwatch_node_id: Optional[int] = None,
197
+ hotkey: Optional[str] = None,
198
+ stake_amount: Optional[float] = None,
199
+ coldkey_name: Optional[str] = None,
200
+ ) -> Tuple[int, str, int]:
201
+ """
202
+ Collect parameters for adding stake to an overwatch node.
203
+
204
+ Returns:
205
+ Tuple of (overwatch_node_id, hotkey_address, stake_amount_in_wei)
206
+ """
207
+ console.print(info("Add Overwatch Stake"))
208
+ console.print("Add stake to your overwatch node.\n")
209
+
210
+ client = get_client()
211
+
212
+ # Get overwatch node ID
213
+ if overwatch_node_id is None:
214
+ overwatch_node_id = prompt.integer_prompt(
215
+ "Enter the Overwatch Node ID",
216
+ min_value=1,
217
+ )
218
+
219
+ # Get coldkey address if provided (for hotkey disambiguation)
220
+ coldkey_address = None
221
+ if coldkey_name:
222
+ try:
223
+ from ...utils.wallet.crypto import get_wallet_info_by_name
224
+ coldkey_info = get_wallet_info_by_name(coldkey_name, is_hotkey=False)
225
+ coldkey_address = coldkey_info.get("evm_address") or coldkey_info.get("ss58_address") or coldkey_info.get("address")
226
+ except Exception:
227
+ # Coldkey not found yet, will be resolved later - that's okay
228
+ pass
229
+
230
+ # Get hotkey - try to resolve first with coldkey context
231
+ if hotkey is not None:
232
+ try:
233
+ hotkey = client.offchain.wallet.resolve_hotkey_address(
234
+ hotkey,
235
+ owner_coldkey_name=coldkey_name,
236
+ owner_address=coldkey_address,
237
+ )
238
+ console.print(f"[htcli.success]✅ Using hotkey: {format_address_display(hotkey, truncate=False)}[/]")
239
+ except (ValueError, Exception) as e:
240
+ print_error(f"Invalid hotkey: {str(e)}")
241
+ hotkey = None
242
+
243
+ # Get hotkey if not provided
244
+ if hotkey is None:
245
+ from ...utils import retrieve_wallet_with_validation
246
+ from ...utils.wallet.crypto import get_wallet_info_by_name
247
+
248
+ console.print(info("\nHotkey Selection (must match overwatch node):"))
249
+ hotkey_wallet, hotkey_keypair = retrieve_wallet_with_validation(
250
+ wallet_type="hotkey", purpose="identify the overwatch node"
251
+ )
252
+ # Get wallet info to retrieve the EVM address with coldkey context
253
+ hotkey_wallet_info = get_wallet_info_by_name(
254
+ hotkey_wallet,
255
+ is_hotkey=True,
256
+ owner_coldkey_name=coldkey_name,
257
+ owner_address=coldkey_address,
258
+ )
259
+ hotkey = (
260
+ hotkey_wallet_info.get("evm_address")
261
+ or hotkey_wallet_info.get("address")
262
+ or hotkey_wallet_info.get("ss58_address")
263
+ )
264
+ if not hotkey:
265
+ raise ValueError(f"Could not determine address for hotkey wallet '{hotkey_wallet}'")
266
+ console.print(f"[htcli.success]✅ Using hotkey: {format_address_display(hotkey, truncate=False)}[/]")
267
+
268
+ # Get stake amount
269
+ if stake_amount is None:
270
+ stake_amount = amount_prompt(
271
+ "Enter stake amount to add",
272
+ currency="TENSOR",
273
+ min_amount=0.001, # Very small minimum
274
+ )
275
+
276
+ # Convert TENSOR to WEI
277
+ stake_amount_wei = int(stake_amount * 1e18)
278
+
279
+ return overwatch_node_id, hotkey, stake_amount_wei
280
+
281
+
282
+ def prompt_overwatch_remove_stake(
283
+ hotkey: Optional[str] = None,
284
+ stake_amount: Optional[float] = None,
285
+ coldkey_name: Optional[str] = None,
286
+ ) -> Tuple[str, int]:
287
+ """
288
+ Collect parameters for removing stake from an overwatch node.
289
+
290
+ Returns:
291
+ Tuple of (hotkey_address, stake_amount_in_wei)
292
+ """
293
+ console.print(info("Remove Overwatch Stake"))
294
+ console.print("Remove stake from your overwatch node.\n")
295
+ console.print("[htcli.warning]⚠️ Removed stake will enter unbonding period before becoming available.[/]\n")
296
+
297
+ client = get_client()
298
+
299
+ # Get coldkey address if provided (for hotkey disambiguation)
300
+ coldkey_address = None
301
+ if coldkey_name:
302
+ try:
303
+ from ...utils.wallet.crypto import get_wallet_info_by_name
304
+ coldkey_info = get_wallet_info_by_name(coldkey_name, is_hotkey=False)
305
+ coldkey_address = coldkey_info.get("evm_address") or coldkey_info.get("ss58_address") or coldkey_info.get("address")
306
+ except Exception:
307
+ # Coldkey not found yet, will be resolved later - that's okay
308
+ pass
309
+
310
+ # Get hotkey - try to resolve first with coldkey context
311
+ if hotkey is not None:
312
+ try:
313
+ hotkey = client.offchain.wallet.resolve_hotkey_address(
314
+ hotkey,
315
+ owner_coldkey_name=coldkey_name,
316
+ owner_address=coldkey_address,
317
+ )
318
+ console.print(f"[htcli.success]✅ Using hotkey: {format_address_display(hotkey, truncate=False)}[/]")
319
+ except (ValueError, Exception) as e:
320
+ print_error(f"Invalid hotkey: {str(e)}")
321
+ hotkey = None
322
+
323
+ # Get hotkey if not provided
324
+ if hotkey is None:
325
+ from ...utils import retrieve_wallet_with_validation
326
+ from ...utils.wallet.crypto import get_wallet_info_by_name
327
+
328
+ console.print(info("\nHotkey Selection (must match overwatch node):"))
329
+ hotkey_wallet, hotkey_keypair = retrieve_wallet_with_validation(
330
+ wallet_type="hotkey", purpose="identify the overwatch node"
331
+ )
332
+ # Get wallet info to retrieve the EVM address with coldkey context
333
+ hotkey_wallet_info = get_wallet_info_by_name(
334
+ hotkey_wallet,
335
+ is_hotkey=True,
336
+ owner_coldkey_name=coldkey_name,
337
+ owner_address=coldkey_address,
338
+ )
339
+ hotkey = (
340
+ hotkey_wallet_info.get("evm_address")
341
+ or hotkey_wallet_info.get("address")
342
+ or hotkey_wallet_info.get("ss58_address")
343
+ )
344
+ if not hotkey:
345
+ raise ValueError(f"Could not determine address for hotkey wallet '{hotkey_wallet}'")
346
+ console.print(f"[htcli.success]✅ Using hotkey: {format_address_display(hotkey, truncate=False)}[/]")
347
+
348
+ # Get stake amount
349
+ if stake_amount is None:
350
+ stake_amount = amount_prompt(
351
+ "Enter stake amount to remove",
352
+ currency="TENSOR",
353
+ min_amount=0.001, # Very small minimum
354
+ )
355
+
356
+ # Convert TENSOR to WEI
357
+ stake_amount_wei = int(stake_amount * 1e18)
358
+
359
+ return hotkey, stake_amount_wei