bittensor-cli 8.4.3__py3-none-any.whl → 9.0.0rc2__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 (30) hide show
  1. bittensor_cli/__init__.py +2 -2
  2. bittensor_cli/cli.py +1508 -1385
  3. bittensor_cli/src/__init__.py +627 -197
  4. bittensor_cli/src/bittensor/balances.py +41 -8
  5. bittensor_cli/src/bittensor/chain_data.py +557 -428
  6. bittensor_cli/src/bittensor/extrinsics/registration.py +161 -47
  7. bittensor_cli/src/bittensor/extrinsics/root.py +14 -8
  8. bittensor_cli/src/bittensor/extrinsics/transfer.py +14 -21
  9. bittensor_cli/src/bittensor/minigraph.py +46 -8
  10. bittensor_cli/src/bittensor/subtensor_interface.py +572 -253
  11. bittensor_cli/src/bittensor/utils.py +326 -75
  12. bittensor_cli/src/commands/stake/__init__.py +154 -0
  13. bittensor_cli/src/commands/stake/children_hotkeys.py +121 -87
  14. bittensor_cli/src/commands/stake/move.py +1000 -0
  15. bittensor_cli/src/commands/stake/stake.py +1637 -1264
  16. bittensor_cli/src/commands/subnets/__init__.py +0 -0
  17. bittensor_cli/src/commands/subnets/price.py +867 -0
  18. bittensor_cli/src/commands/subnets/subnets.py +2055 -0
  19. bittensor_cli/src/commands/sudo.py +529 -26
  20. bittensor_cli/src/commands/wallets.py +234 -544
  21. bittensor_cli/src/commands/weights.py +15 -11
  22. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/METADATA +7 -4
  23. bittensor_cli-9.0.0rc2.dist-info/RECORD +32 -0
  24. bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
  25. bittensor_cli/src/commands/root.py +0 -1752
  26. bittensor_cli/src/commands/subnets.py +0 -897
  27. bittensor_cli-8.4.3.dist-info/RECORD +0 -31
  28. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/WHEEL +0 -0
  29. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/entry_points.txt +0 -0
  30. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1000 @@
