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.
- htcli-1.1.0.dist-info/METADATA +509 -0
- htcli-1.1.0.dist-info/RECORD +140 -0
- htcli-1.1.0.dist-info/WHEEL +4 -0
- htcli-1.1.0.dist-info/entry_points.txt +2 -0
- htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/htcli/__init__.py +5 -0
- src/htcli/client/__init__.py +338 -0
- src/htcli/client/extrinsics/__init__.py +26 -0
- src/htcli/client/extrinsics/base.py +487 -0
- src/htcli/client/extrinsics/consensus.py +79 -0
- src/htcli/client/extrinsics/governance.py +714 -0
- src/htcli/client/extrinsics/identity.py +490 -0
- src/htcli/client/extrinsics/node.py +1054 -0
- src/htcli/client/extrinsics/overwatch.py +401 -0
- src/htcli/client/extrinsics/staking.py +1504 -0
- src/htcli/client/extrinsics/subnet.py +2218 -0
- src/htcli/client/extrinsics/validator.py +203 -0
- src/htcli/client/extrinsics/wallet.py +323 -0
- src/htcli/client/offchain/__init__.py +10 -0
- src/htcli/client/offchain/backup.py +385 -0
- src/htcli/client/offchain/config.py +541 -0
- src/htcli/client/offchain/wallet.py +839 -0
- src/htcli/client/rpc/__init__.py +20 -0
- src/htcli/client/rpc/chain.py +568 -0
- src/htcli/client/rpc/node.py +783 -0
- src/htcli/client/rpc/overwatch.py +680 -0
- src/htcli/client/rpc/staking.py +216 -0
- src/htcli/client/rpc/subnet.py +2104 -0
- src/htcli/client/rpc/wallet.py +912 -0
- src/htcli/commands/__init__.py +31 -0
- src/htcli/commands/chain/__init__.py +66 -0
- src/htcli/commands/chain/display.py +204 -0
- src/htcli/commands/chain/handlers.py +260 -0
- src/htcli/commands/config/__init__.py +158 -0
- src/htcli/commands/config/display.py +353 -0
- src/htcli/commands/config/handlers.py +347 -0
- src/htcli/commands/config/prompts.py +357 -0
- src/htcli/commands/consensus/__init__.py +61 -0
- src/htcli/commands/consensus/handlers.py +100 -0
- src/htcli/commands/governance/__init__.py +49 -0
- src/htcli/commands/governance/handlers.py +81 -0
- src/htcli/commands/node/__init__.py +304 -0
- src/htcli/commands/node/display.py +749 -0
- src/htcli/commands/node/error_handling.py +470 -0
- src/htcli/commands/node/handlers.py +844 -0
- src/htcli/commands/node/prompts.py +346 -0
- src/htcli/commands/overwatch/__init__.py +219 -0
- src/htcli/commands/overwatch/display.py +396 -0
- src/htcli/commands/overwatch/error_handling.py +276 -0
- src/htcli/commands/overwatch/handlers.py +443 -0
- src/htcli/commands/overwatch/prompts.py +359 -0
- src/htcli/commands/stake/__init__.py +736 -0
- src/htcli/commands/stake/display.py +1103 -0
- src/htcli/commands/stake/error_handling.py +425 -0
- src/htcli/commands/stake/handlers.py +1902 -0
- src/htcli/commands/stake/prompts.py +1080 -0
- src/htcli/commands/subnet/__init__.py +639 -0
- src/htcli/commands/subnet/display.py +801 -0
- src/htcli/commands/subnet/error_handling.py +524 -0
- src/htcli/commands/subnet/handlers.py +2855 -0
- src/htcli/commands/subnet/prompts.py +1225 -0
- src/htcli/commands/validator/__init__.py +192 -0
- src/htcli/commands/validator/display.py +54 -0
- src/htcli/commands/validator/handlers.py +340 -0
- src/htcli/commands/wallet/__init__.py +546 -0
- src/htcli/commands/wallet/display.py +806 -0
- src/htcli/commands/wallet/error_handling.py +210 -0
- src/htcli/commands/wallet/handlers.py +3040 -0
- src/htcli/commands/wallet/prompts.py +1518 -0
- src/htcli/config.py +184 -0
- src/htcli/dependencies.py +186 -0
- src/htcli/errors/__init__.py +63 -0
- src/htcli/errors/base.py +141 -0
- src/htcli/errors/display.py +20 -0
- src/htcli/errors/handlers.py +710 -0
- src/htcli/main.py +343 -0
- src/htcli/models/__init__.py +21 -0
- src/htcli/models/enums/enum_types.py +35 -0
- src/htcli/models/errors.py +103 -0
- src/htcli/models/requests/__init__.py +197 -0
- src/htcli/models/requests/config.py +70 -0
- src/htcli/models/requests/consensus.py +19 -0
- src/htcli/models/requests/governance.py +38 -0
- src/htcli/models/requests/identity.py +51 -0
- src/htcli/models/requests/key.py +22 -0
- src/htcli/models/requests/node.py +91 -0
- src/htcli/models/requests/overwatch.py +64 -0
- src/htcli/models/requests/staking.py +580 -0
- src/htcli/models/requests/subnet.py +195 -0
- src/htcli/models/requests/validator.py +139 -0
- src/htcli/models/requests/wallet.py +118 -0
- src/htcli/models/responses/__init__.py +147 -0
- src/htcli/models/responses/base.py +18 -0
- src/htcli/models/responses/chain.py +39 -0
- src/htcli/models/responses/config.py +58 -0
- src/htcli/models/responses/identity.py +102 -0
- src/htcli/models/responses/overwatch.py +51 -0
- src/htcli/models/responses/staking.py +502 -0
- src/htcli/models/responses/subnet.py +856 -0
- src/htcli/models/responses/wallet.py +185 -0
- src/htcli/ui/__init__.py +87 -0
- src/htcli/ui/colors.py +309 -0
- src/htcli/ui/components/__init__.py +60 -0
- src/htcli/ui/components/panels.py +174 -0
- src/htcli/ui/components/progress.py +166 -0
- src/htcli/ui/components/spinners.py +92 -0
- src/htcli/ui/components/tables.py +809 -0
- src/htcli/ui/components/trees.py +721 -0
- src/htcli/ui/display.py +336 -0
- src/htcli/ui/prompts.py +870 -0
- src/htcli/utils/__init__.py +76 -0
- src/htcli/utils/blockchain/__init__.py +75 -0
- src/htcli/utils/blockchain/formatting.py +368 -0
- src/htcli/utils/blockchain/patches.py +286 -0
- src/htcli/utils/blockchain/peer_id.py +186 -0
- src/htcli/utils/blockchain/staking.py +448 -0
- src/htcli/utils/blockchain/type_registry.py +1373 -0
- src/htcli/utils/blockchain/validation.py +179 -0
- src/htcli/utils/cache.py +613 -0
- src/htcli/utils/constants.py +38 -0
- src/htcli/utils/legacy/__init__.py +12 -0
- src/htcli/utils/legacy/colors.py +311 -0
- src/htcli/utils/legacy/crypto.py +1176 -0
- src/htcli/utils/legacy/formatting.py +452 -0
- src/htcli/utils/legacy/interactive.py +306 -0
- src/htcli/utils/legacy/subnet_manifest.py +265 -0
- src/htcli/utils/legacy/validation.py +488 -0
- src/htcli/utils/logging.py +183 -0
- src/htcli/utils/network/__init__.py +20 -0
- src/htcli/utils/network/subnet.py +344 -0
- src/htcli/utils/prompts.py +27 -0
- src/htcli/utils/scale_codec.py +155 -0
- src/htcli/utils/validation/__init__.py +57 -0
- src/htcli/utils/validation/prompt_validators.py +267 -0
- src/htcli/utils/wallet/__init__.py +65 -0
- src/htcli/utils/wallet/auth.py +151 -0
- src/htcli/utils/wallet/core.py +1069 -0
- src/htcli/utils/wallet/crypto.py +1615 -0
- 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
|