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,396 @@
1
+ """
2
+ Overwatch display functions for RPC-based operations and extrinsic operations.
3
+ """
4
+
5
+ from typing import Any, List, Optional
6
+
7
+ from ...ui.colors import amount, info, success
8
+ from ...ui.components import HTCLIPanel, HTCLITable
9
+ from ...ui.display import HTCLIConsole
10
+
11
+ console = HTCLIConsole()
12
+
13
+
14
+ def format_balance(balance: int) -> str:
15
+ """Convert raw balance (wei) to TENSOR."""
16
+ return f"{balance / 1e18:,.4f}"
17
+
18
+
19
+ def format_peer_id(peer_id) -> str:
20
+ """Format peer ID - show only if not empty."""
21
+ if not peer_id:
22
+ return "[dim]Not set[/dim]"
23
+ if isinstance(peer_id, bytes):
24
+ if len(peer_id) == 0:
25
+ return "[dim]Not set[/dim]"
26
+ try:
27
+ decoded = peer_id.decode('utf-8', errors='ignore')
28
+ return decoded if decoded else "[dim]Empty[/dim]"
29
+ except Exception:
30
+ return "[dim]Invalid[/dim]"
31
+ if isinstance(peer_id, str):
32
+ return peer_id if peer_id else "[dim]Not set[/dim]"
33
+ if isinstance(peer_id, list):
34
+ if len(peer_id) == 0:
35
+ return "[dim]Not set[/dim]"
36
+ try:
37
+ return format_peer_id(bytes(peer_id))
38
+ except Exception:
39
+ return "[dim]Invalid[/dim]"
40
+ return str(peer_id)
41
+
42
+
43
+ # ============================================================================
44
+ # RPC DISPLAY FUNCTIONS - Read Operations
45
+ # ============================================================================
46
+
47
+
48
+ def display_overwatch_qualification(data: dict):
49
+ """Display overwatch qualification check results.
50
+
51
+ Shows a detailed breakdown of requirements met and failed,
52
+ similar to subnet check-activation display.
53
+ """
54
+ can_register = data.get("can_register", False)
55
+ requirements_met = data.get("requirements_met", [])
56
+ requirements_failed = data.get("requirements_failed", [])
57
+ details = data.get("details", {})
58
+ coldkey = data.get("coldkey", "Unknown")
59
+
60
+ # Truncate coldkey for display
61
+ # Use centralized formatter for consistency
62
+ from ...utils.wallet.crypto import format_address_display
63
+ coldkey_display = format_address_display(coldkey)
64
+
65
+ # Build content
66
+ if can_register:
67
+ status_line = "[htcli.success]✅ QUALIFIED - Can register as overwatch node[/htcli.success]"
68
+ border_style = "htcli.success"
69
+ title_emoji = "✅"
70
+ else:
71
+ status_line = "[htcli.error]❌ NOT QUALIFIED - Cannot register as overwatch node yet[/htcli.error]"
72
+ border_style = "htcli.error"
73
+ title_emoji = "❌"
74
+
75
+ content = f"""{status_line}
76
+
77
+ [htcli.accent]Coldkey:[/htcli.accent] [htcli.address]{coldkey_display}[/htcli.address]
78
+
79
+ """
80
+
81
+ # Requirements Met section
82
+ if requirements_met:
83
+ content += "[htcli.success]✅ Requirements Met:[/htcli.success]\n"
84
+ for req in requirements_met:
85
+ content += f" [htcli.success]•[/htcli.success] {req}\n"
86
+ content += "\n"
87
+
88
+ # Requirements Failed section
89
+ if requirements_failed:
90
+ content += "[htcli.error]❌ Requirements Not Met:[/htcli.error]\n"
91
+ for req in requirements_failed:
92
+ content += f" [htcli.error]•[/htcli.error] {req}\n"
93
+ content += "\n"
94
+
95
+ # Show network status details
96
+ content += "[htcli.accent]Network Status:[/htcli.accent]\n"
97
+ content += f" • Overwatch Epoch: {details.get('current_overwatch_epoch', 'N/A')}\n"
98
+ content += f" • Current Epoch: {details.get('current_epoch', 'N/A')}\n"
99
+ content += f" • Overwatch Nodes: {details.get('total_overwatch_nodes', 'N/A')}/{details.get('max_overwatch_nodes', 'N/A')}\n"
100
+ content += f" • Active Subnets: {details.get('total_active_subnets', 'N/A')}\n"
101
+ content += "\n"
102
+
103
+ # Show your status
104
+ content += "[htcli.accent]Your Status:[/htcli.accent]\n"
105
+ content += f" • Subnets with Nodes: {details.get('coldkey_subnets', 0)}\n"
106
+ content += f" • Balance: {details.get('balance_tensor', 0):,.2f}\n"
107
+
108
+ # Tips if not qualified
109
+ if not can_register:
110
+ content += "\n[htcli.warning]💡 Tips to Qualify:[/htcli.warning]\n"
111
+ content += " • Run nodes on multiple subnets to increase diversification\n"
112
+ content += " • Maintain high attestation rate to build reputation\n"
113
+ content += f" • Need at least {details.get('min_stake_tensor', 0):,.2f} for initial stake\n"
114
+
115
+ panel = HTCLIPanel(
116
+ content,
117
+ title=f"{title_emoji} Overwatch Qualification Check",
118
+ border_style=border_style,
119
+ highlight=True,
120
+ )
121
+ panel.render(console.console)
122
+
123
+
124
+ def display_overwatch_list(nodes_data: List[Any]):
125
+ """Display all overwatch nodes from RPC response."""
126
+ if not nodes_data:
127
+ console.print(info("No overwatch nodes found on the network"))
128
+ return
129
+
130
+ # Create table for overwatch nodes
131
+ table = HTCLITable(title="Overwatch Nodes")
132
+ table.add_column("ID", style="htcli.accent", justify="right")
133
+ table.add_column("Coldkey", style="htcli.address")
134
+ table.add_column("Hotkey", style="htcli.address")
135
+ table.add_column("Stake", style="htcli.value", justify="right")
136
+ table.add_column("Penalties", style="htcli.value", justify="right")
137
+
138
+ for node in nodes_data:
139
+ node_id = getattr(node, "overwatch_node_id", "N/A")
140
+ coldkey = getattr(node, "coldkey", "N/A")
141
+ hotkey = getattr(node, "hotkey", "N/A")
142
+ penalties = getattr(node, "penalties", 0)
143
+ stake_balance = getattr(node, "stake_balance", 0)
144
+ stake_tensor = stake_balance / 1e18 if stake_balance else 0
145
+
146
+ # Truncate addresses for display: 0x<5 chars>...<5 chars>
147
+ from ...utils.wallet.crypto import format_address_display
148
+ coldkey_display = format_address_display(str(coldkey))
149
+ hotkey_display = format_address_display(str(hotkey))
150
+
151
+ table.add_row(
152
+ str(node_id),
153
+ coldkey_display,
154
+ hotkey_display,
155
+ f"{stake_tensor:,.2f}",
156
+ str(penalties),
157
+ )
158
+
159
+ table.render()
160
+ console.print(f"\n[htcli.info]Total: {len(nodes_data)} overwatch node(s)[/]")
161
+
162
+
163
+
164
+ def display_overwatch_node_info(node_data: Any):
165
+ """Display specific overwatch node information from RPC response."""
166
+ from ...utils.wallet.crypto import format_address_display
167
+
168
+ if not node_data:
169
+ console.print(info("Overwatch node not found"))
170
+ return
171
+
172
+ node_id = getattr(node_data, "overwatch_node_id", "N/A")
173
+ coldkey = getattr(node_data, "coldkey", "N/A")
174
+ hotkey = getattr(node_data, "hotkey", "N/A")
175
+ penalties = getattr(node_data, "penalties", 0)
176
+ peer_ids = getattr(node_data, "peer_ids", [])
177
+ reputation = getattr(node_data, "reputation", None)
178
+ stake_balance = getattr(node_data, "stake_balance", 0)
179
+
180
+ # Format stake balance
181
+ stake_tensor = stake_balance / 1e18 if stake_balance else 0
182
+
183
+ # Format peer IDs
184
+ peer_ids_display = "None"
185
+ if peer_ids:
186
+ formatted_peers = []
187
+ # Sort by subnet ID for consistent display
188
+ sorted_peers = sorted(peer_ids.items())
189
+
190
+ for subnet_id, peer_id in sorted_peers[:5]: # Limit to first 5
191
+ formatted_peers.append(f" • Subnet {subnet_id}: {format_peer_id(peer_id)}")
192
+ peer_ids_display = "\n".join(formatted_peers)
193
+ if len(peer_ids) > 5:
194
+ peer_ids_display += f"\n ... and {len(peer_ids) - 5} more"
195
+
196
+ # Format reputation
197
+ reputation_display = "[dim]No reputation data[/dim]"
198
+ if reputation:
199
+ rep_score = reputation.get('score', 0) / 1e12
200
+ reputation_display = f""" • Score: {rep_score:,.2f}
201
+ • Lifetime Nodes: {reputation.get('lifetime_node_count', 0)}
202
+ • Active Nodes: {reputation.get('total_active_nodes', 0)}
203
+ • Avg Attestation: {reputation.get('average_attestation', 0)}"""
204
+
205
+ content = f"""[htcli.accent]Basic Information[/htcli.accent]
206
+ [htcli.value]Overwatch Node ID:[/htcli.value] {node_id}
207
+ [htcli.value]Penalties:[/htcli.value] {penalties}
208
+
209
+ [htcli.accent]Stake[/htcli.accent]
210
+ [htcli.value]Current Stake:[/htcli.value] {stake_tensor:,.4f}
211
+
212
+ [htcli.accent]Wallet Addresses[/htcli.accent]
213
+ [htcli.value]Coldkey:[/htcli.value] [htcli.address]{format_address_display(str(coldkey), truncate=False)}[/htcli.address]
214
+ [htcli.value]Hotkey:[/htcli.value] [htcli.address]{format_address_display(str(hotkey), truncate=False)}[/htcli.address]
215
+
216
+ [htcli.accent]Peer IDs by Subnet[/htcli.accent]
217
+ {peer_ids_display}
218
+
219
+ [htcli.accent]Reputation[/htcli.accent]
220
+ {reputation_display}
221
+ """
222
+
223
+ panel = HTCLIPanel(
224
+ content,
225
+ title=f"🔮 Overwatch Node {node_id} Details",
226
+ border_style="htcli.info",
227
+ highlight=True,
228
+ )
229
+ panel.render(console.console)
230
+
231
+
232
+ # ============================================================================
233
+ # EXTRINSIC DISPLAY FUNCTIONS - Write Operations
234
+ # ============================================================================
235
+
236
+
237
+ def display_overwatch_register_result(response: dict):
238
+ """Display overwatch node registration result."""
239
+ from ...utils.wallet.crypto import format_address_display
240
+
241
+ if response.get("success"):
242
+ stake_wei = response.get("stake_added", 0)
243
+ stake_tensor = stake_wei / 1e18 if stake_wei else 0
244
+ hotkey = format_address_display(response.get("hotkey", "N/A"), truncate=False)
245
+
246
+ content = f"""[htcli.success]✅ Overwatch Node Registered Successfully![/htcli.success]
247
+
248
+ [htcli.value]Overwatch Node ID:[/htcli.value] {response.get("overwatch_node_id", "Pending")}
249
+ [htcli.value]Hotkey:[/htcli.value] [htcli.address]{hotkey}[/htcli.address]
250
+ [htcli.value]Initial Stake:[/htcli.value] {stake_tensor:,.4f}
251
+ [htcli.value]Transaction Hash:[/htcli.value] {response.get("transaction_hash", "N/A")}
252
+ [htcli.value]Block Number:[/htcli.value] {response.get("block_number", "N/A")}
253
+ """
254
+ if response.get("block_hash"):
255
+ content += f"[htcli.value]Block Hash:[/htcli.value] {response.get('block_hash')}\n"
256
+ content += "\n[htcli.info]💡 Your overwatch node is now registered! You can start participating in consensus.[/htcli.info]\n"
257
+
258
+ panel = HTCLIPanel(
259
+ content,
260
+ title="🔮 Overwatch Node Registration Complete",
261
+ border_style="htcli.success",
262
+ highlight=True,
263
+ )
264
+ panel.render(console.console)
265
+ else:
266
+ error_msg = response.get("error", "Unknown error")
267
+ panel = HTCLIPanel(
268
+ f"""{error_msg}
269
+
270
+ 💡 Troubleshooting:
271
+ • Ensure you meet the reputation requirements for overwatch nodes
272
+ • Verify you have sufficient balance for stake and fees
273
+ • Check that your hotkey is not already registered
274
+ • Review: htcli overwatch register --help
275
+ """,
276
+ title="⚠️ Overwatch Node Registration Failed",
277
+ border_style="htcli.error",
278
+ highlight=True,
279
+ )
280
+ panel.render(console.console)
281
+
282
+
283
+ def display_overwatch_remove_result(response: dict):
284
+ """Display overwatch node removal result."""
285
+ if response.get("success"):
286
+ content = f"""[htcli.success]✅ Overwatch Node Removed Successfully[/htcli.success]
287
+
288
+ [htcli.value]Overwatch Node ID:[/htcli.value] {response.get("overwatch_node_id", "N/A")}
289
+ [htcli.value]Transaction Hash:[/htcli.value] {response.get("transaction_hash", "N/A")}
290
+ [htcli.value]Block Number:[/htcli.value] {response.get("block_number", "N/A")}
291
+ """
292
+ if response.get("block_hash"):
293
+ content += f"[htcli.value]Block Hash:[/htcli.value] {response.get('block_hash')}\n"
294
+ content += "\n[htcli.info]💡 Your overwatch node has been removed. Any staked balance will enter unbonding.[/htcli.info]\n"
295
+
296
+ panel = HTCLIPanel(
297
+ content,
298
+ title="🗑️ Overwatch Node Removal Complete",
299
+ border_style="htcli.success",
300
+ highlight=True,
301
+ )
302
+ panel.render(console.console)
303
+ else:
304
+ error_msg = response.get("error", "Unknown error")
305
+ console.print(f"[htcli.error]❌ Failed to remove overwatch node: {error_msg}[/]")
306
+
307
+
308
+ def display_overwatch_peer_id_result(response: dict):
309
+ """Display set peer ID result."""
310
+ if response.get("success"):
311
+ content = f"""[htcli.success]✅ Peer ID Set Successfully[/htcli.success]
312
+
313
+ [htcli.value]Subnet ID:[/htcli.value] {response.get("subnet_id", "N/A")}
314
+ [htcli.value]Overwatch Node ID:[/htcli.value] {response.get("overwatch_node_id", "N/A")}
315
+ [htcli.value]Peer ID:[/htcli.value] {response.get("peer_id", "N/A")}
316
+ [htcli.value]Transaction Hash:[/htcli.value] {response.get("transaction_hash", "N/A")}
317
+ [htcli.value]Block Number:[/htcli.value] {response.get("block_number", "N/A")}
318
+ """
319
+ if response.get("block_hash"):
320
+ content += f"[htcli.value]Block Hash:[/htcli.value] {response.get('block_hash')}\n"
321
+ content += "\n[htcli.info]💡 Your peer ID has been registered for this subnet.[/htcli.info]\n"
322
+
323
+ panel = HTCLIPanel(
324
+ content,
325
+ title="🔗 Peer ID Update Complete",
326
+ border_style="htcli.success",
327
+ highlight=True,
328
+ )
329
+ panel.render(console.console)
330
+ else:
331
+ error_msg = response.get("error", "Unknown error")
332
+ console.print(f"[htcli.error]❌ Failed to set peer ID: {error_msg}[/]")
333
+
334
+
335
+ def display_overwatch_stake_add_result(response: dict):
336
+ """Display add stake result."""
337
+ from ...utils.wallet.crypto import format_address_display
338
+
339
+ if response.get("success"):
340
+ stake_wei = response.get("stake_added", 0)
341
+ stake_tensor = stake_wei / 1e18 if stake_wei else 0
342
+ hotkey = format_address_display(response.get("hotkey", "N/A"), truncate=False)
343
+
344
+ content = f"""[htcli.success]✅ Stake Added Successfully[/htcli.success]
345
+
346
+ [htcli.value]Overwatch Node ID:[/htcli.value] {response.get("overwatch_node_id", "N/A")}
347
+ [htcli.value]Hotkey:[/htcli.value] [htcli.address]{hotkey}[/htcli.address]
348
+ [htcli.value]Stake Added:[/htcli.value] {stake_tensor:,.4f}
349
+ [htcli.value]Transaction Hash:[/htcli.value] {response.get("transaction_hash", "N/A")}
350
+ [htcli.value]Block Number:[/htcli.value] {response.get("block_number", "N/A")}
351
+ """
352
+ if response.get("block_hash"):
353
+ content += f"[htcli.value]Block Hash:[/htcli.value] {response.get('block_hash')}\n"
354
+
355
+ panel = HTCLIPanel(
356
+ content,
357
+ title="💰 Overwatch Stake Added",
358
+ border_style="htcli.success",
359
+ highlight=True,
360
+ )
361
+ panel.render(console.console)
362
+ else:
363
+ error_msg = response.get("error", "Unknown error")
364
+ console.print(f"[htcli.error]❌ Failed to add stake: {error_msg}[/]")
365
+
366
+
367
+ def display_overwatch_stake_remove_result(response: dict):
368
+ """Display remove stake result."""
369
+ from ...utils.wallet.crypto import format_address_display
370
+
371
+ if response.get("success"):
372
+ stake_wei = response.get("stake_removed", 0)
373
+ stake_tensor = stake_wei / 1e18 if stake_wei else 0
374
+ hotkey = format_address_display(response.get("hotkey", "N/A"), truncate=False)
375
+
376
+ content = f"""[htcli.success]✅ Stake Removal Started[/htcli.success]
377
+
378
+ [htcli.value]Hotkey:[/htcli.value] [htcli.address]{hotkey}[/htcli.address]
379
+ [htcli.value]Stake Removed:[/htcli.value] {stake_tensor:,.4f}
380
+ [htcli.value]Transaction Hash:[/htcli.value] {response.get("transaction_hash", "N/A")}
381
+ [htcli.value]Block Number:[/htcli.value] {response.get("block_number", "N/A")}
382
+ """
383
+ if response.get("block_hash"):
384
+ content += f"[htcli.value]Block Hash:[/htcli.value] {response.get('block_hash')}\n"
385
+ content += "\n[htcli.warning]⚠️ Removed stake will be available after the unbonding period.[/htcli.warning]\n"
386
+
387
+ panel = HTCLIPanel(
388
+ content,
389
+ title="💸 Overwatch Stake Removal Initiated",
390
+ border_style="htcli.success",
391
+ highlight=True,
392
+ )
393
+ panel.render(console.console)
394
+ else:
395
+ error_msg = response.get("error", "Unknown error")
396
+ console.print(f"[htcli.error]❌ Failed to remove stake: {error_msg}[/]")
@@ -0,0 +1,276 @@
1
+ """
2
+ Error handling utilities for overwatch commands.
3
+ Provides user-friendly error messages for all overwatch-related errors.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+ from ...ui.components import HTCLIPanel
9
+ from ...ui.display import HTCLIConsole
10
+
11
+
12
+ def handle_overwatch_error(
13
+ error_msg: str,
14
+ overwatch_node_id: Optional[int] = None,
15
+ operation: str = "operation",
16
+ client=None,
17
+ ) -> bool:
18
+ """
19
+ Handle overwatch-related errors with user-friendly messages.
20
+
21
+ Args:
22
+ error_msg: The error message from the blockchain
23
+ overwatch_node_id: The overwatch node ID (if applicable)
24
+ operation: Description of the operation being performed
25
+ client: Optional client instance for fetching additional info
26
+
27
+ Returns:
28
+ bool: True if error was handled, False otherwise
29
+ """
30
+ console = HTCLIConsole()
31
+ node_id_str = f" {overwatch_node_id}" if overwatch_node_id is not None else ""
32
+
33
+ # Normalize error message for case-insensitive matching
34
+ error_msg_lower = error_msg.lower()
35
+
36
+ # =========================================================================
37
+ # Registration Errors
38
+ # =========================================================================
39
+
40
+ if "coldkeyblacklisted" in error_msg_lower:
41
+ error_panel = HTCLIPanel(
42
+ f"Your coldkey is blacklisted from registering overwatch nodes.\n\n"
43
+ f"💡 Blacklisted accounts cannot participate as overwatch nodes.\n"
44
+ f" Contact support if you believe this is in error.",
45
+ title="🚫 Coldkey Blacklisted",
46
+ border_style="htcli.error",
47
+ highlight=True,
48
+ )
49
+ error_panel.render(console.console)
50
+ return True
51
+
52
+ elif "overwatchepochiszero" in error_msg_lower:
53
+ error_panel = HTCLIPanel(
54
+ f"Overwatch epoch has not started yet.\n\n"
55
+ f"💡 The overwatch system is not yet active on the network.\n"
56
+ f" Wait for the first overwatch epoch to begin before registering.",
57
+ title="⏳ Overwatch Not Started",
58
+ border_style="htcli.warning",
59
+ highlight=True,
60
+ )
61
+ error_panel.render(console.console)
62
+ return True
63
+
64
+ elif "maxoverwatchnodes" in error_msg_lower:
65
+ error_panel = HTCLIPanel(
66
+ f"Maximum number of overwatch nodes has been reached.\n\n"
67
+ f"💡 The network has a cap on the number of overwatch nodes.\n"
68
+ f" Wait for a slot to become available when another node exits.",
69
+ title="❌ Max Overwatch Nodes Reached",
70
+ border_style="htcli.error",
71
+ highlight=True,
72
+ )
73
+ error_panel.render(console.console)
74
+ return True
75
+
76
+ elif "coldkeymatcheshotkey" in error_msg_lower:
77
+ error_panel = HTCLIPanel(
78
+ f"Coldkey and hotkey cannot be the same account.\n\n"
79
+ f"💡 You must use different accounts for coldkey and hotkey.\n"
80
+ f" Generate a separate hotkey: htcli wallet generate-hotkey",
81
+ title="❌ Invalid Key Configuration",
82
+ border_style="htcli.error",
83
+ highlight=True,
84
+ )
85
+ error_panel.render(console.console)
86
+ return True
87
+
88
+ elif "hotkeyhasowner" in error_msg_lower:
89
+ error_panel = HTCLIPanel(
90
+ f"This hotkey is already registered to another account.\n\n"
91
+ f"💡 Each hotkey can only be owned by one coldkey.\n"
92
+ f" Use a fresh hotkey: htcli wallet generate-hotkey",
93
+ title="❌ Hotkey Already Owned",
94
+ border_style="htcli.error",
95
+ highlight=True,
96
+ )
97
+ error_panel.render(console.console)
98
+ return True
99
+
100
+ elif "hotkeyalreadyregisteredtocoldkey" in error_msg_lower:
101
+ error_panel = HTCLIPanel(
102
+ f"This hotkey is already registered to your coldkey.\n\n"
103
+ f"💡 You've already registered this hotkey.\n"
104
+ f" Check your existing nodes: htcli overwatch list",
105
+ title="❌ Hotkey Already Registered",
106
+ border_style="htcli.error",
107
+ highlight=True,
108
+ )
109
+ error_panel.render(console.console)
110
+ return True
111
+
112
+ elif "coldkeynotoverwatchqualified" in error_msg_lower:
113
+ error_panel = HTCLIPanel(
114
+ f"Your coldkey is not qualified to run an overwatch node.\n\n"
115
+ f"💡 Overwatch nodes require sufficient reputation.\n"
116
+ f" Build reputation by running subnet nodes successfully.\n"
117
+ f" Check your reputation status: htcli wallet info",
118
+ title="❌ Not Qualified",
119
+ border_style="htcli.error",
120
+ highlight=True,
121
+ )
122
+ error_panel.render(console.console)
123
+ return True
124
+
125
+ # =========================================================================
126
+ # Node Existence Errors
127
+ # =========================================================================
128
+
129
+ elif "invalidoverwatchnodeid" in error_msg_lower or "overwatchnodenotexist" in error_msg_lower:
130
+ error_panel = HTCLIPanel(
131
+ f"Overwatch node{node_id_str} does not exist.\n\n"
132
+ f"💡 Check the overwatch node ID and try again.\n"
133
+ f" Use: htcli overwatch list to see all overwatch nodes",
134
+ title="❌ Invalid Overwatch Node",
135
+ border_style="htcli.error",
136
+ highlight=True,
137
+ )
138
+ error_panel.render(console.console)
139
+ return True
140
+
141
+ elif "notoverwatchnodeowner" in error_msg_lower:
142
+ error_panel = HTCLIPanel(
143
+ f"You are not the owner of overwatch node{node_id_str}.\n\n"
144
+ f"💡 Only the coldkey owner can manage this overwatch node.\n"
145
+ f" Check your wallet: htcli wallet list",
146
+ title="❌ Permission Denied",
147
+ border_style="htcli.error",
148
+ highlight=True,
149
+ )
150
+ error_panel.render(console.console)
151
+ return True
152
+
153
+ # =========================================================================
154
+ # Stake Errors
155
+ # =========================================================================
156
+
157
+ elif "overwatchminstakenotreached" in error_msg_lower or "minoverwatchstake" in error_msg_lower:
158
+ error_panel = HTCLIPanel(
159
+ f"Stake amount is below the minimum requirement for overwatch nodes.\n\n"
160
+ f"💡 Overwatch nodes require a minimum stake to participate.\n"
161
+ f" Increase your stake amount and try again.",
162
+ title="❌ Below Minimum Stake",
163
+ border_style="htcli.error",
164
+ highlight=True,
165
+ )
166
+ error_panel.render(console.console)
167
+ return True
168
+
169
+ elif "notenoughoverwatchstake" in error_msg_lower:
170
+ error_panel = HTCLIPanel(
171
+ f"Insufficient overwatch stake to remove.\n\n"
172
+ f"💡 You don't have enough staked balance to remove this amount.\n"
173
+ f" Check your current stake: htcli overwatch info",
174
+ title="❌ Insufficient Stake",
175
+ border_style="htcli.error",
176
+ highlight=True,
177
+ )
178
+ error_panel.render(console.console)
179
+ return True
180
+
181
+ elif "notenoughbalancetostake" in error_msg_lower or "insufficientbalance" in error_msg_lower:
182
+ error_panel = HTCLIPanel(
183
+ f"Insufficient balance to add stake.\n\n"
184
+ f"💡 You don't have enough balance in your wallet.\n"
185
+ f" Check your balance: htcli wallet balance",
186
+ title="❌ Insufficient Balance",
187
+ border_style="htcli.error",
188
+ highlight=True,
189
+ )
190
+ error_panel.render(console.console)
191
+ return True
192
+
193
+ # =========================================================================
194
+ # Peer ID Errors
195
+ # =========================================================================
196
+
197
+ elif "invalidpeerid" in error_msg_lower:
198
+ error_panel = HTCLIPanel(
199
+ f"Invalid peer ID format.\n\n"
200
+ f"💡 The peer ID must be a valid libp2p peer identifier.\n"
201
+ f" Example: 12D3KooWxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
202
+ title="❌ Invalid Peer ID",
203
+ border_style="htcli.error",
204
+ highlight=True,
205
+ )
206
+ error_panel.render(console.console)
207
+ return True
208
+
209
+ elif "peeridalreadyinuse" in error_msg_lower:
210
+ error_panel = HTCLIPanel(
211
+ f"This peer ID is already in use by another node.\n\n"
212
+ f"💡 Each node must have a unique peer ID.\n"
213
+ f" Generate a new peer ID for your node.",
214
+ title="❌ Peer ID Already Used",
215
+ border_style="htcli.error",
216
+ highlight=True,
217
+ )
218
+ error_panel.render(console.console)
219
+ return True
220
+
221
+ # =========================================================================
222
+ # Removal Errors
223
+ # =========================================================================
224
+
225
+ elif "overwatchnodecannotberemoved" in error_msg_lower:
226
+ error_panel = HTCLIPanel(
227
+ f"Overwatch node{node_id_str} cannot be removed at this time.\n\n"
228
+ f"💡 The node may be in an active validation epoch.\n"
229
+ f" Wait for the current epoch to complete before removing.",
230
+ title="⚠️ Cannot Remove Node",
231
+ border_style="htcli.warning",
232
+ highlight=True,
233
+ )
234
+ error_panel.render(console.console)
235
+ return True
236
+
237
+ elif "overwatchnodealreadyremoved" in error_msg_lower:
238
+ error_panel = HTCLIPanel(
239
+ f"Overwatch node{node_id_str} has already been removed.\n\n"
240
+ f"💡 This node no longer exists in the network.",
241
+ title="⚠️ Node Already Removed",
242
+ border_style="htcli.warning",
243
+ highlight=True,
244
+ )
245
+ error_panel.render(console.console)
246
+ return True
247
+
248
+ # =========================================================================
249
+ # System Errors
250
+ # =========================================================================
251
+
252
+ elif "txratelimitexceeded" in error_msg_lower:
253
+ error_panel = HTCLIPanel(
254
+ f"Transaction rate limit exceeded.\n\n"
255
+ f"⏱️ You've exceeded the maximum transaction rate.\n\n"
256
+ f"💡 Wait a moment before submitting another transaction.",
257
+ title="⏳ Rate Limit Exceeded",
258
+ border_style="htcli.warning",
259
+ highlight=True,
260
+ )
261
+ error_panel.render(console.console)
262
+ return True
263
+
264
+ elif "badorigin" in error_msg_lower:
265
+ error_panel = HTCLIPanel(
266
+ f"Invalid transaction signature.\n\n"
267
+ f"💡 Make sure you're using the correct wallet.\n"
268
+ f" Check your wallet: htcli wallet list",
269
+ title="❌ Invalid Signature",
270
+ border_style="htcli.error",
271
+ highlight=True,
272
+ )
273
+ error_panel.render(console.console)
274
+ return True
275
+
276
+ return False # Error not handled by this function