1
+ import asyncio
2
+
3
+ from typing import TYPE_CHECKING
4
+ import typer
5
+
6
+ from bittensor_wallet import Wallet
7
+ from bittensor_wallet.errors import KeyFileError
8
+ from rich.table import Table
9
+ from rich.prompt import Confirm, Prompt
10
+
11
+ from bittensor_cli.src import COLOR_PALETTE
12
+ from bittensor_cli.src.bittensor.balances import Balance
13
+ from bittensor_cli.src.bittensor.utils import (
14
+ console,
15
+ err_console,
16
+ print_error,
17
+ format_error_message,
18
+ group_subnets,
19
+ get_subnet_name,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
24
+
25
+ MIN_STAKE_FEE = Balance.from_rao(50_000)
26
+
27
+
28
+ # Helpers
29
+ async def display_stake_movement_cross_subnets(
30
+ subtensor: "SubtensorInterface",
31
+ origin_netuid: int,
32
+ destination_netuid: int,
33
+ origin_hotkey: str,
34
+ destination_hotkey: str,
35
+ amount_to_move: Balance,
36
+ ) -> tuple[Balance, float, str, str]:
37
+ """Calculate and display slippage information"""
38
+
39
+ if origin_netuid == destination_netuid:
40
+ subnet = await subtensor.subnet(origin_netuid)
41
+ received_amount_tao = subnet.alpha_to_tao(amount_to_move)
42
+ received_amount_tao -= MIN_STAKE_FEE
43
+ received_amount = subnet.tao_to_alpha(received_amount_tao)
44
+ slippage_pct_float = (
45
+ 100 * float(MIN_STAKE_FEE) / float(MIN_STAKE_FEE + received_amount_tao)
46
+ if received_amount_tao != 0
47
+ else 0
48
+ )
49
+ slippage_pct = f"{slippage_pct_float:.4f}%"
50
+ price = Balance.from_tao(1).set_unit(origin_netuid)
51
+ price_str = (
52
+ str(float(price.tao))
53
+ + f"{Balance.get_unit(origin_netuid)}/{Balance.get_unit(origin_netuid)}"
54
+ )
55
+ else:
56
+ dynamic_origin, dynamic_destination = await asyncio.gather(
57
+ subtensor.subnet(origin_netuid),
58
+ subtensor.subnet(destination_netuid),
59
+ )
60
+ price = (
61
+ float(dynamic_origin.price) * 1 / (float(dynamic_destination.price) or 1)
62
+ )
63
+ received_amount_tao, _, _ = dynamic_origin.alpha_to_tao_with_slippage(
64
+ amount_to_move
65
+ )
66
+ received_amount_tao -= MIN_STAKE_FEE
67
+ received_amount, _, _ = dynamic_destination.tao_to_alpha_with_slippage(
68
+ received_amount_tao
69
+ )
70
+ received_amount.set_unit(destination_netuid)
71
+
72
+ if received_amount < Balance.from_tao(0):
73
+ print_error("Not enough Alpha to pay the transaction fee.")
74
+ raise typer.Exit()
75
+
76
+ ideal_amount = amount_to_move * price
77
+ total_slippage = ideal_amount - received_amount
78
+ slippage_pct_float = 100 * (total_slippage.tao / ideal_amount.tao)
79
+ slippage_pct = f"{slippage_pct_float:.4f} %"
80
+ price_str = (
81
+ f"{price:.5f}"
82
+ + f"{Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)}"
83
+ )
84
+
85
+ # Create and display table
86
+ table = Table(
87
+ title=(
88
+ f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]"
89
+ f"Moving stake from: "
90
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})"
91
+ f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
92
+ f"to: "
93
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})"
94
+ f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\nNetwork: {subtensor.network}\n"
95
+ f"[/{COLOR_PALETTE['GENERAL']['HEADER']}]"
96
+ ),
97
+ show_footer=True,
98
+ show_edge=False,
99
+ header_style="bold white",
100
+ border_style="bright_black",
101
+ style="bold",
102
+ title_justify="center",
103
+ show_lines=False,
104
+ pad_edge=True,
105
+ )
106
+
107
+ table.add_column(
108
+ "origin netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]
109
+ )
110
+ table.add_column(
111
+ "origin hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
112
+ )
113
+ table.add_column(
114
+ "dest netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]
115
+ )
116
+ table.add_column(
117
+ "dest hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
118
+ )
119
+ table.add_column(
120
+ f"amount ({Balance.get_unit(origin_netuid)})",
121
+ justify="center",
122
+ style=COLOR_PALETTE["STAKE"]["TAO"],
123
+ )
124
+ table.add_column(
125
+ f"rate ({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})",
126
+ justify="center",
127
+ style=COLOR_PALETTE["POOLS"]["RATE"],
128
+ )
129
+ table.add_column(
130
+ f"received ({Balance.get_unit(destination_netuid)})",
131
+ justify="center",
132
+ style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
133
+ )
134
+ table.add_column(
135
+ "slippage",
136
+ justify="center",
137
+ style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"],
138
+ )
139
+
140
+ table.add_row(
141
+ f"{Balance.get_unit(origin_netuid)}({origin_netuid})",
142
+ f"{origin_hotkey[:3]}...{origin_hotkey[-3:]}",
143
+ f"{Balance.get_unit(destination_netuid)}({destination_netuid})",
144
+ f"{destination_hotkey[:3]}...{destination_hotkey[-3:]}",
145
+ str(amount_to_move),
146
+ price_str,
147
+ str(received_amount),
148
+ str(slippage_pct),
149
+ )
150
+
151
+ console.print(table)
152
+ # console.print(
153
+ # f"[dim]A fee of {MIN_STAKE_FEE} applies.[/dim]"
154
+ # )
155
+
156
+ # Display slippage warning if necessary
157
+ if slippage_pct_float > 5:
158
+ message = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
159
+ message += f"[bold]WARNING:\tSlippage is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_pct}[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.[/bold] \n"
160
+ message += "-------------------------------------------------------------------------------------------------------------------\n"
161
+ console.print(message)
162
+
163
+ return received_amount, slippage_pct_float, slippage_pct, price_str
164
+
165
+
166
+ def prompt_stake_amount(
167
+ current_balance: Balance, netuid: int, action_name: str
168
+ ) -> tuple[Balance, bool]:
169
+ """Prompts user to input a stake amount with validation.
170
+
171
+ Args:
172
+ current_balance (Balance): The maximum available balance
173
+ netuid (int): The subnet id to get the correct unit
174
+ action_name (str): The name of the action (e.g. "transfer", "move", "unstake")
175
+
176
+ Returns:
177
+ tuple[Balance, bool]: (The amount to use as Balance object, whether all balance was selected)
178
+ """
179
+ while True:
180
+ amount_input = Prompt.ask(
181
+ f"\nEnter amount to {action_name} from "
182
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
183
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}](max: {current_balance})[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
184
+ f"or "
185
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]'all'[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
186
+ f"for entire balance"
187
+ )
188
+
189
+ if amount_input.lower() == "all":
190
+ return current_balance, True
191
+
192
+ try:
193
+ amount = float(amount_input)
194
+ if amount <= 0:
195
+ console.print("[red]Amount must be greater than 0[/red]")
196
+ continue
197
+ if amount > current_balance.tao:
198
+ console.print(
199
+ f"[red]Amount exceeds available balance of "
200
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
201
+ f"[/red]"
202
+ )
203
+ continue
204
+ return Balance.from_tao(amount), False
205
+ except ValueError:
206
+ console.print("[red]Please enter a valid number or 'all'[/red]")
207
+
208
+
209
+ async def stake_move_selection(
210
+ subtensor: "SubtensorInterface",
211
+ wallet: Wallet,
212
+ ):
213
+ """Selection interface for moving stakes between hotkeys and subnets."""
214
+ stakes, ck_hk_identities, old_identities = await asyncio.gather(
215
+ subtensor.get_stake_for_coldkey(coldkey_ss58=wallet.coldkeypub.ss58_address),
216
+ subtensor.fetch_coldkey_hotkey_identities(),
217
+ subtensor.get_delegate_identities(),
218
+ )
219
+
220
+ hotkey_stakes = {}
221
+ for stake in stakes:
222
+ if stake.stake.tao > 0:
223
+ hotkey = stake.hotkey_ss58
224
+ netuid = stake.netuid
225
+ stake_balance = stake.stake
226
+ hotkey_stakes.setdefault(hotkey, {})[netuid] = stake_balance
227
+
228
+ if not hotkey_stakes:
229
+ print_error("You have no stakes to move.")
230
+ raise typer.Exit()
231
+
232
+ # Display hotkeys with stakes
233
+ table = Table(
234
+ title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes\n",
235
+ show_footer=True,
236
+ show_edge=False,
237
+ header_style="bold white",
238
+ border_style="bright_black",
239
+ style="bold",
240
+ title_justify="center",
241
+ show_lines=False,
242
+ pad_edge=True,
243
+ )
244
+ table.add_column("Index", justify="right")
245
+ table.add_column("Identity", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"])
246
+ table.add_column("Netuids", style=COLOR_PALETTE["GENERAL"]["NETUID"])
247
+ table.add_column("Hotkey Address", style=COLOR_PALETTE["GENERAL"]["HOTKEY"])
248
+
249
+ hotkeys_info = []
250
+ for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()):
251
+ if hk_identity := ck_hk_identities["hotkeys"].get(hotkey_ss58):
252
+ hotkey_name = hk_identity.get("identity", {}).get(
253
+ "name", ""
254
+ ) or hk_identity.get("display", "~")
255
+ elif old_identity := old_identities.get(hotkey_ss58):
256
+ hotkey_name = old_identity.display
257
+ else:
258
+ hotkey_name = "~"
259
+ hotkeys_info.append(
260
+ {
261
+ "index": idx,
262
+ "identity": hotkey_name,
263
+ "hotkey_ss58": hotkey_ss58,
264
+ "netuids": list(netuid_stakes.keys()),
265
+ "stakes": netuid_stakes,
266
+ }
267
+ )
268
+ table.add_row(
269
+ str(idx),
270
+ hotkey_name,
271
+ group_subnets([n for n in netuid_stakes.keys()]),
272
+ hotkey_ss58,
273
+ )
274
+
275
+ console.print("\n", table)
276
+
277
+ # Select origin hotkey
278
+ origin_idx = Prompt.ask(
279
+ "\nEnter the index of the hotkey you want to move stake from",
280
+ choices=[str(i) for i in range(len(hotkeys_info))],
281
+ )
282
+ origin_hotkey_info = hotkeys_info[int(origin_idx)]
283
+ origin_hotkey_ss58 = origin_hotkey_info["hotkey_ss58"]
284
+
285
+ # Display available netuids for selected hotkey
286
+ table = Table(
287
+ title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Available Stakes for Hotkey\n[/{COLOR_PALETTE['GENERAL']['HEADER']}]"
288
+ f"[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{origin_hotkey_ss58}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n",
289
+ show_edge=False,
290
+ header_style="bold white",
291
+ border_style="bright_black",
292
+ title_justify="center",
293
+ width=len(origin_hotkey_ss58) + 20,
294
+ )
295
+ table.add_column("Index", justify="right")
296
+ table.add_column("Netuid", style="cyan")
297
+ table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
298
+
299
+ available_netuids = []
300
+ for idx, netuid in enumerate(origin_hotkey_info["netuids"]):
301
+ stake = origin_hotkey_info["stakes"][netuid]
302
+ if stake.tao > 0:
303
+ available_netuids.append(netuid)
304
+ table.add_row(str(idx), str(netuid), str(stake))
305
+
306
+ console.print("\n", table)
307
+
308
+ # Select origin netuid
309
+ netuid_idx = Prompt.ask(
310
+ "\nEnter the index of the subnet you want to move stake from",
311
+ choices=[str(i) for i in range(len(available_netuids))],
312
+ )
313
+ origin_netuid = available_netuids[int(netuid_idx)]
314
+ origin_stake = origin_hotkey_info["stakes"][origin_netuid]
315
+
316
+ # Ask for amount to move
317
+ amount, stake_all = prompt_stake_amount(origin_stake, origin_netuid, "move")
318
+
319
+ all_subnets = sorted(await subtensor.get_all_subnet_netuids())
320
+ destination_netuid = Prompt.ask(
321
+ "\nEnter the netuid of the subnet you want to move stake to"
322
+ + f" ([dim]{group_subnets(all_subnets)}[/dim])",
323
+ choices=[str(netuid) for netuid in all_subnets],
324
+ show_choices=False,
325
+ )
326
+
327
+ return {
328
+ "origin_hotkey": origin_hotkey_ss58,
329
+ "origin_netuid": origin_netuid,
330
+ "amount": amount.tao,
331
+ "stake_all": stake_all,
332
+ "destination_netuid": int(destination_netuid),
333
+ }
334
+
335
+
336
+ async def stake_transfer_selection(
337
+ wallet: Wallet,
338
+ subtensor: "SubtensorInterface",
339
+ ):
340
+ """Selection interface for transferring stakes."""
341
+ (
342
+ stakes,
343
+ all_netuids,
344
+ all_subnets,
345
+ ) = await asyncio.gather(
346
+ subtensor.get_stake_for_coldkey(coldkey_ss58=wallet.coldkeypub.ss58_address),
347
+ subtensor.get_all_subnet_netuids(),
348
+ subtensor.all_subnets(),
349
+ )
350
+ all_netuids = sorted(all_netuids)
351
+ all_subnets = {di.netuid: di for di in all_subnets}
352
+
353
+ available_stakes = {}
354
+ for stake in stakes:
355
+ if stake.stake.tao > 0 and stake.hotkey_ss58 == wallet.hotkey.ss58_address:
356
+ available_stakes[stake.netuid] = {
357
+ "hotkey_ss58": stake.hotkey_ss58,
358
+ "stake": stake.stake,
359
+ "is_registered": stake.is_registered,
360
+ }
361
+
362
+ if not available_stakes:
363
+ console.print("[red]No stakes available to transfer.[/red]")
364
+ return None
365
+
366
+ table = Table(
367
+ title=(
368
+ f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]"
369
+ f"Available Stakes to Transfer\n"
370
+ f"for wallet hotkey:\n"
371
+ f"[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey_str}: {wallet.hotkey.ss58_address}"
372
+ f"[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n"
373
+ ),
374
+ show_edge=False,
375
+ header_style="bold white",
376
+ border_style="bright_black",
377
+ title_justify="center",
378
+ width=len(wallet.hotkey_str + wallet.hotkey.ss58_address) + 10,
379
+ )
380
+
381
+ table.add_column("Index", justify="right", style="cyan")
382
+ table.add_column("Netuid")
383
+ table.add_column("Name", style="cyan", justify="left")
384
+ table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
385
+ table.add_column("Registered", justify="center")
386
+
387
+ for idx, (netuid, stake_info) in enumerate(available_stakes.items()):
388
+ subnet_name_cell = (
389
+ f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{all_subnets[netuid].symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
390
+ f" {get_subnet_name(all_subnets[netuid])}"
391
+ )
392
+ table.add_row(
393
+ str(idx),
394
+ str(netuid),
395
+ subnet_name_cell,
396
+ str(stake_info["stake"]),
397
+ "[dark_sea_green3]YES"
398
+ if stake_info["is_registered"]
399
+ else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO",
400
+ )
401
+
402
+ console.print(table)
403
+
404
+ if not available_stakes:
405
+ console.print("[red]No stakes available to transfer.[/red]")
406
+ return None
407
+
408
+ # Prompt to select index of stake to transfer
409
+ selection = Prompt.ask(
410
+ "\nEnter the index of the stake you want to transfer",
411
+ choices=[str(i) for i in range(len(available_stakes))],
412
+ )
413
+ selected_netuid = list(available_stakes.keys())[int(selection)]
414
+ selected_stake = available_stakes[selected_netuid]
415
+
416
+ # Prompt for amount
417
+ stake_balance = selected_stake["stake"]
418
+ amount, _ = prompt_stake_amount(stake_balance, selected_netuid, "transfer")
419
+
420
+ # Prompt for destination subnet
421
+ destination_netuid = Prompt.ask(
422
+ "\nEnter the netuid of the subnet you want to move stake to"
423
+ + f" ([dim]{group_subnets(all_netuids)}[/dim])",
424
+ choices=[str(netuid) for netuid in all_netuids],
425
+ show_choices=False,
426
+ )
427
+
428
+ return {
429
+ "origin_netuid": selected_netuid,
430
+ "amount": amount.tao,
431
+ "destination_netuid": int(destination_netuid),
432
+ }
433
+
434
+
435
+ async def stake_swap_selection(
436
+ subtensor: "SubtensorInterface",
437
+ wallet: Wallet,
438
+ ) -> dict:
439
+ """Selection interface for swapping stakes between subnets."""
440
+ block_hash = await subtensor.substrate.get_chain_head()
441
+ stakes, all_subnets = await asyncio.gather(
442
+ subtensor.get_stake_for_coldkey(
443
+ coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash
444
+ ),
445
+ subtensor.all_subnets(block_hash=block_hash),
446
+ )
447
+ subnet_dict = {di.netuid: di for di in all_subnets}
448
+
449
+ # Filter stakes for this hotkey
450
+ hotkey_stakes = {}
451
+ for stake in stakes:
452
+ if stake.hotkey_ss58 == wallet.hotkey.ss58_address and stake.stake.tao > 0:
453
+ hotkey_stakes[stake.netuid] = {
454
+ "stake": stake.stake,
455
+ "is_registered": stake.is_registered,
456
+ }
457
+
458
+ if not hotkey_stakes:
459
+ print_error(f"No stakes found for hotkey: {wallet.hotkey_str}")
460
+ raise typer.Exit()
461
+
462
+ # Display available stakes
463
+ table = Table(
464
+ title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Available Stakes for Hotkey\n[/{COLOR_PALETTE['GENERAL']['HEADER']}]"
465
+ f"[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey_str}: {wallet.hotkey.ss58_address}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n",
466
+ show_edge=False,
467
+ header_style="bold white",
468
+ border_style="bright_black",
469
+ title_justify="center",
470
+ width=len(wallet.hotkey.ss58_address) + 20,
471
+ )
472
+
473
+ table.add_column("Index", justify="right", style="cyan")
474
+ table.add_column("Netuid", style=COLOR_PALETTE["GENERAL"]["NETUID"])
475
+ table.add_column("Name", style="cyan", justify="left")
476
+ table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
477
+ table.add_column("Registered", justify="center")
478
+
479
+ available_netuids = []
480
+ for idx, (netuid, stake_info) in enumerate(sorted(hotkey_stakes.items())):
481
+ subnet_info = subnet_dict[netuid]
482
+ subnet_name_cell = (
483
+ f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{subnet_info.symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
484
+ f" {get_subnet_name(subnet_info)}"
485
+ )
486
+
487
+ available_netuids.append(netuid)
488
+ table.add_row(
489
+ str(idx),
490
+ str(netuid),
491
+ subnet_name_cell,
492
+ str(stake_info["stake"]),
493
+ "[dark_sea_green3]YES"
494
+ if stake_info["is_registered"]
495
+ else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO",
496
+ )
497
+
498
+ console.print("\n", table)
499
+
500
+ # Select origin netuid
501
+ origin_idx = Prompt.ask(
502
+ "\nEnter the index of the subnet you want to swap stake from",
503
+ choices=[str(i) for i in range(len(available_netuids))],
504
+ )
505
+ origin_netuid = available_netuids[int(origin_idx)]
506
+ origin_stake = hotkey_stakes[origin_netuid]["stake"]
507
+
508
+ # Ask for amount to swap
509
+ amount, all_balance = prompt_stake_amount(origin_stake, origin_netuid, "swap")
510
+
511
+ all_netuids = sorted(await subtensor.get_all_subnet_netuids())
512
+ destination_choices = [
513
+ str(netuid) for netuid in all_netuids if netuid != origin_netuid
514
+ ]
515
+ destination_netuid = Prompt.ask(
516
+ "\nEnter the netuid of the subnet you want to swap stake to"
517
+ + f" ([dim]{group_subnets(all_netuids)}[/dim])",
518
+ choices=destination_choices,
519
+ show_choices=False,
520
+ )
521
+
522
+ return {
523
+ "origin_netuid": origin_netuid,
524
+ "amount": amount.tao,
525
+ "destination_netuid": int(destination_netuid),
526
+ }
527
+
528
+
529
+ # Commands
530
+ async def move_stake(
531
+ subtensor: "SubtensorInterface",
532
+ wallet: Wallet,
533
+ origin_netuid: int,
534
+ origin_hotkey: str,
535
+ destination_netuid: int,
536
+ destination_hotkey: str,
537
+ amount: float,
538
+ stake_all: bool,
539
+ interactive_selection: bool = False,
540
+ prompt: bool = True,
541
+ ):
542
+ if interactive_selection:
543
+ selection = await stake_move_selection(subtensor, wallet)
544
+ origin_hotkey = selection["origin_hotkey"]
545
+ origin_netuid = selection["origin_netuid"]
546
+ amount = selection["amount"]
547
+ stake_all = selection["stake_all"]
548
+ destination_netuid = selection["destination_netuid"]
549
+
550
+ # Get the wallet stake balances.
551
+ block_hash = await subtensor.substrate.get_chain_head()
552
+ origin_stake_balance, destination_stake_balance = await asyncio.gather(
553
+ subtensor.get_stake(
554
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
555
+ hotkey_ss58=origin_hotkey,
556
+ netuid=origin_netuid,
557
+ block_hash=block_hash,
558
+ ),
559
+ subtensor.get_stake(
560
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
561
+ hotkey_ss58=destination_hotkey,
562
+ netuid=destination_netuid,
563
+ block_hash=block_hash,
564
+ ),
565
+ )
566
+
567
+ if origin_stake_balance.tao == 0:
568
+ print_error(
569
+ f"Your balance is "
570
+ f"[{COLOR_PALETTE['POOLS']['TAO']}]0[/{COLOR_PALETTE['POOLS']['TAO']}] "
571
+ f"in Netuid: "
572
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
573
+ )
574
+ raise typer.Exit()
575
+
576
+ console.print(
577
+ f"\nOrigin Netuid: "
578
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], "
579
+ f"Origin stake: "
580
+ f"[{COLOR_PALETTE['POOLS']['TAO']}]{origin_stake_balance}[/{COLOR_PALETTE['POOLS']['TAO']}]"
581
+ )
582
+ console.print(
583
+ f"Destination netuid: "
584
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{destination_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], "
585
+ f"Destination stake: "
586
+ f"[{COLOR_PALETTE['POOLS']['TAO']}]{destination_stake_balance}[/{COLOR_PALETTE['POOLS']['TAO']}]\n"
587
+ )
588
+
589
+ # Determine the amount we are moving.
590
+ amount_to_move_as_balance = None
591
+ if amount:
592
+ amount_to_move_as_balance = Balance.from_tao(amount)
593
+ elif stake_all:
594
+ amount_to_move_as_balance = origin_stake_balance
595
+ else:
596
+ amount_to_move_as_balance, _ = prompt_stake_amount(
597
+ origin_stake_balance, origin_netuid, "move"
598
+ )
599
+
600
+ # Check enough to move.
601
+ amount_to_move_as_balance.set_unit(origin_netuid)
602
+ if amount_to_move_as_balance > origin_stake_balance:
603
+ err_console.print(
604
+ f"[red]Not enough stake[/red]:\n"
605
+ f" Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
606
+ f" < Moving amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_move_as_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
607
+ )
608
+ return False
609
+
610
+ # Slippage warning
611
+ if prompt:
612
+ await display_stake_movement_cross_subnets(
613
+ subtensor=subtensor,
614
+ origin_netuid=origin_netuid,
615
+ destination_netuid=destination_netuid,
616
+ origin_hotkey=origin_hotkey,
617
+ destination_hotkey=destination_hotkey,
618
+ amount_to_move=amount_to_move_as_balance,
619
+ )
620
+ if not Confirm.ask("Would you like to continue?"):
621
+ raise typer.Exit()
622
+
623
+ # Perform moving operation.
624
+ try:
625
+ wallet.unlock_coldkey()
626
+ except KeyFileError:
627
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
628
+ return False
629
+ with console.status(
630
+ f"\n:satellite: Moving [blue]{amount_to_move_as_balance}[/blue] from [blue]{origin_hotkey}[/blue] on netuid: [blue]{origin_netuid}[/blue] \nto "
631
+ f"[blue]{destination_hotkey}[/blue] on netuid: [blue]{destination_netuid}[/blue] ..."
632
+ ):
633
+ call = await subtensor.substrate.compose_call(
634
+ call_module="SubtensorModule",
635
+ call_function="move_stake",
636
+ call_params={
637
+ "origin_hotkey": origin_hotkey,
638
+ "origin_netuid": origin_netuid,
639
+ "destination_hotkey": destination_hotkey,
640
+ "destination_netuid": destination_netuid,
641
+ "alpha_amount": amount_to_move_as_balance.rao,
642
+ },
643
+ )
644
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
645
+ call=call, keypair=wallet.coldkey
646
+ )
647
+ response = await subtensor.substrate.submit_extrinsic(
648
+ extrinsic, wait_for_inclusion=True, wait_for_finalization=False
649
+ )
650
+
651
+ if not prompt:
652
+ console.print(":white_heavy_check_mark: [green]Sent[/green]")
653
+ return True
654
+ else:
655
+ await response.process_events()
656
+ if not await response.is_success:
657
+ err_console.print(
658
+ f"\n:cross_mark: [red]Failed[/red] with error:"
659
+ f" {format_error_message(await response.error_message)}"
660
+ )
661
+ return
662
+ else:
663
+ console.print(
664
+ ":white_heavy_check_mark: [dark_sea_green3]Stake moved.[/dark_sea_green3]"
665
+ )
666
+ block_hash = await subtensor.substrate.get_chain_head()
667
+ (
668
+ new_origin_stake_balance,
669
+ new_destination_stake_balance,
670
+ ) = await asyncio.gather(
671
+ subtensor.get_stake(
672
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
673
+ hotkey_ss58=origin_hotkey,
674
+ netuid=origin_netuid,
675
+ block_hash=block_hash,
676
+ ),
677
+ subtensor.get_stake(
678
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
679
+ hotkey_ss58=destination_hotkey,
680
+ netuid=destination_netuid,
681
+ block_hash=block_hash,
682
+ ),
683
+ )
684
+
685
+ console.print(
686
+ f"Origin Stake:\n [blue]{origin_stake_balance}[/blue] :arrow_right: "
687
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_origin_stake_balance}"
688
+ )
689
+ console.print(
690
+ f"Destination Stake:\n [blue]{destination_stake_balance}[/blue] :arrow_right: "
691
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_destination_stake_balance}"
692
+ )
693
+ return
694
+
695
+
696
+ async def transfer_stake(
697
+ wallet: Wallet,
698
+ subtensor: "SubtensorInterface",
699
+ amount: float,
700
+ origin_netuid: int,
701
+ dest_netuid: int,
702
+ dest_coldkey_ss58: str,
703
+ interactive_selection: bool = False,
704
+ prompt: bool = True,
705
+ ) -> bool:
706
+ """Transfers stake from one network to another.
707
+
708
+ Args:
709
+ wallet (Wallet): Bittensor wallet object.
710
+ subtensor (SubtensorInterface): Subtensor interface instance.
711
+ amount (float): Amount to transfer.
712
+ origin_netuid (int): The netuid to transfer stake from.
713
+ dest_netuid (int): The netuid to transfer stake to.
714
+ dest_coldkey_ss58 (str): The destination coldkey to transfer stake to.
715
+ interactive_selection (bool): If true, prompts for selection of origin and destination subnets.
716
+ prompt (bool): If true, prompts for confirmation before executing transfer.
717
+
718
+ Returns:
719
+ bool: True if transfer was successful, False otherwise.
720
+ """
721
+ if interactive_selection:
722
+ selection = await stake_transfer_selection(wallet, subtensor)
723
+ origin_netuid = selection["origin_netuid"]
724
+ amount = selection["amount"]
725
+ dest_netuid = selection["destination_netuid"]
726
+
727
+ # Check if both subnets exist
728
+ block_hash = await subtensor.substrate.get_chain_head()
729
+ dest_exists, origin_exists = await asyncio.gather(
730
+ subtensor.subnet_exists(netuid=dest_netuid, block_hash=block_hash),
731
+ subtensor.subnet_exists(netuid=origin_netuid, block_hash=block_hash),
732
+ )
733
+ if not dest_exists:
734
+ err_console.print(f"[red]Subnet {dest_netuid} does not exist[/red]")
735
+ return False
736
+
737
+ if not origin_exists:
738
+ err_console.print(f"[red]Subnet {origin_netuid} does not exist[/red]")
739
+ return False
740
+
741
+ # Get current stake balances
742
+ hotkey_ss58 = wallet.hotkey.ss58_address
743
+ with console.status(f"Retrieving stake data from {subtensor.network}..."):
744
+ current_stake = await subtensor.get_stake(
745
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
746
+ hotkey_ss58=hotkey_ss58,
747
+ netuid=origin_netuid,
748
+ )
749
+ current_dest_stake = await subtensor.get_stake(
750
+ coldkey_ss58=dest_coldkey_ss58,
751
+ hotkey_ss58=hotkey_ss58,
752
+ netuid=dest_netuid,
753
+ )
754
+ amount_to_transfer = Balance.from_tao(amount).set_unit(origin_netuid)
755
+
756
+ # Check if enough stake to transfer
757
+ if amount_to_transfer > current_stake:
758
+ err_console.print(
759
+ f"[red]Not enough stake to transfer[/red]:\n"
760
+ f"Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] < "
761
+ f"Transfer amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_transfer}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
762
+ )
763
+ return False
764
+
765
+ # Slippage warning
766
+ if prompt:
767
+ await display_stake_movement_cross_subnets(
768
+ subtensor=subtensor,
769
+ origin_netuid=origin_netuid,
770
+ destination_netuid=dest_netuid,
771
+ origin_hotkey=hotkey_ss58,
772
+ destination_hotkey=hotkey_ss58,
773
+ amount_to_move=amount_to_transfer,
774
+ )
775
+
776
+ if not Confirm.ask("Would you like to continue?"):
777
+ raise typer.Exit()
778
+
779
+ # Perform transfer operation
780
+ try:
781
+ wallet.unlock_coldkey()
782
+ except KeyFileError:
783
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
784
+ return False
785
+
786
+ with console.status("\n:satellite: Transferring stake ..."):
787
+ call = await subtensor.substrate.compose_call(
788
+ call_module="SubtensorModule",
789
+ call_function="transfer_stake",
790
+ call_params={
791
+ "destination_coldkey": dest_coldkey_ss58,
792
+ "hotkey": hotkey_ss58,
793
+ "origin_netuid": origin_netuid,
794
+ "destination_netuid": dest_netuid,
795
+ "alpha_amount": amount_to_transfer.rao,
796
+ },
797
+ )
798
+
799
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
800
+ call=call, keypair=wallet.coldkey
801
+ )
802
+
803
+ response = await subtensor.substrate.submit_extrinsic(
804
+ extrinsic, wait_for_inclusion=True, wait_for_finalization=False
805
+ )
806
+
807
+ if not prompt:
808
+ console.print(":white_heavy_check_mark: [green]Sent[/green]")
809
+ return True
810
+
811
+ await response.process_events()
812
+ if not await response.is_success:
813
+ err_console.print(
814
+ f":cross_mark: [red]Failed[/red] with error: "
815
+ f"{format_error_message(await response.error_message, subtensor.substrate)}"
816
+ )
817
+ return False
818
+
819
+ # Get and display new stake balances
820
+ new_stake, new_dest_stake = await asyncio.gather(
821
+ subtensor.get_stake(
822
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
823
+ hotkey_ss58=hotkey_ss58,
824
+ netuid=origin_netuid,
825
+ ),
826
+ subtensor.get_stake(
827
+ coldkey_ss58=dest_coldkey_ss58,
828
+ hotkey_ss58=hotkey_ss58,
829
+ netuid=dest_netuid,
830
+ ),
831
+ )
832
+
833
+ console.print(
834
+ f"Origin Stake:\n [blue]{current_stake}[/blue] :arrow_right: "
835
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
836
+ )
837
+ console.print(
838
+ f"Destination Stake:\n [blue]{current_dest_stake}[/blue] :arrow_right: "
839
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_dest_stake}"
840
+ )
841
+ return True
842
+
843
+
844
+ async def swap_stake(
845
+ wallet: Wallet,
846
+ subtensor: "SubtensorInterface",
847
+ origin_netuid: int,
848
+ destination_netuid: int,
849
+ amount: float,
850
+ swap_all: bool = False,
851
+ interactive_selection: bool = False,
852
+ prompt: bool = True,
853
+ wait_for_inclusion: bool = True,
854
+ wait_for_finalization: bool = False,
855
+ ) -> bool:
856
+ """Swaps stake between subnets while keeping the same coldkey-hotkey pair ownership.
857
+
858
+ Args:
859
+ wallet (Wallet): The wallet to swap stake from.
860
+ subtensor (SubtensorInterface): Subtensor interface instance.
861
+ hotkey_ss58 (str): The SS58 address of the hotkey whose stake is being swapped.
862
+ origin_netuid (int): The netuid from which stake is removed.
863
+ destination_netuid (int): The netuid to which stake is added.
864
+ amount (float): The amount to swap.
865
+ interactive_selection (bool): If true, prompts for selection of origin and destination subnets.
866
+ prompt (bool): If true, prompts for confirmation before executing swap.
867
+ wait_for_inclusion (bool): If true, waits for the transaction to be included in a block.
868
+ wait_for_finalization (bool): If true, waits for the transaction to be finalized.
869
+
870
+ Returns:
871
+ bool: True if the swap was successful, False otherwise.
872
+ """
873
+ hotkey_ss58 = wallet.hotkey.ss58_address
874
+ if interactive_selection:
875
+ selection = await stake_swap_selection(subtensor, wallet)
876
+ origin_netuid = selection["origin_netuid"]
877
+ amount = selection["amount"]
878
+ destination_netuid = selection["destination_netuid"]
879
+
880
+ # Check if both subnets exist
881
+ block_hash = await subtensor.substrate.get_chain_head()
882
+ dest_exists, origin_exists = await asyncio.gather(
883
+ subtensor.subnet_exists(netuid=destination_netuid, block_hash=block_hash),
884
+ subtensor.subnet_exists(netuid=origin_netuid, block_hash=block_hash),
885
+ )
886
+ if not dest_exists:
887
+ err_console.print(f"[red]Subnet {destination_netuid} does not exist[/red]")
888
+ return False
889
+
890
+ if not origin_exists:
891
+ err_console.print(f"[red]Subnet {origin_netuid} does not exist[/red]")
892
+ return False
893
+
894
+ # Get current stake balances
895
+ with console.status(f"Retrieving stake data from {subtensor.network}..."):
896
+ current_stake = await subtensor.get_stake(
897
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
898
+ hotkey_ss58=hotkey_ss58,
899
+ netuid=origin_netuid,
900
+ )
901
+ current_dest_stake = await subtensor.get_stake(
902
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
903
+ hotkey_ss58=hotkey_ss58,
904
+ netuid=destination_netuid,
905
+ )
906
+
907
+ if swap_all:
908
+ amount_to_swap = Balance.from_tao(current_stake).set_unit(origin_netuid)
909
+ else:
910
+ amount_to_swap = Balance.from_tao(amount).set_unit(origin_netuid)
911
+
912
+ # Check if enough stake to swap
913
+ if amount_to_swap > current_stake:
914
+ err_console.print(
915
+ f"[red]Not enough stake to swap[/red]:\n"
916
+ f"Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] < "
917
+ f"Swap amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_swap}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
918
+ )
919
+ return False
920
+
921
+ # Slippage warning
922
+ if prompt:
923
+ await display_stake_movement_cross_subnets(
924
+ subtensor=subtensor,
925
+ origin_netuid=origin_netuid,
926
+ destination_netuid=destination_netuid,
927
+ origin_hotkey=hotkey_ss58,
928
+ destination_hotkey=hotkey_ss58,
929
+ amount_to_move=amount_to_swap,
930
+ )
931
+
932
+ if not Confirm.ask("Would you like to continue?"):
933
+ raise typer.Exit()
934
+
935
+ # Perform swap operation
936
+ try:
937
+ wallet.unlock_coldkey()
938
+ except KeyFileError:
939
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
940
+ return False
941
+
942
+ with console.status(
943
+ f"\n:satellite: Swapping stake from netuid [blue]{origin_netuid}[/blue] to netuid [blue]{destination_netuid}[/blue]..."
944
+ ):
945
+ call = await subtensor.substrate.compose_call(
946
+ call_module="SubtensorModule",
947
+ call_function="swap_stake",
948
+ call_params={
949
+ "hotkey": hotkey_ss58,
950
+ "origin_netuid": origin_netuid,
951
+ "destination_netuid": destination_netuid,
952
+ "alpha_amount": amount_to_swap.rao,
953
+ },
954
+ )
955
+
956
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
957
+ call=call, keypair=wallet.coldkey
958
+ )
959
+
960
+ response = await subtensor.substrate.submit_extrinsic(
961
+ extrinsic,
962
+ wait_for_inclusion=wait_for_inclusion,
963
+ wait_for_finalization=wait_for_finalization,
964
+ )
965
+
966
+ if not prompt:
967
+ console.print(":white_heavy_check_mark: [green]Sent[/green]")
968
+ return True
969
+
970
+ await response.process_events()
971
+ if not await response.is_success:
972
+ err_console.print(
973
+ f":cross_mark: [red]Failed[/red] with error: "
974
+ f"{format_error_message(await response.error_message, subtensor.substrate)}"
975
+ )
976
+ return False
977
+
978
+ # Get and display new stake balances
979
+ new_stake, new_dest_stake = await asyncio.gather(
980
+ subtensor.get_stake(
981
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
982
+ hotkey_ss58=hotkey_ss58,
983
+ netuid=origin_netuid,
984
+ ),
985
+ subtensor.get_stake(
986
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
987
+ hotkey_ss58=hotkey_ss58,
988
+ netuid=destination_netuid,
989
+ ),
990
+ )
991
+
992
+ console.print(
993
+ f"Origin Stake:\n [blue]{current_stake}[/blue] :arrow_right: "
994
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
995
+ )
996
+ console.print(
997
+ f"Destination Stake:\n [blue]{current_dest_stake}[/blue] :arrow_right: "
998
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_dest_stake}"
999
+ )
1000
+ return True