bittensor-cli 9.0.1__py3-none-any.whl → 9.0.3__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.
@@ -21,6 +21,7 @@ from bittensor_cli.src.bittensor.utils import (
21
21
  is_valid_ss58_address,
22
22
  format_error_message,
23
23
  group_subnets,
24
+ unlock_key,
24
25
  )
25
26
 
26
27
  if TYPE_CHECKING:
@@ -49,26 +50,36 @@ async def unstake(
49
50
  f"Retrieving subnet data & identities from {subtensor.network}...",
50
51
  spinner="earth",
51
52
  ):
52
- all_sn_dynamic_info_, ck_hk_identities, old_identities = await asyncio.gather(
53
- subtensor.all_subnets(),
54
- subtensor.fetch_coldkey_hotkey_identities(),
55
- subtensor.get_delegate_identities(),
53
+ chain_head = await subtensor.substrate.get_chain_head()
54
+ (
55
+ all_sn_dynamic_info_,
56
+ ck_hk_identities,
57
+ old_identities,
58
+ stake_infos,
59
+ ) = await asyncio.gather(
60
+ subtensor.all_subnets(block_hash=chain_head),
61
+ subtensor.fetch_coldkey_hotkey_identities(block_hash=chain_head),
62
+ subtensor.get_delegate_identities(block_hash=chain_head),
63
+ subtensor.get_stake_for_coldkey(
64
+ wallet.coldkeypub.ss58_address, block_hash=chain_head
65
+ ),
56
66
  )
57
67
  all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_}
58
68
 
59
69
  if interactive:
60
70
  hotkeys_to_unstake_from, unstake_all_from_hk = await _unstake_selection(
61
- subtensor,
62
- wallet,
63
71
  all_sn_dynamic_info,
64
72
  ck_hk_identities,
65
73
  old_identities,
74
+ stake_infos,
66
75
  netuid=netuid,
67
76
  )
68
77
  if unstake_all_from_hk:
69
78
  hotkey_to_unstake_all = hotkeys_to_unstake_from[0]
70
79
  unstake_all_alpha = Confirm.ask(
71
- "\nUnstake [blue]all alpha stakes[/blue] and stake back to [blue]root[/blue]? (No will unstake everything)",
80
+ "\nDo you want to:\n"
81
+ "[blue]Yes[/blue]: Unstake from all subnets and automatically restake to subnet 0 (root)\n"
82
+ "[blue]No[/blue]: Unstake everything (including subnet 0)",
72
83
  default=True,
73
84
  )
