meshtensor-cli 9.18.1__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.
- meshtensor_cli/__init__.py +22 -0
- meshtensor_cli/cli.py +10742 -0
- meshtensor_cli/doc_generation_helper.py +4 -0
- meshtensor_cli/src/__init__.py +1085 -0
- meshtensor_cli/src/commands/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/axon.py +132 -0
- meshtensor_cli/src/commands/crowd/__init__.py +0 -0
- meshtensor_cli/src/commands/crowd/contribute.py +621 -0
- meshtensor_cli/src/commands/crowd/contributors.py +200 -0
- meshtensor_cli/src/commands/crowd/create.py +783 -0
- meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
- meshtensor_cli/src/commands/crowd/refund.py +233 -0
- meshtensor_cli/src/commands/crowd/update.py +418 -0
- meshtensor_cli/src/commands/crowd/utils.py +124 -0
- meshtensor_cli/src/commands/crowd/view.py +991 -0
- meshtensor_cli/src/commands/governance/__init__.py +0 -0
- meshtensor_cli/src/commands/governance/governance.py +794 -0
- meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
- meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
- meshtensor_cli/src/commands/liquidity/utils.py +202 -0
- meshtensor_cli/src/commands/proxy.py +700 -0
- meshtensor_cli/src/commands/stake/__init__.py +0 -0
- meshtensor_cli/src/commands/stake/add.py +799 -0
- meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
- meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
- meshtensor_cli/src/commands/stake/claim.py +770 -0
- meshtensor_cli/src/commands/stake/list.py +738 -0
- meshtensor_cli/src/commands/stake/move.py +1211 -0
- meshtensor_cli/src/commands/stake/remove.py +1466 -0
- meshtensor_cli/src/commands/stake/wizard.py +323 -0
- meshtensor_cli/src/commands/subnets/__init__.py +0 -0
- meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
- meshtensor_cli/src/commands/subnets/price.py +733 -0
- meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
- meshtensor_cli/src/commands/sudo.py +1294 -0
- meshtensor_cli/src/commands/tc/__init__.py +0 -0
- meshtensor_cli/src/commands/tc/tc.py +190 -0
- meshtensor_cli/src/commands/treasury/__init__.py +0 -0
- meshtensor_cli/src/commands/treasury/treasury.py +194 -0
- meshtensor_cli/src/commands/view.py +354 -0
- meshtensor_cli/src/commands/wallets.py +2311 -0
- meshtensor_cli/src/commands/weights.py +467 -0
- meshtensor_cli/src/meshtensor/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/balances.py +313 -0
- meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
- meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
- meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
- meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
- meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
- meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
- meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
- meshtensor_cli/src/meshtensor/minigraph.py +254 -0
- meshtensor_cli/src/meshtensor/networking.py +12 -0
- meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
- meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
- meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
- meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
- meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
- meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
- meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
- meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
- meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
- meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
- meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
- meshtensor_cli/src/meshtensor/utils.py +2007 -0
- meshtensor_cli/version.py +23 -0
- meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
- meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
- meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
- meshtensor_cli-9.18.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from meshtensor_wallet import Wallet
|
|
7
|
+
from rich.prompt import Prompt
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table, Column
|
|
10
|
+
from rich import box
|
|
11
|
+
|
|
12
|
+
from meshtensor_cli.src import COLORS
|
|
13
|
+
from meshtensor_cli.src.meshtensor.balances import Balance
|
|
14
|
+
from meshtensor_cli.src.meshtensor.utils import (
|
|
15
|
+
confirm_action,
|
|
16
|
+
console,
|
|
17
|
+
print_error,
|
|
18
|
+
print_success,
|
|
19
|
+
unlock_key,
|
|
20
|
+
print_extrinsic_id,
|
|
21
|
+
json_console,
|
|
22
|
+
millify_tao,
|
|
23
|
+
group_subnets,
|
|
24
|
+
parse_subnet_range,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ClaimType(Enum):
|
|
32
|
+
Keep = "Keep"
|
|
33
|
+
Swap = "Swap"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def set_claim_type(
|
|
37
|
+
wallet: Wallet,
|
|
38
|
+
meshtensor: "MeshtensorInterface",
|
|
39
|
+
claim_type: Optional[ClaimType],
|
|
40
|
+
proxy: Optional[str],
|
|
41
|
+
netuids: Optional[str] = None,
|
|
42
|
+
prompt: bool = True,
|
|
43
|
+
decline: bool = False,
|
|
44
|
+
quiet: bool = False,
|
|
45
|
+
json_output: bool = False,
|
|
46
|
+
) -> tuple[bool, str, Optional[str]]:
|
|
47
|
+
"""
|
|
48
|
+
Sets the root claim type for the coldkey.
|
|
49
|
+
|
|
50
|
+
Root claim types control how staking emissions are handled on the ROOT network (subnet 0):
|
|
51
|
+
- "Swap": Future Root Alpha Emissions are swapped to MESH at claim time and added to root stake
|
|
52
|
+
- "Keep": Future Root Alpha Emissions are kept as Alpha tokens
|
|
53
|
+
- "KeepSubnets": Specific subnets kept as Alpha, rest swapped to MESH
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
wallet: Meshtensor wallet object
|
|
57
|
+
meshtensor: MeshtensorInterface object
|
|
58
|
+
claim_type: Claim type ("Keep" or "Swap"). If omitted, user will be prompted.
|
|
59
|
+
proxy: Optional proxy to use with this extrinsic submission.
|
|
60
|
+
netuids: Optional string of subnet IDs (e.g., "1-5,10,20-30"). Will be parsed internally.
|
|
61
|
+
prompt: Whether to prompt for user confirmation
|
|
62
|
+
json_output: Whether to output JSON
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
tuple[bool, str, Optional[str]]: Tuple containing:
|
|
66
|
+
- bool: True if successful, False otherwise
|
|
67
|
+
- str: Error message if failed
|
|
68
|
+
- Optional[str]: Extrinsic identifier if successful
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if claim_type is not None:
|
|
72
|
+
claim_type = claim_type.value
|
|
73
|
+
|
|
74
|
+
current_claim_info, all_netuids = await asyncio.gather(
|
|
75
|
+
meshtensor.get_coldkey_claim_type(coldkey_ss58=wallet.coldkeypub.ss58_address),
|
|
76
|
+
meshtensor.get_all_subnet_netuids(),
|
|
77
|
+
)
|
|
78
|
+
all_subnets = sorted([n for n in all_netuids if n != 0])
|
|
79
|
+
|
|
80
|
+
selected_netuids = None
|
|
81
|
+
if netuids is not None:
|
|
82
|
+
try:
|
|
83
|
+
selected_netuids = parse_subnet_range(
|
|
84
|
+
netuids, total_subnets=len(all_subnets)
|
|
85
|
+
)
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
msg = f"Invalid netuid format: {e}"
|
|
88
|
+
print_error(msg)
|
|
89
|
+
if json_output:
|
|
90
|
+
json_console.print(json.dumps({"success": False, "message": msg}))
|
|
91
|
+
return False, msg, None
|
|
92
|
+
|
|
93
|
+
claim_table = Table(
|
|
94
|
+
Column("[bold white]Coldkey", style=COLORS.GENERAL.COLDKEY, justify="left"),
|
|
95
|
+
Column(
|
|
96
|
+
"[bold white]Current Type", style=COLORS.GENERAL.SUBHEADING, justify="left"
|
|
97
|
+
),
|
|
98
|
+
show_header=True,
|
|
99
|
+
border_style="bright_black",
|
|
100
|
+
box=box.SIMPLE,
|
|
101
|
+
title=f"\n[{COLORS.GENERAL.HEADER}]Current Root Claim Type[/{COLORS.GENERAL.HEADER}]",
|
|
102
|
+
)
|
|
103
|
+
claim_table.add_row(
|
|
104
|
+
wallet.coldkeypub.ss58_address,
|
|
105
|
+
_format_claim_type_display(current_claim_info, all_subnets),
|
|
106
|
+
)
|
|
107
|
+
console.print(claim_table)
|
|
108
|
+
|
|
109
|
+
# Full wizard
|
|
110
|
+
if claim_type is None and selected_netuids is None:
|
|
111
|
+
new_claim_info = await _ask_for_claim_types(
|
|
112
|
+
wallet, meshtensor, all_subnets, decline=decline, quiet=quiet
|
|
113
|
+
)
|
|
114
|
+
if new_claim_info is None:
|
|
115
|
+
msg = "Operation cancelled."
|
|
116
|
+
console.print(f"[yellow]{msg}[/yellow]")
|
|
117
|
+
if json_output:
|
|
118
|
+
json_console.print(
|
|
119
|
+
json.dumps(
|
|
120
|
+
{
|
|
121
|
+
"success": False,
|
|
122
|
+
"message": msg,
|
|
123
|
+
"extrinsic_identifier": None,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
return False, msg, None
|
|
128
|
+
|
|
129
|
+
# Keep netuids passed thru the cli and assume Keep type
|
|
130
|
+
elif claim_type is None and selected_netuids is not None:
|
|
131
|
+
new_claim_info = {"type": "KeepSubnets", "subnets": selected_netuids}
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
# Netuids passed with Keep type
|
|
135
|
+
if selected_netuids is not None and claim_type == "Keep":
|
|
136
|
+
new_claim_info = {"type": "KeepSubnets", "subnets": selected_netuids}
|
|
137
|
+
|
|
138
|
+
# Netuids passed with Swap type
|
|
139
|
+
elif selected_netuids is not None and claim_type == "Swap":
|
|
140
|
+
keep_subnets = [n for n in all_subnets if n not in selected_netuids]
|
|
141
|
+
invalid = [n for n in selected_netuids if n not in all_subnets]
|
|
142
|
+
if invalid:
|
|
143
|
+
msg = f"Invalid subnets (not available): {group_subnets(invalid)}"
|
|
144
|
+
print_error(msg)
|
|
145
|
+
if json_output:
|
|
146
|
+
json_console.print(json.dumps({"success": False, "message": msg}))
|
|
147
|
+
return False, msg, None
|
|
148
|
+
|
|
149
|
+
if not keep_subnets:
|
|
150
|
+
new_claim_info = {"type": "Swap"}
|
|
151
|
+
elif set(keep_subnets) == set(all_subnets):
|
|
152
|
+
new_claim_info = {"type": "Keep"}
|
|
153
|
+
else:
|
|
154
|
+
new_claim_info = {"type": "KeepSubnets", "subnets": keep_subnets}
|
|
155
|
+
else:
|
|
156
|
+
new_claim_info = {"type": claim_type}
|
|
157
|
+
|
|
158
|
+
if _claim_types_equal(current_claim_info, new_claim_info):
|
|
159
|
+
if new_claim_info["type"] == "KeepSubnets":
|
|
160
|
+
msg = f"Claim type already set to {_format_claim_type_display(new_claim_info)}. \nNo change needed."
|
|
161
|
+
console.print(msg)
|
|
162
|
+
if json_output:
|
|
163
|
+
json_console.print(
|
|
164
|
+
json.dumps(
|
|
165
|
+
{
|
|
166
|
+
"success": True,
|
|
167
|
+
"message": msg,
|
|
168
|
+
"extrinsic_identifier": None,
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
return True, msg, None
|
|
173
|
+
|
|
174
|
+
if prompt:
|
|
175
|
+
console.print(
|
|
176
|
+
Panel(
|
|
177
|
+
f"[{COLORS.GENERAL.HEADER}]Confirm Claim Type Change[/{COLORS.GENERAL.HEADER}]\n\n"
|
|
178
|
+
f"FROM: {_format_claim_type_display(current_claim_info, all_subnets)}\n\n"
|
|
179
|
+
f"TO: {_format_claim_type_display(new_claim_info, all_subnets)}"
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not confirm_action(
|
|
184
|
+
"\nProceed with this change?", decline=decline, quiet=quiet
|
|
185
|
+
):
|
|
186
|
+
msg = "Operation cancelled."
|
|
187
|
+
console.print(f"[yellow]{msg}[/yellow]")
|
|
188
|
+
if json_output:
|
|
189
|
+
json_console.print(json.dumps({"success": False, "message": msg}))
|
|
190
|
+
return False, msg, None
|
|
191
|
+
|
|
192
|
+
if not (unlock := unlock_key(wallet)).success:
|
|
193
|
+
msg = f"Failed to unlock wallet: {unlock.message}"
|
|
194
|
+
print_error(msg)
|
|
195
|
+
if json_output:
|
|
196
|
+
json_console.print(json.dumps({"success": False, "message": msg}))
|
|
197
|
+
return False, msg, None
|
|
198
|
+
|
|
199
|
+
with console.status(":satellite: Setting root claim type...", spinner="earth"):
|
|
200
|
+
claim_type_param = _prepare_claim_type_args(new_claim_info)
|
|
201
|
+
call = await meshtensor.substrate.compose_call(
|
|
202
|
+
call_module="MeshtensorModule",
|
|
203
|
+
call_function="set_root_claim_type",
|
|
204
|
+
call_params={"new_root_claim_type": claim_type_param},
|
|
205
|
+
)
|
|
206
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
207
|
+
call, wallet, proxy=proxy
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if success:
|
|
211
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
212
|
+
msg = "Successfully changed claim type"
|
|
213
|
+
print_success(msg)
|
|
214
|
+
await print_extrinsic_id(ext_receipt)
|
|
215
|
+
if json_output:
|
|
216
|
+
json_console.print(
|
|
217
|
+
json.dumps(
|
|
218
|
+
{
|
|
219
|
+
"success": True,
|
|
220
|
+
"message": msg,
|
|
221
|
+
"extrinsic_identifier": ext_id,
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
return True, msg, ext_id
|
|
226
|
+
else:
|
|
227
|
+
msg = f"Failed to set claim type: {err_msg}"
|
|
228
|
+
print_error(msg)
|
|
229
|
+
if json_output:
|
|
230
|
+
json_console.print(json.dumps({"success": False, "message": msg}))
|
|
231
|
+
return False, msg, None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
async def process_pending_claims(
|
|
235
|
+
wallet: Wallet,
|
|
236
|
+
meshtensor: "MeshtensorInterface",
|
|
237
|
+
netuids: Optional[list[int]] = None,
|
|
238
|
+
proxy: Optional[str] = None,
|
|
239
|
+
prompt: bool = True,
|
|
240
|
+
decline: bool = False,
|
|
241
|
+
quiet: bool = False,
|
|
242
|
+
json_output: bool = False,
|
|
243
|
+
verbose: bool = False,
|
|
244
|
+
) -> tuple[bool, str, Optional[str]]:
|
|
245
|
+
"""Claims root network emissions for the coldkey across specified subnets"""
|
|
246
|
+
|
|
247
|
+
coldkey_ss58 = proxy or wallet.coldkeypub.ss58_address
|
|
248
|
+
with console.status(":satellite: Discovering claimable emissions..."):
|
|
249
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
250
|
+
all_stakes, identities = await asyncio.gather(
|
|
251
|
+
meshtensor.get_stake_for_coldkey(
|
|
252
|
+
coldkey_ss58=coldkey_ss58, block_hash=block_hash
|
|
253
|
+
),
|
|
254
|
+
meshtensor.query_all_identities(block_hash=block_hash),
|
|
255
|
+
)
|
|
256
|
+
if not all_stakes:
|
|
257
|
+
msg = "No stakes found for this coldkey"
|
|
258
|
+
console.print(f"[yellow]{msg}[/yellow]")
|
|
259
|
+
if json_output:
|
|
260
|
+
json_console.print(
|
|
261
|
+
json.dumps(
|
|
262
|
+
{
|
|
263
|
+
"success": True,
|
|
264
|
+
"message": msg,
|
|
265
|
+
"extrinsic_identifier": None,
|
|
266
|
+
"netuids": [],
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
return True, msg, None
|
|
271
|
+
|
|
272
|
+
current_stakes = {
|
|
273
|
+
(stake.hotkey_ss58, stake.netuid): stake for stake in all_stakes
|
|
274
|
+
}
|
|
275
|
+
claimable_by_hotkey = await meshtensor.get_claimable_stakes_for_coldkey(
|
|
276
|
+
coldkey_ss58=coldkey_ss58,
|
|
277
|
+
stakes_info=all_stakes,
|
|
278
|
+
block_hash=block_hash,
|
|
279
|
+
)
|
|
280
|
+
hotkey_owner_tasks = [
|
|
281
|
+
meshtensor.get_hotkey_owner(
|
|
282
|
+
hotkey, check_exists=False, block_hash=block_hash
|
|
283
|
+
)
|
|
284
|
+
for hotkey in claimable_by_hotkey.keys()
|
|
285
|
+
]
|
|
286
|
+
hotkey_owners = await asyncio.gather(*hotkey_owner_tasks)
|
|
287
|
+
hotkey_to_owner = dict(zip(claimable_by_hotkey.keys(), hotkey_owners))
|
|
288
|
+
|
|
289
|
+
# Consolidate data
|
|
290
|
+
claimable_stake_info = {}
|
|
291
|
+
for vali_hotkey, claimable_stakes in claimable_by_hotkey.items():
|
|
292
|
+
vali_coldkey = hotkey_to_owner.get(vali_hotkey, "~")
|
|
293
|
+
vali_identity = identities.get(vali_coldkey, {}).get("name", "~")
|
|
294
|
+
for netuid, claimable_stake in claimable_stakes.items():
|
|
295
|
+
if claimable_stake.meshlet > 0:
|
|
296
|
+
if netuid not in claimable_stake_info:
|
|
297
|
+
claimable_stake_info[netuid] = {}
|
|
298
|
+
current_stake = (
|
|
299
|
+
stake_info.stake
|
|
300
|
+
if (stake_info := current_stakes.get((vali_hotkey, netuid)))
|
|
301
|
+
else Balance.from_meshlet(0).set_unit(netuid)
|
|
302
|
+
)
|
|
303
|
+
claimable_stake_info[netuid][vali_hotkey] = {
|
|
304
|
+
"claimable": claimable_stake,
|
|
305
|
+
"stake": current_stake,
|
|
306
|
+
"coldkey": vali_coldkey,
|
|
307
|
+
"identity": vali_identity,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if netuids:
|
|
311
|
+
claimable_stake_info = {
|
|
312
|
+
netuid: hotkeys_info
|
|
313
|
+
for netuid, hotkeys_info in claimable_stake_info.items()
|
|
314
|
+
if netuid in netuids
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if not claimable_stake_info:
|
|
318
|
+
msg = "No claimable emissions found"
|
|
319
|
+
console.print(f"[yellow]{msg}[/yellow]")
|
|
320
|
+
if json_output:
|
|
321
|
+
json_console.print(
|
|
322
|
+
json.dumps(
|
|
323
|
+
{
|
|
324
|
+
"success": True,
|
|
325
|
+
"message": msg,
|
|
326
|
+
"extrinsic_identifier": None,
|
|
327
|
+
"netuids": netuids,
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
return True, msg, None
|
|
332
|
+
|
|
333
|
+
_print_claimable_table(wallet, claimable_stake_info, verbose)
|
|
334
|
+
selected_netuids = (
|
|
335
|
+
netuids if netuids else _prompt_claim_selection(claimable_stake_info)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
call = await meshtensor.substrate.compose_call(
|
|
339
|
+
call_module="MeshtensorModule",
|
|
340
|
+
call_function="claim_root",
|
|
341
|
+
call_params={"subnets": selected_netuids},
|
|
342
|
+
)
|
|
343
|
+
extrinsic_fee = await meshtensor.get_extrinsic_fee(
|
|
344
|
+
call, wallet.coldkeypub, proxy=proxy
|
|
345
|
+
)
|
|
346
|
+
console.print(
|
|
347
|
+
f"\n[dim]Estimated extrinsic fee: {extrinsic_fee.tao:.9f} τ"
|
|
348
|
+
+ (" (paid by signer account)" if proxy else "")
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if prompt:
|
|
352
|
+
if not confirm_action("Do you want to proceed?", decline=decline, quiet=quiet):
|
|
353
|
+
msg = "Operation cancelled by user"
|
|
354
|
+
console.print(f"[yellow]{msg}[/yellow]")
|
|
355
|
+
if json_output:
|
|
356
|
+
json_console.print(
|
|
357
|
+
json.dumps(
|
|
358
|
+
{
|
|
359
|
+
"success": False,
|
|
360
|
+
"message": msg,
|
|
361
|
+
"extrinsic_identifier": None,
|
|
362
|
+
"netuids": selected_netuids,
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
return False, msg, None
|
|
367
|
+
|
|
368
|
+
if not (unlock := unlock_key(wallet)).success:
|
|
369
|
+
msg = f"Failed to unlock wallet: {unlock.message}"
|
|
370
|
+
print_error(msg)
|
|
371
|
+
if json_output:
|
|
372
|
+
json_console.print(
|
|
373
|
+
json.dumps(
|
|
374
|
+
{
|
|
375
|
+
"success": False,
|
|
376
|
+
"message": msg,
|
|
377
|
+
"extrinsic_identifier": None,
|
|
378
|
+
"netuids": selected_netuids,
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
return False, msg, None
|
|
383
|
+
|
|
384
|
+
with console.status(
|
|
385
|
+
f":satellite: Claiming root emissions for {len(selected_netuids)} subnet(s)...",
|
|
386
|
+
spinner="earth",
|
|
387
|
+
):
|
|
388
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
389
|
+
call, wallet, proxy=proxy
|
|
390
|
+
)
|
|
391
|
+
if success:
|
|
392
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
393
|
+
msg = f"Successfully claimed root emissions for {len(selected_netuids)} subnet(s)"
|
|
394
|
+
console.print(f"[dark_sea_green3]{msg}[/dark_sea_green3]")
|
|
395
|
+
await print_extrinsic_id(ext_receipt)
|
|
396
|
+
if json_output:
|
|
397
|
+
json_console.print(
|
|
398
|
+
json.dumps(
|
|
399
|
+
{
|
|
400
|
+
"success": True,
|
|
401
|
+
"message": msg,
|
|
402
|
+
"extrinsic_identifier": ext_id,
|
|
403
|
+
"netuids": selected_netuids,
|
|
404
|
+
}
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
return True, msg, ext_id
|
|
408
|
+
else:
|
|
409
|
+
msg = f"Failed to claim root emissions: {err_msg}"
|
|
410
|
+
print_error(msg)
|
|
411
|
+
if json_output:
|
|
412
|
+
json_console.print(
|
|
413
|
+
json.dumps(
|
|
414
|
+
{
|
|
415
|
+
"success": False,
|
|
416
|
+
"message": msg,
|
|
417
|
+
"extrinsic_identifier": None,
|
|
418
|
+
"netuids": selected_netuids,
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
return False, msg, None
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _prompt_claim_selection(claimable_stake: dict) -> Optional[list[int]]:
|
|
426
|
+
"""Prompts user to select up to 5 netuids to claim from"""
|
|
427
|
+
|
|
428
|
+
available_netuids = sorted(claimable_stake.keys())
|
|
429
|
+
while True:
|
|
430
|
+
netuid_input = Prompt.ask(
|
|
431
|
+
"Enter up to 5 netuids to claim from (comma-separated)",
|
|
432
|
+
default=",".join(str(n) for n in available_netuids),
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
if "," in netuid_input:
|
|
437
|
+
selected = [int(n.strip()) for n in netuid_input.split(",")]
|
|
438
|
+
else:
|
|
439
|
+
selected = [int(netuid_input.strip())]
|
|
440
|
+
except ValueError:
|
|
441
|
+
print_error("Invalid input. Please enter numbers only.")
|
|
442
|
+
continue
|
|
443
|
+
|
|
444
|
+
if len(selected) > 5:
|
|
445
|
+
print_error(
|
|
446
|
+
f"You selected {len(selected)} netuids. Maximum is 5. Please try again."
|
|
447
|
+
)
|
|
448
|
+
continue
|
|
449
|
+
|
|
450
|
+
if len(selected) == 0:
|
|
451
|
+
print_error("Please select at least one netuid.")
|
|
452
|
+
continue
|
|
453
|
+
|
|
454
|
+
invalid_netuids = [n for n in selected if n not in available_netuids]
|
|
455
|
+
if invalid_netuids:
|
|
456
|
+
print_error(f"Invalid netuids: {', '.join(map(str, invalid_netuids))}")
|
|
457
|
+
continue
|
|
458
|
+
|
|
459
|
+
selected = list(dict.fromkeys(selected))
|
|
460
|
+
|
|
461
|
+
return selected
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _print_claimable_table(
|
|
465
|
+
wallet: Wallet, claimable_stake: dict, verbose: bool = False
|
|
466
|
+
):
|
|
467
|
+
"""Prints claimable stakes table grouped by netuid"""
|
|
468
|
+
|
|
469
|
+
table = Table(
|
|
470
|
+
show_header=True,
|
|
471
|
+
show_footer=False,
|
|
472
|
+
show_edge=True,
|
|
473
|
+
border_style="bright_black",
|
|
474
|
+
box=box.SIMPLE,
|
|
475
|
+
pad_edge=False,
|
|
476
|
+
title=f"\n[{COLORS.GENERAL.HEADER}]Claimable emissions for coldkey: {wallet.coldkeypub.ss58_address}",
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
table.add_column("Netuid", style=COLORS.GENERAL.NETUID, justify="center")
|
|
480
|
+
table.add_column("Current Stake", style=COLORS.GENERAL.SUBHEADING, justify="right")
|
|
481
|
+
table.add_column("Claimable", style=COLORS.GENERAL.SUCCESS, justify="right")
|
|
482
|
+
table.add_column("Hotkey", style=COLORS.GENERAL.HOTKEY, justify="left")
|
|
483
|
+
table.add_column("Identity", style=COLORS.GENERAL.SUBHEADING, justify="left")
|
|
484
|
+
|
|
485
|
+
for netuid in sorted(claimable_stake.keys()):
|
|
486
|
+
hotkeys_info = claimable_stake[netuid]
|
|
487
|
+
first_row = True
|
|
488
|
+
|
|
489
|
+
for hotkey, info in hotkeys_info.items():
|
|
490
|
+
hotkey_display = hotkey if verbose else f"{hotkey[:8]}...{hotkey[-8:]}"
|
|
491
|
+
netuid_display = str(netuid) if first_row else ""
|
|
492
|
+
|
|
493
|
+
stake_display = info["stake"]
|
|
494
|
+
stake_formatted = (
|
|
495
|
+
f"{stake_display.tao:.4f} {stake_display.unit}"
|
|
496
|
+
if verbose
|
|
497
|
+
else f"{millify_tao(stake_display.tao)} {stake_display.unit}"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
claimable_display = info["claimable"]
|
|
501
|
+
claimable_formatted = (
|
|
502
|
+
f"{claimable_display.tao:.4f} {claimable_display.unit}"
|
|
503
|
+
if verbose
|
|
504
|
+
else f"{millify_tao(claimable_display.tao)} {claimable_display.unit}"
|
|
505
|
+
)
|
|
506
|
+
table.add_row(
|
|
507
|
+
netuid_display,
|
|
508
|
+
stake_formatted,
|
|
509
|
+
claimable_formatted,
|
|
510
|
+
hotkey_display,
|
|
511
|
+
info.get("identity", "~"),
|
|
512
|
+
)
|
|
513
|
+
first_row = False
|
|
514
|
+
|
|
515
|
+
console.print(table)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
async def _ask_for_claim_types(
|
|
519
|
+
wallet: Wallet,
|
|
520
|
+
meshtensor: "MeshtensorInterface",
|
|
521
|
+
all_subnets: list,
|
|
522
|
+
decline: bool = False,
|
|
523
|
+
quiet: bool = False,
|
|
524
|
+
) -> Optional[dict]:
|
|
525
|
+
"""
|
|
526
|
+
Interactive prompts for claim type selection.
|
|
527
|
+
|
|
528
|
+
Flow:
|
|
529
|
+
1. Ask "Keep or Swap?"
|
|
530
|
+
2. Ask "All subnets?"
|
|
531
|
+
- If yes → return simple type (Keep or Swap)
|
|
532
|
+
- If no → enter subnet selection
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
dict: Selected claim type, or None if cancelled
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
console.print("\n")
|
|
539
|
+
console.print(
|
|
540
|
+
Panel(
|
|
541
|
+
f"[{COLORS.GENERAL.HEADER}]Root Claim Type Selection[/{COLORS.GENERAL.HEADER}]\n\n"
|
|
542
|
+
"Configure how your root network emissions are claimed.\n\n"
|
|
543
|
+
"[yellow]Options:[/yellow]\n"
|
|
544
|
+
" • [green]Swap[/green] - Convert emissions to MESH\n"
|
|
545
|
+
" • [green]Keep[/green] - Keep emissions as Alpha\n"
|
|
546
|
+
" • [green]Keep Specific[/green] - Keep selected subnets, swap others\n",
|
|
547
|
+
)
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
primary_choice = Prompt.ask(
|
|
551
|
+
"\nSelect new root claim type",
|
|
552
|
+
choices=["keep", "swap", "cancel"],
|
|
553
|
+
default="cancel",
|
|
554
|
+
)
|
|
555
|
+
if primary_choice == "cancel":
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
apply_to_all = confirm_action(
|
|
559
|
+
f"\nSet {primary_choice.capitalize()} to ALL subnets?",
|
|
560
|
+
default=True,
|
|
561
|
+
decline=decline,
|
|
562
|
+
quiet=quiet,
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
if apply_to_all:
|
|
566
|
+
return {"type": primary_choice.capitalize()}
|
|
567
|
+
|
|
568
|
+
if primary_choice == "keep":
|
|
569
|
+
console.print(
|
|
570
|
+
"\nYou can select which subnets to KEEP as Alpha (others will be swapped to MESH).\n"
|
|
571
|
+
)
|
|
572
|
+
else:
|
|
573
|
+
console.print(
|
|
574
|
+
"\nYou can select which subnets to SWAP to MESH (others will be kept as Alpha).\n"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
return await _prompt_claim_netuids(
|
|
578
|
+
wallet,
|
|
579
|
+
meshtensor,
|
|
580
|
+
all_subnets,
|
|
581
|
+
mode=primary_choice,
|
|
582
|
+
decline=decline,
|
|
583
|
+
quiet=quiet,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
async def _prompt_claim_netuids(
|
|
588
|
+
wallet: Wallet,
|
|
589
|
+
meshtensor: "MeshtensorInterface",
|
|
590
|
+
all_subnets: list,
|
|
591
|
+
mode: str = "keep",
|
|
592
|
+
decline: bool = False,
|
|
593
|
+
quiet: bool = False,
|
|
594
|
+
) -> Optional[dict]:
|
|
595
|
+
"""
|
|
596
|
+
Interactive subnet selection.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
mode: "keep" to select subnets to keep as Alpha, "swap" to select subnets to swap to MESH
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
dict: KeepSubnets claim type or None if cancelled
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
if not all_subnets:
|
|
606
|
+
console.print("[yellow]No subnets available.[/yellow]")
|
|
607
|
+
return {"type": "Swap"}
|
|
608
|
+
|
|
609
|
+
if mode == "keep":
|
|
610
|
+
action = "KEEP as Alpha"
|
|
611
|
+
else:
|
|
612
|
+
action = "SWAP to MESH"
|
|
613
|
+
|
|
614
|
+
console.print(
|
|
615
|
+
Panel(
|
|
616
|
+
f"[{COLORS.GENERAL.HEADER}]Subnet Selection[/{COLORS.GENERAL.HEADER}]\n\n"
|
|
617
|
+
f"[bold]Available subnets:[/bold] {group_subnets(sorted(all_subnets))}\n"
|
|
618
|
+
f"[dim]Total: {len(all_subnets)} subnets[/dim]\n\n"
|
|
619
|
+
"[yellow]Input examples:[/yellow]\n"
|
|
620
|
+
" • [cyan]1-10[/cyan] - Range from 1 to 10\n"
|
|
621
|
+
" • [cyan]1, 5, 10[/cyan] - Specific subnets\n"
|
|
622
|
+
" • [cyan]1-10, 20-30, 50[/cyan] - Mixed"
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
while True:
|
|
627
|
+
subnet_input = Prompt.ask(
|
|
628
|
+
f"\nEnter subnets to {action} [dim]{group_subnets(sorted(all_subnets))}",
|
|
629
|
+
default="",
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
if not subnet_input.strip():
|
|
633
|
+
print_error("No subnets entered. Please try again.")
|
|
634
|
+
continue
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
selected = parse_subnet_range(subnet_input, total_subnets=len(all_subnets))
|
|
638
|
+
invalid = [s for s in selected if s not in all_subnets]
|
|
639
|
+
if invalid:
|
|
640
|
+
print_error(
|
|
641
|
+
f"Invalid subnets (not available): {group_subnets(invalid)}"
|
|
642
|
+
)
|
|
643
|
+
print_error("[yellow]Please try again.[/yellow]")
|
|
644
|
+
continue
|
|
645
|
+
|
|
646
|
+
if mode == "keep":
|
|
647
|
+
keep_subnets = selected
|
|
648
|
+
else:
|
|
649
|
+
keep_subnets = [n for n in all_subnets if n not in selected]
|
|
650
|
+
|
|
651
|
+
if _preview_subnet_selection(
|
|
652
|
+
keep_subnets, all_subnets, decline=decline, quiet=quiet
|
|
653
|
+
):
|
|
654
|
+
if not keep_subnets:
|
|
655
|
+
return {"type": "Swap"}
|
|
656
|
+
elif set(keep_subnets) == set(all_subnets):
|
|
657
|
+
return {"type": "Keep"}
|
|
658
|
+
else:
|
|
659
|
+
return {"type": "KeepSubnets", "subnets": keep_subnets}
|
|
660
|
+
else:
|
|
661
|
+
console.print(
|
|
662
|
+
"[yellow]Selection cancelled. Starting over...[/yellow]\n"
|
|
663
|
+
)
|
|
664
|
+
return await _prompt_claim_netuids(
|
|
665
|
+
wallet, meshtensor, all_subnets, mode=mode
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
except ValueError as e:
|
|
669
|
+
print_error(f"Invalid subnet selection: {e}\nPlease try again.")
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _preview_subnet_selection(
|
|
673
|
+
keep_subnets: list[int],
|
|
674
|
+
all_subnets: list[int],
|
|
675
|
+
decline: bool = False,
|
|
676
|
+
quiet: bool = False,
|
|
677
|
+
) -> bool:
|
|
678
|
+
"""Show preview and ask for confirmation."""
|
|
679
|
+
|
|
680
|
+
swap_subnets = [n for n in all_subnets if n not in keep_subnets]
|
|
681
|
+
preview_content = (
|
|
682
|
+
f"[{COLORS.GENERAL.HEADER}]Preview Your Selection[/{COLORS.GENERAL.HEADER}]\n\n"
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
if keep_subnets:
|
|
686
|
+
preview_content += (
|
|
687
|
+
f"[green]✓ Keep as Alpha:[/green] {group_subnets(keep_subnets)}\n"
|
|
688
|
+
f"[dim] ({len(keep_subnets)} subnet{'s' if len(keep_subnets) != 1 else ''})[/dim]"
|
|
689
|
+
)
|
|
690
|
+
else:
|
|
691
|
+
preview_content += "[dim]No subnets kept as Alpha[/dim]"
|
|
692
|
+
|
|
693
|
+
if swap_subnets:
|
|
694
|
+
preview_content += (
|
|
695
|
+
f"\n\n[yellow]⟳ Swap to MESH:[/yellow] {group_subnets(swap_subnets)}\n"
|
|
696
|
+
f"[dim] ({len(swap_subnets)} subnet{'s' if len(swap_subnets) != 1 else ''})[/dim]"
|
|
697
|
+
)
|
|
698
|
+
else:
|
|
699
|
+
preview_content += "\n\n[dim]No subnets swapped to MESH[/dim]"
|
|
700
|
+
|
|
701
|
+
console.print(Panel(preview_content))
|
|
702
|
+
|
|
703
|
+
return confirm_action(
|
|
704
|
+
"\nIs this correct?", default=True, decline=decline, quiet=quiet
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _format_claim_type_display(
|
|
709
|
+
claim_info: dict, all_subnets: Optional[list[int]] = None
|
|
710
|
+
) -> str:
|
|
711
|
+
"""
|
|
712
|
+
Format claim type for human-readable display.
|
|
713
|
+
|
|
714
|
+
Args:
|
|
715
|
+
claim_info: Claim type information dict
|
|
716
|
+
all_subnets: Optional list of all available subnets (for showing swap info)
|
|
717
|
+
"""
|
|
718
|
+
|
|
719
|
+
claim_type = claim_info["type"]
|
|
720
|
+
if claim_type == "Swap":
|
|
721
|
+
return "[yellow]Swap All[/yellow]"
|
|
722
|
+
|
|
723
|
+
elif claim_type == "Keep":
|
|
724
|
+
return "[dark_sea_green3]Keep All[/dark_sea_green3]"
|
|
725
|
+
|
|
726
|
+
elif claim_type == "KeepSubnets":
|
|
727
|
+
subnets = claim_info["subnets"]
|
|
728
|
+
subnet_display = group_subnets(subnets)
|
|
729
|
+
|
|
730
|
+
result = (
|
|
731
|
+
f"[cyan]Keep Specific[/cyan]\n[green] ✓ Keep:[/green] {subnet_display}"
|
|
732
|
+
)
|
|
733
|
+
if all_subnets:
|
|
734
|
+
swap_subnets = [n for n in all_subnets if n not in subnets]
|
|
735
|
+
if swap_subnets:
|
|
736
|
+
swap_display = group_subnets(swap_subnets)
|
|
737
|
+
result += f"\n[yellow] ⟳ Swap:[/yellow] {swap_display}"
|
|
738
|
+
|
|
739
|
+
return result
|
|
740
|
+
else:
|
|
741
|
+
return "[red]Unknown[/red]"
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _claim_types_equal(claim1: dict, claim2: dict) -> bool:
|
|
745
|
+
"""Check if two claim type configs are equivalent."""
|
|
746
|
+
|
|
747
|
+
if claim1["type"] != claim2["type"]:
|
|
748
|
+
return False
|
|
749
|
+
|
|
750
|
+
if claim1["type"] == "KeepSubnets":
|
|
751
|
+
subnets1 = sorted(claim1.get("subnets", []))
|
|
752
|
+
subnets2 = sorted(claim2.get("subnets", []))
|
|
753
|
+
return subnets1 == subnets2
|
|
754
|
+
|
|
755
|
+
return True
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def _prepare_claim_type_args(claim_info: dict) -> dict:
|
|
759
|
+
"""Convert claim type arguments for chain call"""
|
|
760
|
+
|
|
761
|
+
claim_type = claim_info["type"]
|
|
762
|
+
if claim_type == "Swap":
|
|
763
|
+
return {"Swap": None}
|
|
764
|
+
elif claim_type == "Keep":
|
|
765
|
+
return {"Keep": None}
|
|
766
|
+
elif claim_type == "KeepSubnets":
|
|
767
|
+
subnets = claim_info["subnets"]
|
|
768
|
+
return {"KeepSubnets": {"subnets": subnets}}
|
|
769
|
+
else:
|
|
770
|
+
raise ValueError(f"Unknown claim type: {claim_type}")
|