74
85
  return await unstake_all(
@@ -96,20 +107,17 @@ async def unstake(
96
107
  all_hotkeys=all_hotkeys,
97
108
  include_hotkeys=include_hotkeys,
98
109
  exclude_hotkeys=exclude_hotkeys,
110
+ stake_infos=stake_infos,
111
+ identities=ck_hk_identities,
112
+ old_identities=old_identities,
99
113
  )
100
114
 
101
115
  with console.status(
102
116
  f"Retrieving stake data from {subtensor.network}...",
103
117
  spinner="earth",
104
118
  ):
105
- # Fetch stake balances
106
- chain_head = await subtensor.substrate.get_chain_head()
107
- stake_info_list = await subtensor.get_stake_for_coldkey(
108
- coldkey_ss58=wallet.coldkeypub.ss58_address,
109
- block_hash=chain_head,
110
- )
111
119
  stake_in_netuids = {}
112
- for stake_info in stake_info_list:
120
+ for stake_info in stake_infos:
113
121
  if stake_info.hotkey_ss58 not in stake_in_netuids:
114
122
  stake_in_netuids[stake_info.hotkey_ss58] = {}
115
123
  stake_in_netuids[stake_info.hotkey_ss58][stake_info.netuid] = (
@@ -261,10 +269,7 @@ async def unstake(
261
269
  raise typer.Exit()
262
270
 
263
271
  # Execute extrinsics
264
- try:
265
- wallet.unlock_coldkey()
266
- except KeyFileError:
267
- err_console.print("Error decrypting coldkey (possibly incorrect password)")
272
+ if not unlock_key(wallet).success:
268
273
  return False
269
274
 
270
275
  with console.status("\n:satellite: Performing unstaking operations...") as status:
@@ -313,6 +318,9 @@ async def unstake_all(
313
318
  subtensor: "SubtensorInterface",
314
319
  hotkey_ss58_address: str,
315
320
  unstake_all_alpha: bool = False,
321
+ all_hotkeys: bool = False,
322
+ include_hotkeys: list[str] = [],
323
+ exclude_hotkeys: list[str] = [],
316
324
  prompt: bool = True,
317
325
  ) -> bool:
318
326
  """Unstakes all stakes from all hotkeys in all subnets."""
@@ -334,10 +342,27 @@ async def unstake_all(
334
342
  subtensor.all_subnets(),
335
343
  subtensor.get_balance(wallet.coldkeypub.ss58_address),
336
344
  )
337
- if not hotkey_ss58_address:
338
- hotkey_ss58_address = wallet.hotkey.ss58_address
345
+
346
+ if all_hotkeys:
347
+ hotkeys = _get_hotkeys_to_unstake(
348
+ wallet,
349
+ hotkey_ss58_address=hotkey_ss58_address,
350
+ all_hotkeys=all_hotkeys,
351
+ include_hotkeys=include_hotkeys,
352
+ exclude_hotkeys=exclude_hotkeys,
353
+ stake_infos=stake_info,
354
+ identities=ck_hk_identities,
355
+ old_identities=old_identities,
356
+ )
357
+ elif not hotkey_ss58_address:
358
+ hotkeys = [(wallet.hotkey_str, wallet.hotkey.ss58_address)]
359
+ else:
360
+ hotkeys = [(None, hotkey_ss58_address)]
361
+
362
+ hotkey_names = {ss58: name for name, ss58 in hotkeys if name is not None}
363
+ hotkey_ss58s = [ss58 for _, ss58 in hotkeys]
339
364
  stake_info = [
340
- stake for stake in stake_info if stake.hotkey_ss58 == hotkey_ss58_address
365
+ stake for stake in stake_info if stake.hotkey_ss58 in hotkey_ss58s
341
366
  ]
342
367
 
343
368
  if unstake_all_alpha:
@@ -403,18 +428,7 @@ async def unstake_all(
403
428
  if stake.stake.rao == 0:
404
429
  continue
405
430
 
406
- # Get hotkey identity
407
- if hk_identity := ck_hk_identities["hotkeys"].get(stake.hotkey_ss58):
408
- hotkey_name = hk_identity.get("identity", {}).get(
409
- "name", ""
410
- ) or hk_identity.get("display", "~")
411
- hotkey_display = f"{hotkey_name}"
412
- elif old_identity := old_identities.get(stake.hotkey_ss58):
413
- hotkey_name = old_identity.display
414
- hotkey_display = f"{hotkey_name}"
415
- else:
416
- hotkey_display = stake.hotkey_ss58
417
-
431
+ hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58)
418
432
  subnet_info = all_sn_dynamic_info.get(stake.netuid)
419
433
  stake_amount = stake.stake
420
434
  received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
@@ -449,62 +463,19 @@ async def unstake_all(
449
463
  ):
450
464
  return False
451
465
 
452
- try:
453
- wallet.unlock_coldkey()
454
- except KeyFileError:
455
- err_console.print("Error decrypting coldkey (possibly incorrect password)")
466
+ if not unlock_key(wallet).success:
456
467
  return False
457
468
 
458
- console_status = (
459
- ":satellite: Unstaking all Alpha stakes..."
460
- if unstake_all_alpha
461
- else ":satellite: Unstaking all stakes..."
462
- )
463
- previous_root_stake = await subtensor.get_stake(
464
- hotkey_ss58=hotkey_ss58_address,
465
- coldkey_ss58=wallet.coldkeypub.ss58_address,
466
- netuid=0,
467
- )
468
- with console.status(console_status):
469
- call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all"
470
- call = await subtensor.substrate.compose_call(
471
- call_module="SubtensorModule",
472
- call_function=call_function,
473
- call_params={"hotkey": hotkey_ss58_address},
474
- )
475
- success, error_message = await subtensor.sign_and_send_extrinsic(
476
- call=call,
477
- wallet=wallet,
478
- wait_for_inclusion=True,
479
- wait_for_finalization=False,
480
- )
481
-
482
- if success:
483
- success_message = (
484
- ":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]"
485
- if not unstake_all_alpha
486
- else ":white_heavy_check_mark: [green]Successfully unstaked all Alpha stakes[/green]"
487
- )
488
- console.print(success_message)
489
- new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
490
- console.print(
491
- f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
492
- )
493
- if unstake_all_alpha:
494
- root_stake = await subtensor.get_stake(
495
- hotkey_ss58=hotkey_ss58_address,
496
- coldkey_ss58=wallet.coldkeypub.ss58_address,
497
- netuid=0,
498
- )
499
- console.print(
500
- f"Root Stake:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{root_stake}"
501
- )
502
- return True
503
- else:
504
- err_console.print(
505
- f":cross_mark: [red]Failed to unstake[/red]: {error_message}"
469
+ with console.status("Unstaking all stakes...") as status:
470
+ for hotkey_ss58 in hotkey_ss58s:
471
+ await _unstake_all_extrinsic(
472
+ wallet=wallet,
473
+ subtensor=subtensor,
474
+ hotkey_ss58=hotkey_ss58,
475
+ hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
476
+ unstake_all_alpha=unstake_all_alpha,
477
+ status=status,
506
478
  )
507
- return False
508
479
 
509
480
 
510
481
  # Extrinsics
@@ -666,9 +637,7 @@ async def _safe_unstake_extrinsic(
666
637
  )
667
638
  return
668
639
  else:
669
- err_out(
670
- f"\n{failure_prelude} with error: {format_error_message(e)}"
671
- )
640
+ err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
672
641
  return
673
642
 
674
643
  await response.process_events()
@@ -709,6 +678,115 @@ async def _safe_unstake_extrinsic(
709
678
  )
710
679
 
711
680
 
681
+ async def _unstake_all_extrinsic(
682
+ wallet: Wallet,
683
+ subtensor: "SubtensorInterface",
684
+ hotkey_ss58: str,
685
+ hotkey_name: str,
686
+ unstake_all_alpha: bool,
687
+ status=None,
688
+ ) -> None:
689
+ """Execute an unstake all extrinsic.
690
+
691
+ Args:
692
+ wallet: Wallet instance
693
+ subtensor: Subtensor interface
694
+ hotkey_ss58: Hotkey SS58 address
695
+ hotkey_name: Display name of the hotkey
696
+ unstake_all_alpha: Whether to unstake only alpha stakes
697
+ status: Optional status for console updates
698
+ """
699
+ err_out = partial(print_error, status=status)
700
+ failure_prelude = (
701
+ f":cross_mark: [red]Failed[/red] to unstake all from {hotkey_name}"
702
+ )
703
+
704
+ if status:
705
+ status.update(
706
+ f"\n:satellite: {'Unstaking all Alpha stakes' if unstake_all_alpha else 'Unstaking all stakes'} from {hotkey_name} ..."
707
+ )
708
+
709
+ block_hash = await subtensor.substrate.get_chain_head()
710
+ if unstake_all_alpha:
711
+ previous_root_stake, current_balance = await asyncio.gather(
712
+ subtensor.get_stake(
713
+ hotkey_ss58=hotkey_ss58,
714
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
715
+ netuid=0,
716
+ block_hash=block_hash,
717
+ ),
718
+ subtensor.get_balance(
719
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
720
+ ),
721
+ )
722
+ else:
723
+ current_balance = await subtensor.get_balance(
724
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
725
+ )
726
+
727
+ call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all"
728
+ call = await subtensor.substrate.compose_call(
729
+ call_module="SubtensorModule",
730
+ call_function=call_function,
731
+ call_params={"hotkey": hotkey_ss58},
732
+ )
733
+
734
+ try:
735
+ response = await subtensor.substrate.submit_extrinsic(
736
+ extrinsic=await subtensor.substrate.create_signed_extrinsic(
737
+ call=call,
738
+ keypair=wallet.coldkey,
739
+ ),
740
+ wait_for_inclusion=True,
741
+ wait_for_finalization=False,
742
+ )
743
+ await response.process_events()
744
+
745
+ if not await response.is_success:
746
+ err_out(
747
+ f"{failure_prelude} with error: "
748
+ f"{format_error_message(await response.error_message)}"
749
+ )
750
+ return
751
+
752
+ # Fetch latest balance and stake
753
+ block_hash = await subtensor.substrate.get_chain_head()
754
+ if unstake_all_alpha:
755
+ new_root_stake, new_balance = await asyncio.gather(
756
+ subtensor.get_stake(
757
+ hotkey_ss58=hotkey_ss58,
758
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
759
+ netuid=0,
760
+ block_hash=block_hash,
761
+ ),
762
+ subtensor.get_balance(
763
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
764
+ ),
765
+ )
766
+ else:
767
+ new_balance = await subtensor.get_balance(
768
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
769
+ )
770
+
771
+ success_message = (
772
+ ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all stakes[/green]"
773
+ if not unstake_all_alpha
774
+ else ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all Alpha stakes[/green]"
775
+ )
776
+ console.print(f"{success_message} from {hotkey_name}")
777
+ console.print(
778
+ f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
779
+ )
780
+
781
+ if unstake_all_alpha:
782
+ console.print(
783
+ f"Root Stake for {hotkey_name}:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_root_stake}"
784
+ )
785
+
786
+ except Exception as e:
787
+ err_out(f"{failure_prelude} with error: {str(e)}")
788
+
789
+
712
790
  # Helpers
713
791
  def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, float]:
714
792
  """Calculate slippage and received amount for unstaking operation.
@@ -737,17 +815,12 @@ def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, flo
737
815
 
738
816
 
739
817
  async def _unstake_selection(
740
- subtensor: "SubtensorInterface",
741
- wallet: Wallet,
742
818
  dynamic_info,
743
819
  identities,
744
820
  old_identities,
821
+ stake_infos,
745
822
  netuid: Optional[int] = None,
746
823
  ):
747
- stake_infos = await subtensor.get_stake_for_coldkey(
748
- coldkey_ss58=wallet.coldkeypub.ss58_address
749
- )
750
-
751
824
  if not stake_infos:
752
825
  print_error("You have no stakes to unstake.")
753
826
  raise typer.Exit()
@@ -771,16 +844,11 @@ async def _unstake_selection(
771
844
 
772
845
  hotkeys_info = []
773
846
  for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()):
774
- if hk_identity := identities["hotkeys"].get(hotkey_ss58):
775
- hotkey_name = hk_identity.get("identity", {}).get(
776
- "name", ""
777
- ) or hk_identity.get("display", "~")
778
- elif old_identity := old_identities.get(hotkey_ss58):
779
- hotkey_name = old_identity.display
780
- else:
781
- hotkey_name = "~"
782
- # TODO: Add wallet ids here.
783
-
847
+ hotkey_name = get_hotkey_identity(
848
+ hotkey_ss58=hotkey_ss58,
849
+ identities=identities,
850
+ old_identities=old_identities,
851
+ )
784
852
  hotkeys_info.append(
785
853
  {
786
854
  "index": idx,
@@ -983,6 +1051,9 @@ def _get_hotkeys_to_unstake(
983
1051
  all_hotkeys: bool,
984
1052
  include_hotkeys: list[str],
985
1053
  exclude_hotkeys: list[str],
1054
+ stake_infos: list,
1055
+ identities: dict,
1056
+ old_identities: dict,
986
1057
  ) -> list[tuple[Optional[str], str]]:
987
1058
  """Get list of hotkeys to unstake from based on input parameters.
988
1059
 
@@ -1002,13 +1073,27 @@ def _get_hotkeys_to_unstake(
1002
1073
 
1003
1074
  if all_hotkeys:
1004
1075
  print_verbose("Unstaking from all hotkeys")
1005
- all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
1006
- return [
1076
+ all_hotkeys_ = get_hotkey_wallets_for_wallet(wallet=wallet)
1077
+ wallet_hotkeys = [
1007
1078
  (wallet.hotkey_str, wallet.hotkey.ss58_address)
1008
1079
  for wallet in all_hotkeys_
1009
1080
  if wallet.hotkey_str not in exclude_hotkeys
1010
1081
  ]
1011
1082
 
1083
+ wallet_hotkey_addresses = {addr for _, addr in wallet_hotkeys}
1084
+ chain_hotkeys = [
1085
+ (
1086
+ get_hotkey_identity(stake_info.hotkey_ss58, identities, old_identities),
1087
+ stake_info.hotkey_ss58,
1088
+ )
1089
+ for stake_info in stake_infos
1090
+ if (
1091
+ stake_info.hotkey_ss58 not in wallet_hotkey_addresses
1092
+ and stake_info.hotkey_ss58 not in exclude_hotkeys
1093
+ )
1094
+ ]
1095
+ return wallet_hotkeys + chain_hotkeys
1096
+
1012
1097
  if include_hotkeys:
1013
1098
  print_verbose("Unstaking from included hotkeys")
1014
1099
  result = []
@@ -1144,3 +1229,28 @@ The columns are as follows:
1144
1229
  - [bold white]Partial unstaking[/bold white]: If True, allows unstaking up to the rate tolerance limit. If False, the entire transaction will fail if rate tolerance is exceeded.\n"""
1145
1230
 
1146
1231
  console.print(base_description + (safe_staking_description if safe_staking else ""))
1232
+
1233
+
1234
+ def get_hotkey_identity(
1235
+ hotkey_ss58: str,
1236
+ identities: dict,
1237
+ old_identities: dict,
1238
+ ) -> str:
1239
+ """Get identity name for a hotkey from identities or old_identities.
1240
+
1241
+ Args:
1242
+ hotkey_ss58 (str): The hotkey SS58 address
1243
+ identities (dict): Current identities from fetch_coldkey_hotkey_identities
1244
+ old_identities (dict): Old identities from get_delegate_identities
1245
+
1246
+ Returns:
1247
+ str: Identity name or truncated address
1248
+ """
1249
+ if hk_identity := identities["hotkeys"].get(hotkey_ss58):
1250
+ return hk_identity.get("identity", {}).get("name", "") or hk_identity.get(
1251
+ "display", "~"
1252
+ )
1253
+ elif old_identity := old_identities.get(hotkey_ss58):
1254
+ return old_identity.display
1255
+ else:
1256
+ return f"{hotkey_ss58[:4]}...{hotkey_ss58[-4:]}"
@@ -97,8 +97,10 @@ async def register_subnetwork_extrinsic(
97
97
  sn_burn_cost = await burn_cost(subtensor)
98
98
  if sn_burn_cost > your_balance:
99
99
  err_console.print(
100
- f"Your balance of: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}[{COLOR_PALETTE['POOLS']['TAO']}] is not enough to pay the subnet lock cost of: "
101
- f"[{COLOR_PALETTE['POOLS']['TAO']}]{sn_burn_cost}[{COLOR_PALETTE['POOLS']['TAO']}]"
100
+ f"Your balance of: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}[{COLOR_PALETTE['POOLS']['TAO']}]"
101
+ f" is not enough to burn "
102
+ f"[{COLOR_PALETTE['POOLS']['TAO']}]{sn_burn_cost}[{COLOR_PALETTE['POOLS']['TAO']}] "
103
+ f"to register a subnet."
102
104
  )
103
105
  return False
104
106
 
@@ -107,7 +109,7 @@ async def register_subnetwork_extrinsic(
107
109
  f"Your balance is: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}"
108
110
  )
109
111
  if not Confirm.ask(
110
- f"Do you want to register a subnet for [{COLOR_PALETTE['POOLS']['TAO']}]{sn_burn_cost}?"
112
+ f"Do you want to burn [{COLOR_PALETTE['POOLS']['TAO']}]{sn_burn_cost} to register a subnet?"
111
113
  ):
112
114
  return False
113
115
 
@@ -145,10 +147,7 @@ async def register_subnetwork_extrinsic(
145
147
  )
146
148
  return False
147
149
 
148
- try:
149
- wallet.unlock_coldkey()
150
- except KeyFileError:
151
- err_console.print("Error decrypting coldkey (possibly incorrect password)")
150
+ if not unlock_key(wallet).success:
152
151
  return False
153
152
 
154
153
  with console.status(":satellite: Registering subnet...", spinner="earth"):
@@ -889,8 +888,8 @@ async def show(
889
888
  total_emission_per_block = 0
890
889
  for netuid_ in range(len(all_subnets)):
891
890
  subnet = all_subnets[netuid_]
892
- emission_on_subnet = (
893
- root_state.emission_history[netuid_][idx] / (subnet.tempo or 1)
891
+ emission_on_subnet = root_state.emission_history[netuid_][idx] / (
892
+ subnet.tempo or 1
894
893
  )
895
894
  total_emission_per_block += subnet.alpha_to_tao(
896
895
  Balance.from_rao(emission_on_subnet)
@@ -2135,9 +2134,7 @@ async def get_identity(subtensor: "SubtensorInterface", netuid: int, title: str
2135
2134
  title = "Subnet Identity"
2136
2135
 
2137
2136
  if not await subtensor.subnet_exists(netuid):
2138
- print_error(
2139
- f"Subnet {netuid} does not exist."
2140
- )
2137
+ print_error(f"Subnet {netuid} does not exist.")
2141
2138
  raise typer.Exit()
2142
2139
 
2143
2140
  with console.status(