bittensor-cli 8.4.2__py3-none-any.whl → 9.0.0rc1__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 +1503 -1372
  3. bittensor_cli/src/__init__.py +625 -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 +123 -91
  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 +2043 -0
  19. bittensor_cli/src/commands/sudo.py +529 -26
  20. bittensor_cli/src/commands/wallets.py +231 -535
  21. bittensor_cli/src/commands/weights.py +15 -11
  22. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/METADATA +7 -4
  23. bittensor_cli-9.0.0rc1.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.2.dist-info/RECORD +0 -31
  28. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/WHEEL +0 -0
  29. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/entry_points.txt +0 -0
  30. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/top_level.txt +0 -0
bittensor_cli/cli.py CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  import asyncio
3
- import binascii
4
3
  import curses
5
- from functools import partial
6
4
  import os.path
7
5
  import re
8
6
  import ssl
@@ -19,21 +17,22 @@ from bittensor_wallet import Wallet
19
17
  from rich import box
20
18
  from rich.prompt import Confirm, FloatPrompt, Prompt, IntPrompt
21
19
  from rich.table import Column, Table
20
+ from rich.tree import Tree
22
21
  from bittensor_cli.src import (
23
22
  defaults,
24
23
  HELP_PANELS,
25
24
  WalletOptions as WO,
26
25
  WalletValidationTypes as WV,
27
26
  Constants,
27
+ COLOR_PALETTE,
28
28
  )
29
29
  from bittensor_cli.src.bittensor import utils
30
30
  from bittensor_cli.src.bittensor.balances import Balance
31
- from bittensor_cli.src.bittensor.async_substrate_interface import (
32
- SubstrateRequestException,
33
- )
34
- from bittensor_cli.src.commands import root, subnets, sudo, wallets
31
+ from async_substrate_interface.errors import SubstrateRequestException
32
+ from bittensor_cli.src.commands import sudo, wallets
35
33
  from bittensor_cli.src.commands import weights as weights_cmds
36
- from bittensor_cli.src.commands.stake import children_hotkeys, stake
34
+ from bittensor_cli.src.commands.subnets import price, subnets
35
+ from bittensor_cli.src.commands.stake import children_hotkeys, stake, move
37
36
  from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
38
37
  from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
39
38
  from bittensor_cli.src.bittensor.utils import (
@@ -43,22 +42,30 @@ from bittensor_cli.src.bittensor.utils import (
43
42
  is_valid_ss58_address,
44
43
  print_error,
45
44
  validate_chain_endpoint,
46
- retry_prompt,
45
+ validate_netuid,
46
+ is_rao_network,
47
+ get_effective_network,
48
+ prompt_for_identity,
49
+ validate_uri,
50
+ prompt_for_subnet_identity,
51
+ print_linux_dependency_message,
52
+ is_linux,
47
53
  )
48
54
  from typing_extensions import Annotated
49
55
  from textwrap import dedent
50
- from websockets import ConnectionClosed
56
+ from websockets import ConnectionClosed, InvalidHandshake
51
57
  from yaml import safe_dump, safe_load
52
58
 
53
59
  try:
54
60
  from git import Repo, GitError
55
61
  except ImportError:
62
+ Repo = None
56
63
 
57
64
  class GitError(Exception):
58
65
  pass
59
66
 
60
67
 
61
- __version__ = "8.4.2"
68
+ __version__ = "9.0.0rc1"
62
69
 
63
70
 
64
71
  _core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0)
@@ -127,6 +134,8 @@ class Options:
127
134
  use_password = typer.Option(
128
135
  True,
129
136
  help="Set this to `True` to protect the generated Bittensor key with a password.",
137
+ is_flag=True,
138
+ flag_value=False,
130
139
  )
131
140
  public_hex_key = typer.Option(None, help="The public key in hex format.")
132
141
  ss58_address = typer.Option(
@@ -162,6 +171,17 @@ class Options:
162
171
  None,
163
172
  help="The netuid of the subnet in the root network, (e.g. 1).",
164
173
  prompt=True,
174
+ callback=validate_netuid,
175
+ )
176
+ netuid_not_req = typer.Option(
177
+ None,
178
+ help="The netuid of the subnet in the root network, (e.g. 1).",
179
+ prompt=False,
180
+ )
181
+ all_netuids = typer.Option(
182
+ False,
183
+ help="Use all netuids",
184
+ prompt=False,
165
185
  )
166
186
  weights = typer.Option(
167
187
  None,
@@ -207,6 +227,17 @@ class Options:
207
227
  "--quiet",
208
228
  help="Display only critical information on the console.",
209
229
  )
230
+ live = typer.Option(
231
+ False,
232
+ "--live",
233
+ help="Display live view of the table",
234
+ )
235
+ uri = typer.Option(
236
+ None,
237
+ "--uri",
238
+ help="Create wallet from uri (e.g. 'Alice', 'Bob', 'Charlie', 'Dave', 'Eve')",
239
+ callback=validate_uri,
240
+ )
210
241
 
211
242
 
212
243
  def list_prompt(init_var: list, list_type: type, help_text: str) -> list:
@@ -249,7 +280,8 @@ def parse_to_list(
249
280
  def verbosity_console_handler(verbosity_level: int = 1) -> None:
250
281
  """
251
282
  Sets verbosity level of console output
252
- :param verbosity_level: int corresponding to verbosity level of console output (0 is quiet, 1 is normal, 2 is verbose)
283
+ :param verbosity_level: int corresponding to verbosity level of console output (0 is quiet, 1 is normal, 2 is
284
+ verbose)
253
285
  """
254
286
  if verbosity_level not in range(3):
255
287
  raise ValueError(
@@ -269,6 +301,32 @@ def verbosity_console_handler(verbosity_level: int = 1) -> None:
269
301
  verbose_console.quiet = False
270
302
 
271
303
 
304
+ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[int]:
305
+ """
306
+ Parses options to determine if the user wants to use a specific netuid or all netuids (None)
307
+
308
+ Returns:
309
+ None if using all netuids, otherwise int for the netuid to use
310
+ """
311
+ if netuid is None and all_netuids is True:
312
+ return None
313
+ elif netuid is None and all_netuids is False:
314
+ answer = Prompt.ask(
315
+ f"Enter the [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]netuid"
316
+ f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}] to use. Leave blank for all netuids",
317
+ default=None,
318
+ show_default=False,
319
+ )
320
+ if answer is None:
321
+ return None
322
+ if answer.lower() == "all":
323
+ return None
324
+ else:
325
+ return int(answer)
326
+ else:
327
+ return netuid
328
+
329
+
272
330
  def get_n_words(n_words: Optional[int]) -> int:
273
331
  """
274
332
  Prompts the user to select the number of words used in the mnemonic if not supplied or not within the
@@ -418,12 +476,21 @@ def version_callback(value: bool):
418
476
  raise typer.Exit()
419
477
 
420
478
 
479
+ def commands_callback(value: bool):
480
+ """
481
+ Prints a tree of commands for the app
482
+ """
483
+ if value:
484
+ cli = CLIManager()
485
+ console.print(cli.generate_command_tree())
486
+ raise typer.Exit()
487
+
488
+
421
489
  class CLIManager:
422
490
  """
423
491
  :var app: the main CLI Typer app
424
492
  :var config_app: the Typer app as it relates to config commands
425
493
  :var wallet_app: the Typer app as it relates to wallet commands
426
- :var root_app: the Typer app as it relates to root commands
427
494
  :var stake_app: the Typer app as it relates to stake commands
428
495
  :var sudo_app: the Typer app as it relates to sudo commands
429
496
  :var subnets_app: the Typer app as it relates to subnets commands
@@ -434,7 +501,6 @@ class CLIManager:
434
501
  app: typer.Typer
435
502
  config_app: typer.Typer
436
503
  wallet_app: typer.Typer
437
- root_app: typer.Typer
438
504
  subnets_app: typer.Typer
439
505
  weights_app: typer.Typer
440
506
  utils_app = typer.Typer(epilog=_epilog)
@@ -448,7 +514,9 @@ class CLIManager:
448
514
  "use_cache": True,
449
515
  "metagraph_cols": {
450
516
  "UID": True,
451
- "STAKE": True,
517
+ "GLOBAL_STAKE": True,
518
+ "LOCAL_STAKE": True,
519
+ "STAKE_WEIGHT": True,
452
520
  "RANK": True,
453
521
  "TRUST": True,
454
522
  "CONSENSUS": True,
@@ -476,7 +544,6 @@ class CLIManager:
476
544
  )
477
545
  self.config_app = typer.Typer(epilog=_epilog)
478
546
  self.wallet_app = typer.Typer(epilog=_epilog)
479
- self.root_app = typer.Typer(epilog=_epilog)
480
547
  self.stake_app = typer.Typer(epilog=_epilog)
481
548
  self.sudo_app = typer.Typer(epilog=_epilog)
482
549
  self.subnets_app = typer.Typer(epilog=_epilog)
@@ -506,15 +573,6 @@ class CLIManager:
506
573
  self.wallet_app, name="wallets", hidden=True, no_args_is_help=True
507
574
  )
508
575
 
509
- # root aliases
510
- self.app.add_typer(
511
- self.root_app,
512
- name="root",
513
- short_help="Root commands, alias: `r`",
514
- no_args_is_help=True,
515
- )
516
- self.app.add_typer(self.root_app, name="r", hidden=True, no_args_is_help=True)
517
-
518
576
  # stake aliases
519
577
  self.app.add_typer(
520
578
  self.stake_app,
@@ -563,7 +621,9 @@ class CLIManager:
563
621
  )
564
622
 
565
623
  # utils app
566
- self.app.add_typer(self.utils_app, name="utils", no_args_is_help=True)
624
+ self.app.add_typer(
625
+ self.utils_app, name="utils", no_args_is_help=True, hidden=True
626
+ )
567
627
 
568
628
  # config commands
569
629
  self.config_app.command("set")(self.set_config)
@@ -600,16 +660,21 @@ class CLIManager:
600
660
  "balance", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
601
661
  )(self.wallet_balance)
602
662
  self.wallet_app.command(
603
- "history", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
663
+ "history",
664
+ rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"],
665
+ hidden=True,
604
666
  )(self.wallet_history)
605
667
  self.wallet_app.command(
606
- "overview", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
668
+ "overview",
669
+ rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"],
607
670
  )(self.wallet_overview)
608
671
  self.wallet_app.command(
609
672
  "transfer", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
610
673
  )(self.wallet_transfer)
611
674
  self.wallet_app.command(
612
- "inspect", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
675
+ "inspect",
676
+ rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"],
677
+ hidden=True,
613
678
  )(self.wallet_inspect)
614
679
  self.wallet_app.command(
615
680
  "faucet", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
@@ -624,59 +689,25 @@ class CLIManager:
624
689
  "sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
625
690
  )(self.wallet_sign)
626
691
 
627
- # root commands
628
- self.root_app.command("list")(self.root_list)
629
- self.root_app.command(
630
- "set-weights", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
631
- )(self.root_set_weights)
632
- self.root_app.command(
633
- "get-weights", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
634
- )(self.root_get_weights)
635
- self.root_app.command(
636
- "boost", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
637
- )(self.root_boost)
638
- self.root_app.command(
639
- "slash", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
640
- )(self.root_slash)
641
- self.root_app.command(
642
- "senate", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
643
- )(self.root_senate)
644
- self.root_app.command(
645
- "senate-vote", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
646
- )(self.root_senate_vote)
647
- self.root_app.command("register")(self.root_register)
648
- self.root_app.command(
649
- "proposals", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
650
- )(self.root_proposals)
651
- self.root_app.command(
652
- "set-take", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
653
- )(self.root_set_take)
654
- self.root_app.command(
655
- "delegate-stake", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
656
- )(self.root_delegate_stake)
657
- self.root_app.command(
658
- "undelegate-stake", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
659
- )(self.root_undelegate_stake)
660
- self.root_app.command(
661
- "my-delegates", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
662
- )(self.root_my_delegates)
663
- self.root_app.command(
664
- "list-delegates", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
665
- )(self.root_list_delegates)
666
- self.root_app.command(
667
- "nominate", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
668
- )(self.root_nominate)
669
-
670
692
  # stake commands
671
- self.stake_app.command(
672
- "show", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
673
- )(self.stake_show)
674
693
  self.stake_app.command(
675
694
  "add", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
676
695
  )(self.stake_add)
677
696
  self.stake_app.command(
678
697
  "remove", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
679
698
  )(self.stake_remove)
699
+ self.stake_app.command(
700
+ "list", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
701
+ )(self.stake_list)
702
+ self.stake_app.command(
703
+ "move", rich_help_panel=HELP_PANELS["STAKE"]["MOVEMENT"]
704
+ )(self.stake_move)
705
+ self.stake_app.command(
706
+ "transfer", rich_help_panel=HELP_PANELS["STAKE"]["MOVEMENT"]
707
+ )(self.stake_transfer)
708
+ self.stake_app.command(
709
+ "swap", rich_help_panel=HELP_PANELS["STAKE"]["MOVEMENT"]
710
+ )(self.stake_swap)
680
711
 
681
712
  # stake-children commands
682
713
  children_app = typer.Typer()
@@ -702,6 +733,21 @@ class CLIManager:
702
733
  self.sudo_app.command("get", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])(
703
734
  self.sudo_get
704
735
  )
736
+ self.sudo_app.command(
737
+ "senate", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"]
738
+ )(self.sudo_senate)
739
+ self.sudo_app.command(
740
+ "proposals", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"]
741
+ )(self.sudo_proposals)
742
+ self.sudo_app.command(
743
+ "senate-vote", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"]
744
+ )(self.sudo_senate_vote)
745
+ self.sudo_app.command("set-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])(
746
+ self.sudo_set_take
747
+ )
748
+ self.sudo_app.command("get-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])(
749
+ self.sudo_get_take
750
+ )
705
751
 
706
752
  # subnets commands
707
753
  self.subnets_app.command(
@@ -711,8 +757,8 @@ class CLIManager:
711
757
  "list", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
712
758
  )(self.subnets_list)
713
759
  self.subnets_app.command(
714
- "lock-cost", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"]
715
- )(self.subnets_lock_cost)
760
+ "burn-cost", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"]
761
+ )(self.subnets_burn_cost)
716
762
  self.subnets_app.command(
717
763
  "create", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"]
718
764
  )(self.subnets_create)
@@ -723,8 +769,14 @@ class CLIManager:
723
769
  "register", rich_help_panel=HELP_PANELS["SUBNETS"]["REGISTER"]
724
770
  )(self.subnets_register)
725
771
  self.subnets_app.command(
726
- "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
727
- )(self.subnets_metagraph)
772
+ "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"], hidden=True
773
+ )(self.subnets_show) # Aliased to `s show` for now
774
+ self.subnets_app.command(
775
+ "show", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
776
+ )(self.subnets_show)
777
+ self.subnets_app.command(
778
+ "price", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
779
+ )(self.subnets_price)
728
780
 
729
781
  # weights commands
730
782
  self.weights_app.command(
@@ -769,22 +821,49 @@ class CLIManager:
769
821
  hidden=True,
770
822
  )(self.wallet_get_id)
771
823
 
772
- # Root
773
- self.root_app.command("set_weights", hidden=True)(self.root_set_weights)
774
- self.root_app.command("get_weights", hidden=True)(self.root_get_weights)
775
- self.root_app.command("senate_vote", hidden=True)(self.root_senate_vote)
776
- self.root_app.command("set_take", hidden=True)(self.root_set_take)
777
- self.root_app.command("delegate_stake", hidden=True)(self.root_delegate_stake)
778
- self.root_app.command("undelegate_stake", hidden=True)(
779
- self.root_undelegate_stake
780
- )
781
- self.root_app.command("my_delegates", hidden=True)(self.root_my_delegates)
782
- self.root_app.command("list_delegates", hidden=True)(self.root_list_delegates)
783
-
784
824
  # Subnets
785
- self.subnets_app.command("lock_cost", hidden=True)(self.subnets_lock_cost)
825
+ self.subnets_app.command("burn_cost", hidden=True)(self.subnets_burn_cost)
786
826
  self.subnets_app.command("pow_register", hidden=True)(self.subnets_pow_register)
787
827
 
828
+ # Sudo
829
+ self.sudo_app.command("senate_vote", hidden=True)(self.sudo_senate_vote)
830
+ self.sudo_app.command("get_take", hidden=True)(self.sudo_get_take)
831
+ self.sudo_app.command("set_take", hidden=True)(self.sudo_set_take)
832
+
833
+ def generate_command_tree(self) -> Tree:
834
+ """
835
+ Generates a rich.Tree of the commands, subcommands, and groups of this app
836
+ """
837
+
838
+ def build_rich_tree(data: dict, parent: Tree):
839
+ for group, content in data.get("groups", {}).items():
840
+ group_node = parent.add(
841
+ f"[bold cyan]{group}[/]"
842
+ ) # Add group to the tree
843
+ for command in content.get("commands", []):
844
+ group_node.add(f"[green]{command}[/]") # Add commands to the group
845
+ build_rich_tree(content, group_node) # Recurse for subgroups
846
+
847
+ def traverse_group(group: typer.Typer) -> dict:
848
+ tree = {}
849
+ if commands := [
850
+ cmd.name for cmd in group.registered_commands if not cmd.hidden
851
+ ]:
852
+ tree["commands"] = commands
853
+ for group in group.registered_groups:
854
+ if "groups" not in tree:
855
+ tree["groups"] = {}
856
+ if not group.hidden:
857
+ if group_transversal := traverse_group(group.typer_instance):
858
+ tree["groups"][group.name] = group_transversal
859
+
860
+ return tree
861
+
862
+ groups_and_commands = traverse_group(self.app)
863
+ root = Tree("[bold magenta]BTCLI Commands[/]") # Root node
864
+ build_rich_tree(groups_and_commands, root)
865
+ return root
866
+
788
867
  def initialize_chain(
789
868
  self,
790
869
  network: Optional[list[str]] = None,
@@ -815,13 +894,13 @@ class CLIManager:
815
894
  elif self.config["network"]:
816
895
  self.subtensor = SubtensorInterface(self.config["network"])
817
896
  console.print(
818
- f"Using the specified network [dark_orange]{self.config['network']}[/dark_orange] from config"
897
+ f"Using the specified network [{COLOR_PALETTE['GENERAL']['LINKS']}]{self.config['network']}[/{COLOR_PALETTE['GENERAL']['LINKS']}] from config"
819
898
  )
820
899
  else:
821
900
  self.subtensor = SubtensorInterface(defaults.subtensor.network)
822
901
  return self.subtensor
823
902
 
824
- def _run_command(self, cmd: Coroutine):
903
+ def _run_command(self, cmd: Coroutine, exit_early: bool = True):
825
904
  """
826
905
  Runs the supplied coroutine with `asyncio.run`
827
906
  """
@@ -837,7 +916,7 @@ class CLIManager:
837
916
  initiated = True
838
917
  result = await cmd
839
918
  return result
840
- except (ConnectionRefusedError, ssl.SSLError):
919
+ except (ConnectionRefusedError, ssl.SSLError, InvalidHandshake):
841
920
  err_console.print(f"Unable to connect to the chain: {self.subtensor}")
842
921
  verbose_console.print(traceback.format_exc())
843
922
  except (
@@ -854,7 +933,12 @@ class CLIManager:
854
933
  finally:
855
934
  if initiated is False:
856
935
  asyncio.create_task(cmd).cancel()
857
- raise typer.Exit()
936
+ if exit_early is True:
937
+ try:
938
+ raise typer.Exit()
939
+ except Exception as e: # ensures we always exit cleanly
940
+ if not isinstance(e, typer.Exit):
941
+ err_console.print(f"An unknown error has occurred: {e}")
858
942
 
859
943
  if sys.version_info < (3, 10):
860
944
  # For Python 3.9 or lower
@@ -866,11 +950,21 @@ class CLIManager:
866
950
  def main_callback(
867
951
  self,
868
952
  version: Annotated[
869
- Optional[bool], typer.Option("--version", callback=version_callback)
953
+ Optional[bool],
954
+ typer.Option(
955
+ "--version", callback=version_callback, help="Show BTCLI version"
956
+ ),
957
+ ] = None,
958
+ commands: Annotated[
959
+ Optional[bool],
960
+ typer.Option(
961
+ "--commands", callback=commands_callback, help="Show BTCLI commands"
962
+ ),
870
963
  ] = None,
871
964
  ):
872
965
  """
873
- Command line interface (CLI) for Bittensor. Uses the values in the configuration file. These values can be overriden by passing them explicitly in the command line.
966
+ Command line interface (CLI) for Bittensor. Uses the values in the configuration file. These values can be
967
+ overriden by passing them explicitly in the command line.
874
968
  """
875
969
  # Load or create the config file
876
970
  if os.path.exists(self.config_path):
@@ -1178,39 +1272,16 @@ class CLIManager:
1178
1272
  :return: created Wallet object
1179
1273
  """
1180
1274
  # Prompt for missing attributes specified in ask_for
1181
-
1182
- if wallet_path:
1183
- if wallet_path == "default":
1184
- wallet_path = defaults.wallet.path
1185
-
1186
- elif self.config.get("wallet_path"):
1187
- wallet_path = self.config.get("wallet_path")
1188
- console.print(
1189
- f"Using the wallet path from config:[bold magenta] {wallet_path}"
1190
- )
1191
-
1192
- if WO.PATH in ask_for and not wallet_path:
1193
- wallet_path = Prompt.ask(
1194
- "Enter the [blue]wallet path[/blue]"
1195
- + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-path`)[/dark_sea_green3 italic]",
1196
- default=defaults.wallet.path,
1197
- )
1198
- if wallet_path:
1199
- wallet_path = os.path.expanduser(wallet_path)
1200
- else:
1201
- wallet_path = os.path.expanduser(defaults.wallet.path)
1202
- console.print(f"Using default wallet path: ({defaults.wallet.path})")
1203
-
1204
1275
  if WO.NAME in ask_for and not wallet_name:
1205
1276
  if self.config.get("wallet_name"):
1206
1277
  wallet_name = self.config.get("wallet_name")
1207
1278
  console.print(
1208
- f"Using the wallet name from config:[bold cyan] {wallet_name}"
1279
+ f"Using the [blue]wallet name[/blue] from config:[bold cyan] {wallet_name}"
1209
1280
  )
1210
1281
  else:
1211
1282
  wallet_name = Prompt.ask(
1212
1283
  "Enter the [blue]wallet name[/blue]"
1213
- + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-name`)[/dark_sea_green3 italic]",
1284
+ + f" [{COLOR_PALETTE['GENERAL']['HINT']} italic](Hint: You can set this with `btcli config set --wallet-name`)",
1214
1285
  default=defaults.wallet.name,
1215
1286
  )
1216
1287
 
@@ -1218,7 +1289,7 @@ class CLIManager:
1218
1289
  if self.config.get("wallet_hotkey"):
1219
1290
  wallet_hotkey = self.config.get("wallet_hotkey")
1220
1291
  console.print(
1221
- f"Using the wallet hotkey from config:[bold cyan] {wallet_hotkey}"
1292
+ f"Using the [blue]wallet hotkey[/blue] from config:[bold cyan] {wallet_hotkey}"
1222
1293
  )
1223
1294
  else:
1224
1295
  wallet_hotkey = Prompt.ask(
@@ -1226,8 +1297,27 @@ class CLIManager:
1226
1297
  + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-hotkey`)[/dark_sea_green3 italic]",
1227
1298
  default=defaults.wallet.hotkey,
1228
1299
  )
1300
+ if wallet_path:
1301
+ if wallet_path == "default":
1302
+ wallet_path = defaults.wallet.path
1303
+
1304
+ elif self.config.get("wallet_path"):
1305
+ wallet_path = self.config.get("wallet_path")
1306
+ console.print(
1307
+ f"Using the [blue]wallet path[/blue] from config:[bold magenta] {wallet_path}"
1308
+ )
1309
+ else:
1310
+ wallet_path = defaults.wallet.path
1229
1311
 
1312
+ if WO.PATH in ask_for and not wallet_path:
1313
+ wallet_path = Prompt.ask(
1314
+ "Enter the [blue]wallet path[/blue]"
1315
+ + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-path`)[/dark_sea_green3 italic]",
1316
+ default=defaults.wallet.path,
1317
+ )
1230
1318
  # Create the Wallet object
1319
+ if wallet_path:
1320
+ wallet_path = os.path.expanduser(wallet_path)
1231
1321
  wallet = Wallet(name=wallet_name, path=wallet_path, hotkey=wallet_hotkey)
1232
1322
 
1233
1323
  # Validate the wallet if required
@@ -1383,7 +1473,7 @@ class CLIManager:
1383
1473
  "Netuids must be a comma-separated list of ints, e.g., `--netuids 1,2,3,4`.",
1384
1474
  )
1385
1475
 
1386
- ask_for = [WO.NAME] if not all_wallets else []
1476
+ ask_for = [WO.NAME, WO.PATH] if not all_wallets else [WO.PATH]
1387
1477
  validate = WV.WALLET if not all_wallets else WV.NONE
1388
1478
  wallet = self.wallet_ask(
1389
1479
  wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate
@@ -1413,6 +1503,7 @@ class CLIManager:
1413
1503
  include_hotkeys,
1414
1504
  exclude_hotkeys,
1415
1505
  netuids_filter=netuids,
1506
+ verbose=verbose,
1416
1507
  )
1417
1508
  )
1418
1509
 
@@ -1430,12 +1521,9 @@ class CLIManager:
1430
1521
  None,
1431
1522
  "--amount",
1432
1523
  "-a",
1433
- prompt=False,
1524
+ prompt=True,
1434
1525
  help="Amount (in TAO) to transfer.",
1435
1526
  ),
1436
- transfer_all: bool = typer.Option(
1437
- False, "--all", prompt=False, help="Transfer all available balance."
1438
- ),
1439
1527
  wallet_name: str = Options.wallet_name,
1440
1528
  wallet_path: str = Options.wallet_path,
1441
1529
  wallet_hotkey: str = Options.wallet_hotkey,
@@ -1471,25 +1559,20 @@ class CLIManager:
1471
1559
  wallet_name,
1472
1560
  wallet_path,
1473
1561
  wallet_hotkey,
1474
- ask_for=[WO.NAME],
1562
+ ask_for=[WO.NAME, WO.PATH],
1475
1563
  validate=WV.WALLET,
1476
1564
  )
1565
+
1566
+ # For Rao games - temporarily commented out
1567
+ # effective_network = get_effective_network(self.config, network)
1568
+ # if is_rao_network(effective_network):
1569
+ # print_error("This command is disabled on the 'rao' network.")
1570
+ # raise typer.Exit()
1571
+
1477
1572
  subtensor = self.initialize_chain(network)
1478
- if transfer_all and amount:
1479
- print_error("Cannot specify an amount and '--all' flag.")
1480
- raise typer.Exit()
1481
- elif transfer_all:
1482
- amount = 0
1483
- elif not amount:
1484
- amount = FloatPrompt.ask("Enter amount (in TAO) to transfer.")
1485
1573
  return self._run_command(
1486
1574
  wallets.transfer(
1487
- wallet,
1488
- subtensor,
1489
- destination_ss58_address,
1490
- amount,
1491
- transfer_all,
1492
- prompt,
1575
+ wallet, subtensor, destination_ss58_address, amount, prompt
1493
1576
  )
1494
1577
  )
1495
1578
 
@@ -1528,7 +1611,7 @@ class CLIManager:
1528
1611
  wallet_name,
1529
1612
  wallet_path,
1530
1613
  wallet_hotkey,
1531
- ask_for=[WO.NAME, WO.HOTKEY],
1614
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
1532
1615
  validate=WV.WALLET_AND_HOTKEY,
1533
1616
  )
1534
1617
  if not destination_hotkey_name:
@@ -1540,7 +1623,7 @@ class CLIManager:
1540
1623
  wallet_name,
1541
1624
  wallet_path,
1542
1625
  destination_hotkey_name,
1543
- ask_for=[WO.NAME, WO.HOTKEY],
1626
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
1544
1627
  validate=WV.WALLET_AND_HOTKEY,
1545
1628
  )
1546
1629
  self.initialize_chain(network)
@@ -1596,6 +1679,8 @@ class CLIManager:
1596
1679
 
1597
1680
  [bold]Note[/bold]: The `inspect` command is for displaying information only and does not perform any transactions or state changes on the blockchain. It is intended to be used with Bittensor CLI and not as a standalone function in user code.
1598
1681
  """
1682
+ print_error("This command is disabled on the 'rao' network.")
1683
+ raise typer.Exit()
1599
1684
  self.verbosity_handler(quiet, verbose)
1600
1685
 
1601
1686
  if netuids:
@@ -1606,11 +1691,12 @@ class CLIManager:
1606
1691
  )
1607
1692
 
1608
1693
  # if all-wallets is entered, ask for path
1609
- ask_for = [WO.NAME] if not all_wallets else []
1694
+ ask_for = [WO.NAME, WO.PATH] if not all_wallets else [WO.PATH]
1610
1695
  validate = WV.WALLET if not all_wallets else WV.NONE
1611
1696
  wallet = self.wallet_ask(
1612
1697
  wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate
1613
1698
  )
1699
+
1614
1700
  self.initialize_chain(network)
1615
1701
  return self._run_command(
1616
1702
  wallets.inspect(
@@ -1672,7 +1758,6 @@ class CLIManager:
1672
1758
  "--max-successes",
1673
1759
  help="Set the maximum number of times to successfully run the faucet for this command.",
1674
1760
  ),
1675
- prompt: bool = Options.prompt,
1676
1761
  ):
1677
1762
  """
1678
1763
  Obtain test TAO tokens by performing Proof of Work (PoW).
@@ -1696,7 +1781,7 @@ class CLIManager:
1696
1781
  wallet_name,
1697
1782
  wallet_path,
1698
1783
  wallet_hotkey,
1699
- ask_for=[WO.NAME],
1784
+ ask_for=[WO.NAME, WO.PATH],
1700
1785
  validate=WV.WALLET,
1701
1786
  )
1702
1787
  return self._run_command(
@@ -1711,7 +1796,6 @@ class CLIManager:
1711
1796
  output_in_place,
1712
1797
  verbose,
1713
1798
  max_successes,
1714
- prompt,
1715
1799
  )
1716
1800
  )
1717
1801
 
@@ -1754,7 +1838,8 @@ class CLIManager:
1754
1838
 
1755
1839
  if not wallet_name:
1756
1840
  wallet_name = Prompt.ask(
1757
- "Enter the name of the new wallet", default=defaults.wallet.name
1841
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
1842
+ default=defaults.wallet.name,
1758
1843
  )
1759
1844
 
1760
1845
  wallet = Wallet(wallet_name, wallet_hotkey, wallet_path)
@@ -1808,7 +1893,8 @@ class CLIManager:
1808
1893
 
1809
1894
  if not wallet_name:
1810
1895
  wallet_name = Prompt.ask(
1811
- "Enter the name of the new wallet", default=defaults.wallet.name
1896
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
1897
+ default=defaults.wallet.name,
1812
1898
  )
1813
1899
  wallet = Wallet(wallet_name, wallet_hotkey, wallet_path)
1814
1900
 
@@ -1841,6 +1927,8 @@ class CLIManager:
1841
1927
  use_password: bool = typer.Option(
1842
1928
  False, # Overriden to False
1843
1929
  help="Set to 'True' to protect the generated Bittensor key with a password.",
1930
+ is_flag=True,
1931
+ flag_value=True,
1844
1932
  ),
1845
1933
  quiet: bool = Options.quiet,
1846
1934
  verbose: bool = Options.verbose,
@@ -1897,7 +1985,10 @@ class CLIManager:
1897
1985
  use_password: bool = typer.Option(
1898
1986
  False, # Overriden to False
1899
1987
  help="Set to 'True' to protect the generated Bittensor key with a password.",
1988
+ is_flag=True,
1989
+ flag_value=True,
1900
1990
  ),
1991
+ uri: Optional[str] = Options.uri,
1901
1992
  quiet: bool = Options.quiet,
1902
1993
  verbose: bool = Options.verbose,
1903
1994
  ):
@@ -1919,12 +2010,14 @@ class CLIManager:
1919
2010
 
1920
2011
  if not wallet_name:
1921
2012
  wallet_name = Prompt.ask(
1922
- "Enter the wallet name", default=defaults.wallet.name
2013
+ f"Enter the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]wallet name",
2014
+ default=defaults.wallet.name,
1923
2015
  )
1924
2016
 
1925
2017
  if not wallet_hotkey:
1926
2018
  wallet_hotkey = Prompt.ask(
1927
- "Enter the name of the new hotkey", default=defaults.wallet.hotkey
2019
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey",
2020
+ default=defaults.wallet.hotkey,
1928
2021
  )
1929
2022
 
1930
2023
  wallet = self.wallet_ask(
@@ -1934,8 +2027,9 @@ class CLIManager:
1934
2027
  ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
1935
2028
  validate=WV.WALLET,
1936
2029
  )
1937
- n_words = get_n_words(n_words)
1938
- return self._run_command(wallets.new_hotkey(wallet, n_words, use_password))
2030
+ if not uri:
2031
+ n_words = get_n_words(n_words)
2032
+ return self._run_command(wallets.new_hotkey(wallet, n_words, use_password, uri))
1939
2033
 
1940
2034
  def wallet_new_coldkey(
1941
2035
  self,
@@ -1949,6 +2043,7 @@ class CLIManager:
1949
2043
  help="The number of words used in the mnemonic. Options: [12, 15, 18, 21, 24]",
1950
2044
  ),
1951
2045
  use_password: Optional[bool] = Options.use_password,
2046
+ uri: Optional[str] = Options.uri,
1952
2047
  quiet: bool = Options.quiet,
1953
2048
  verbose: bool = Options.verbose,
1954
2049
  ):
@@ -1974,7 +2069,8 @@ class CLIManager:
1974
2069
 
1975
2070
  if not wallet_name:
1976
2071
  wallet_name = Prompt.ask(
1977
- "Enter the name of the new wallet", default=defaults.wallet.name
2072
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
2073
+ default=defaults.wallet.name,
1978
2074
  )
1979
2075
 
1980
2076
  wallet = self.wallet_ask(
@@ -1984,8 +2080,11 @@ class CLIManager:
1984
2080
  ask_for=[WO.NAME, WO.PATH],
1985
2081
  validate=WV.NONE,
1986
2082
  )
1987
- n_words = get_n_words(n_words)
1988
- return self._run_command(wallets.new_coldkey(wallet, n_words, use_password))
2083
+ if not uri:
2084
+ n_words = get_n_words(n_words)
2085
+ return self._run_command(
2086
+ wallets.new_coldkey(wallet, n_words, use_password, uri)
2087
+ )
1989
2088
 
1990
2089
  def wallet_check_ck_swap(
1991
2090
  self,
@@ -2019,6 +2118,7 @@ class CLIManager:
2019
2118
  wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2020
2119
  n_words: Optional[int] = None,
2021
2120
  use_password: bool = Options.use_password,
2121
+ uri: Optional[str] = Options.uri,
2022
2122
  quiet: bool = Options.quiet,
2023
2123
  verbose: bool = Options.verbose,
2024
2124
  ):
@@ -2042,12 +2142,13 @@ class CLIManager:
2042
2142
 
2043
2143
  if not wallet_name:
2044
2144
  wallet_name = Prompt.ask(
2045
- "Enter the name of the new wallet (coldkey)",
2145
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
2046
2146
  default=defaults.wallet.name,
2047
2147
  )
2048
2148
  if not wallet_hotkey:
2049
2149
  wallet_hotkey = Prompt.ask(
2050
- "Enter the the name of the new hotkey", default=defaults.wallet.hotkey
2150
+ f"Enter the the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey",
2151
+ default=defaults.wallet.hotkey,
2051
2152
  )
2052
2153
 
2053
2154
  self.verbosity_handler(quiet, verbose)
@@ -2058,12 +2159,14 @@ class CLIManager:
2058
2159
  ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
2059
2160
  validate=WV.NONE,
2060
2161
  )
2061
- n_words = get_n_words(n_words)
2162
+ if not uri:
2163
+ n_words = get_n_words(n_words)
2062
2164
  return self._run_command(
2063
2165
  wallets.wallet_create(
2064
2166
  wallet,
2065
2167
  n_words,
2066
2168
  use_password,
2169
+ uri,
2067
2170
  )
2068
2171
  )
2069
2172
 
@@ -2108,8 +2211,18 @@ class CLIManager:
2108
2211
 
2109
2212
  """
2110
2213
  self.verbosity_handler(quiet, verbose)
2111
-
2112
- if ss58_addresses:
2214
+ wallet = None
2215
+ if all_balances:
2216
+ ask_for = [WO.PATH]
2217
+ validate = WV.NONE
2218
+ wallet = self.wallet_ask(
2219
+ wallet_name,
2220
+ wallet_path,
2221
+ wallet_hotkey,
2222
+ ask_for=ask_for,
2223
+ validate=validate,
2224
+ )
2225
+ elif ss58_addresses:
2113
2226
  valid_ss58s = [
2114
2227
  ss58 for ss58 in set(ss58_addresses) if is_valid_ss58_address(ss58)
2115
2228
  ]
@@ -2119,20 +2232,45 @@ class CLIManager:
2119
2232
  print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.")
2120
2233
 
2121
2234
  if valid_ss58s:
2122
- wallet = None
2123
2235
  ss58_addresses = valid_ss58s
2124
2236
  else:
2125
2237
  raise typer.Exit()
2126
2238
  else:
2127
- ask_for = [] if all_balances else [WO.NAME]
2128
- validate = WV.NONE if all_balances else WV.WALLET
2129
- wallet = self.wallet_ask(
2130
- wallet_name,
2131
- wallet_path,
2132
- wallet_hotkey,
2133
- ask_for=ask_for,
2134
- validate=validate,
2135
- )
2239
+ if wallet_name:
2240
+ coldkey_or_ss58 = wallet_name
2241
+ else:
2242
+ coldkey_or_ss58 = Prompt.ask(
2243
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 addresses[/blue] (comma-separated)",
2244
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2245
+ )
2246
+
2247
+ # Split and validate ss58 addresses
2248
+ coldkey_or_ss58_list = [x.strip() for x in coldkey_or_ss58.split(",")]
2249
+ if any(is_valid_ss58_address(x) for x in coldkey_or_ss58_list):
2250
+ valid_ss58s = [
2251
+ ss58 for ss58 in coldkey_or_ss58_list if is_valid_ss58_address(ss58)
2252
+ ]
2253
+ invalid_ss58s = set(coldkey_or_ss58_list) - set(valid_ss58s)
2254
+ for invalid_ss58 in invalid_ss58s:
2255
+ print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.")
2256
+
2257
+ if valid_ss58s:
2258
+ ss58_addresses = valid_ss58s
2259
+ else:
2260
+ raise typer.Exit()
2261
+ else:
2262
+ wallet_name = (
2263
+ coldkey_or_ss58_list[0] if coldkey_or_ss58_list else wallet_name
2264
+ )
2265
+ ask_for = [WO.NAME, WO.PATH]
2266
+ validate = WV.WALLET
2267
+ wallet = self.wallet_ask(
2268
+ wallet_name,
2269
+ wallet_path,
2270
+ wallet_hotkey,
2271
+ ask_for=ask_for,
2272
+ validate=validate,
2273
+ )
2136
2274
  subtensor = self.initialize_chain(network)
2137
2275
  return self._run_command(
2138
2276
  wallets.wallet_balance(wallet, subtensor, all_balances, ss58_addresses)
@@ -2158,19 +2296,23 @@ class CLIManager:
2158
2296
  [green]$[/green] btcli wallet history
2159
2297
 
2160
2298
  """
2299
+ # TODO: Fetch effective network and redirect users accordingly - this only works on finney
2300
+ # no_use_config_str = "Using the network [dark_orange]finney[/dark_orange] and ignoring network/chain configs"
2161
2301
 
2162
- no_use_config_str = "Using the network [dark_orange]finney[/dark_orange] and ignoring network/chain configs"
2302
+ # if self.config.get("network"):
2303
+ # if self.config.get("network") != "finney":
2304
+ # console.print(no_use_config_str)
2163
2305
 
2164
- if self.config.get("network"):
2165
- if self.config.get("network") != "finney":
2166
- console.print(no_use_config_str)
2306
+ # For Rao games
2307
+ print_error("This command is disabled on the 'rao' network.")
2308
+ raise typer.Exit()
2167
2309
 
2168
2310
  self.verbosity_handler(quiet, verbose)
2169
2311
  wallet = self.wallet_ask(
2170
2312
  wallet_name,
2171
2313
  wallet_path,
2172
2314
  wallet_hotkey,
2173
- ask_for=[WO.NAME],
2315
+ ask_for=[WO.NAME, WO.PATH],
2174
2316
  validate=WV.WALLET,
2175
2317
  )
2176
2318
  return self._run_command(wallets.wallet_history(wallet))
@@ -2181,69 +2323,42 @@ class CLIManager:
2181
2323
  wallet_path: Optional[str] = Options.wallet_path,
2182
2324
  wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2183
2325
  network: Optional[list[str]] = Options.network,
2184
- display_name: str = typer.Option(
2326
+ name: str = typer.Option(
2185
2327
  "",
2186
- "--display-name",
2187
- "--display",
2328
+ "--name",
2188
2329
  help="The display name for the identity.",
2189
2330
  ),
2190
- legal_name: str = typer.Option(
2191
- "",
2192
- "--legal-name",
2193
- "--legal",
2194
- help="The legal name for the identity.",
2195
- ),
2196
2331
  web_url: str = typer.Option(
2197
2332
  "",
2198
2333
  "--web-url",
2199
2334
  "--web",
2200
2335
  help="The web URL for the identity.",
2201
2336
  ),
2202
- riot_handle: str = typer.Option(
2203
- "",
2204
- "--riot-handle",
2205
- "--riot",
2206
- help="The Riot handle for the identity.",
2207
- ),
2208
- email: str = typer.Option(
2209
- "",
2210
- help="The email address for the identity.",
2211
- ),
2212
- pgp_fingerprint: str = typer.Option(
2213
- "",
2214
- "--pgp-fingerprint",
2215
- "--pgp",
2216
- help="The PGP fingerprint for the identity.",
2217
- ),
2218
2337
  image_url: str = typer.Option(
2219
2338
  "",
2220
2339
  "--image-url",
2221
2340
  "--image",
2222
2341
  help="The image URL for the identity.",
2223
2342
  ),
2224
- info_: str = typer.Option(
2343
+ discord: str = typer.Option(
2225
2344
  "",
2226
- "--info",
2227
- "-i",
2228
- help="The info for the identity.",
2345
+ "--discord",
2346
+ help="The Discord handle for the identity.",
2229
2347
  ),
2230
- twitter_url: str = typer.Option(
2348
+ description: str = typer.Option(
2231
2349
  "",
2232
- "-x",
2233
- "-𝕏",
2234
- "--twitter-url",
2235
- "--twitter",
2236
- help="The 𝕏 (Twitter) URL for the identity.",
2350
+ "--description",
2351
+ help="The description for the identity.",
2237
2352
  ),
2238
- validator_id: Optional[bool] = typer.Option(
2239
- None,
2240
- "--validator/--not-validator",
2241
- help="Are you updating a validator hotkey identity?",
2353
+ additional: str = typer.Option(
2354
+ "",
2355
+ "--additional",
2356
+ help="Additional details for the identity.",
2242
2357
  ),
2243
- subnet_netuid: Optional[int] = typer.Option(
2244
- None,
2245
- "--netuid",
2246
- help="Netuid if you are updating identity of a subnet owner",
2358
+ github_repo: str = typer.Option(
2359
+ "",
2360
+ "--github",
2361
+ help="The GitHub repository for the identity.",
2247
2362
  ),
2248
2363
  quiet: bool = Options.quiet,
2249
2364
  verbose: bool = Options.verbose,
@@ -2271,94 +2386,67 @@ class CLIManager:
2271
2386
  wallet_name,
2272
2387
  wallet_path,
2273
2388
  wallet_hotkey,
2274
- ask_for=[WO.HOTKEY, WO.NAME],
2275
- validate=WV.WALLET_AND_HOTKEY,
2389
+ ask_for=[WO.NAME],
2390
+ validate=WV.WALLET,
2276
2391
  )
2277
2392
 
2278
- if not any(
2279
- [
2280
- display_name,
2281
- legal_name,
2282
- web_url,
2283
- riot_handle,
2284
- email,
2285
- pgp_fingerprint,
2286
- image_url,
2287
- info_,
2288
- twitter_url,
2289
- ]
2290
- ):
2291
- console.print(
2292
- "[yellow]All fields are optional. Press Enter to skip a field.[/yellow]"
2293
- )
2294
- text_rejection = partial(
2295
- retry_prompt,
2296
- rejection=lambda x: sys.getsizeof(x) > 113,
2297
- rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.",
2298
- )
2393
+ current_identity = self._run_command(
2394
+ wallets.get_id(
2395
+ self.initialize_chain(network),
2396
+ wallet.coldkeypub.ss58_address,
2397
+ "Current on-chain identity",
2398
+ ),
2399
+ exit_early=False,
2400
+ )
2299
2401
 
2300
- def pgp_check(s: str):
2301
- try:
2302
- if s.startswith("0x"):
2303
- s = s[2:] # Strip '0x'
2304
- pgp_fingerprint_encoded = binascii.unhexlify(s.replace(" ", ""))
2305
- except Exception:
2306
- return True
2307
- return True if len(pgp_fingerprint_encoded) != 20 else False
2308
-
2309
- display_name = display_name or text_rejection("Display name")
2310
- legal_name = legal_name or text_rejection("Legal name")
2311
- web_url = web_url or text_rejection("Web URL")
2312
- riot_handle = riot_handle or text_rejection("Riot handle")
2313
- email = email or text_rejection("Email address")
2314
- pgp_fingerprint = pgp_fingerprint or retry_prompt(
2315
- "PGP fingerprint (Eg: A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678)",
2316
- lambda s: False if not s else pgp_check(s),
2317
- "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes.",
2318
- )
2319
- image_url = image_url or text_rejection("Image URL")
2320
- info_ = info_ or text_rejection("Enter info")
2321
- twitter_url = twitter_url or text_rejection("𝕏 (Twitter) URL")
2322
-
2323
- validator_id = validator_id or Confirm.ask(
2324
- "Are you updating a [bold blue]validator hotkey[/bold blue] identity or a [bold blue]subnet "
2325
- "owner[/bold blue] identity?\n"
2326
- "Enter [bold green]Y[/bold green] for [bold]validator hotkey[/bold] or [bold red]N[/bold red] for "
2327
- "[bold]subnet owner[/bold]",
2328
- show_choices=True,
2329
- )
2402
+ if prompt:
2403
+ if not Confirm.ask(
2404
+ "Cost to register an [blue]Identity[/blue] is [blue]0.1 TAO[/blue],"
2405
+ " are you sure you wish to continue?"
2406
+ ):
2407
+ console.print(":cross_mark: Aborted!")
2408
+ raise typer.Exit()
2330
2409
 
2331
- if validator_id is False:
2332
- subnet_netuid = IntPrompt.ask("Enter the netuid of the subnet you own")
2410
+ identity = prompt_for_identity(
2411
+ current_identity,
2412
+ name,
2413
+ web_url,
2414
+ image_url,
2415
+ discord,
2416
+ description,
2417
+ additional,
2418
+ github_repo,
2419
+ )
2333
2420
 
2334
2421
  return self._run_command(
2335
2422
  wallets.set_id(
2336
2423
  wallet,
2337
2424
  self.initialize_chain(network),
2338
- display_name,
2339
- legal_name,
2340
- web_url,
2341
- pgp_fingerprint,
2342
- riot_handle,
2343
- email,
2344
- image_url,
2345
- twitter_url,
2346
- info_,
2347
- validator_id,
2425
+ identity["name"],
2426
+ identity["url"],
2427
+ identity["image"],
2428
+ identity["discord"],
2429
+ identity["description"],
2430
+ identity["additional"],
2431
+ identity["github_repo"],
2348
2432
  prompt,
2349
- subnet_netuid,
2350
2433
  )
2351
2434
  )
2352
2435
 
2353
2436
  def wallet_get_id(
2354
2437
  self,
2355
- target_ss58_address: str = typer.Option(
2438
+ wallet_name: Optional[str] = Options.wallet_name,
2439
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2440
+ wallet_path: Optional[str] = Options.wallet_path,
2441
+ coldkey_ss58=typer.Option(
2356
2442
  None,
2443
+ "--ss58",
2444
+ "--coldkey_ss58",
2445
+ "--coldkey.ss58_address",
2446
+ "--coldkey.ss58",
2357
2447
  "--key",
2358
2448
  "-k",
2359
- "--ss58",
2360
- help="The coldkey or hotkey ss58 address to query.",
2361
- prompt=True,
2449
+ help="Coldkey address of the wallet",
2362
2450
  ),
2363
2451
  network: Optional[list[str]] = Options.network,
2364
2452
  quiet: bool = Options.quiet,
@@ -2381,13 +2469,28 @@ class CLIManager:
2381
2469
 
2382
2470
  [bold]Note[/bold]: This command is primarily used for informational purposes and has no side effects on the blockchain network state.
2383
2471
  """
2384
- if not is_valid_ss58_address(target_ss58_address):
2385
- print_error("You have entered an incorrect ss58 address. Please try again")
2386
- raise typer.Exit()
2472
+ wallet = None
2473
+ if coldkey_ss58:
2474
+ if not is_valid_ss58_address(coldkey_ss58):
2475
+ print_error("You entered an invalid ss58 address")
2476
+ raise typer.Exit()
2477
+ else:
2478
+ coldkey_or_ss58 = Prompt.ask(
2479
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]",
2480
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2481
+ )
2482
+ if is_valid_ss58_address(coldkey_or_ss58):
2483
+ coldkey_ss58 = coldkey_or_ss58
2484
+ else:
2485
+ wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name
2486
+ wallet = self.wallet_ask(
2487
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2488
+ )
2489
+ coldkey_ss58 = wallet.coldkeypub.ss58_address
2387
2490
 
2388
2491
  self.verbosity_handler(quiet, verbose)
2389
2492
  return self._run_command(
2390
- wallets.get_id(self.initialize_chain(network), target_ss58_address)
2493
+ wallets.get_id(self.initialize_chain(network), coldkey_ss58)
2391
2494
  )
2392
2495
 
2393
2496
  def wallet_sign(
@@ -2421,882 +2524,127 @@ class CLIManager:
2421
2524
  self.verbosity_handler(quiet, verbose)
2422
2525
  if use_hotkey is None:
2423
2526
  use_hotkey = Confirm.ask(
2424
- "Would you like to sign the transaction using your [red]hotkey[/red]?"
2425
- "\n[Type [red]y[/red] for [red]hotkey[/red] and [blue]n[/blue] for [blue]coldkey[/blue]] "
2426
- "(default is [blue]coldkey[/blue])",
2527
+ f"Would you like to sign the transaction using your [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]?"
2528
+ f"\n[Type [{COLOR_PALETTE['GENERAL']['HOTKEY']}]y[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] for [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]"
2529
+ f" and [{COLOR_PALETTE['GENERAL']['COLDKEY']}]n[/{COLOR_PALETTE['GENERAL']['COLDKEY']}] for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]] (default is [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey[/{COLOR_PALETTE['GENERAL']['COLDKEY']}])",
2427
2530
  default=False,
2428
2531
  )
2429
2532
 
2430
- ask_for = [WO.HOTKEY, WO.NAME] if use_hotkey else [WO.NAME]
2533
+ ask_for = [WO.HOTKEY, WO.PATH, WO.NAME] if use_hotkey else [WO.NAME, WO.PATH]
2431
2534
  validate = WV.WALLET_AND_HOTKEY if use_hotkey else WV.WALLET
2432
2535
 
2433
2536
  wallet = self.wallet_ask(
2434
2537
  wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate
2435
2538
  )
2436
2539
  if not message:
2437
- message = typer.prompt("Enter the message to encode and sign")
2540
+ message = Prompt.ask("Enter the [blue]message[/blue] to encode and sign")
2438
2541
 
2439
2542
  return self._run_command(wallets.sign(wallet, message, use_hotkey))
2440
2543
 
2441
- def root_list(
2544
+ def stake_list(
2442
2545
  self,
2443
2546
  network: Optional[list[str]] = Options.network,
2547
+ wallet_name: Optional[str] = Options.wallet_name,
2548
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2549
+ wallet_path: Optional[str] = Options.wallet_path,
2550
+ coldkey_ss58=typer.Option(
2551
+ None,
2552
+ "--ss58",
2553
+ "--coldkey_ss58",
2554
+ "--coldkey.ss58_address",
2555
+ "--coldkey.ss58",
2556
+ help="Coldkey address of the wallet",
2557
+ ),
2558
+ live: bool = Options.live,
2444
2559
  quiet: bool = Options.quiet,
2445
2560
  verbose: bool = Options.verbose,
2561
+ no_prompt: bool = Options.prompt,
2562
+ # TODO add: all-wallets, reuse_last, html_output
2446
2563
  ):
2447
- """
2448
- Show the neurons (root network validators) in the root network (netuid = 0).
2449
-
2450
- USAGE
2451
-
2452
- The command fetches and lists the neurons (root network validators) in the root network, showing their unique identifiers (UIDs), names, addresses, stakes, and whether they are part of the senate (network governance body).
2453
-
2454
- This command is useful for understanding the composition and governance structure of the Bittensor network's root network. It provides insights into which neurons hold significant influence and responsibility within the Bittensor network.
2564
+ """List all stake accounts for wallet."""
2565
+ self.verbosity_handler(quiet, verbose)
2455
2566
 
2456
- EXAMPLE
2567
+ wallet = None
2568
+ if coldkey_ss58:
2569
+ if not is_valid_ss58_address(coldkey_ss58):
2570
+ print_error("You entered an invalid ss58 address")
2571
+ raise typer.Exit()
2572
+ else:
2573
+ if wallet_name:
2574
+ coldkey_or_ss58 = wallet_name
2575
+ else:
2576
+ coldkey_or_ss58 = Prompt.ask(
2577
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]",
2578
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2579
+ )
2580
+ if is_valid_ss58_address(coldkey_or_ss58):
2581
+ coldkey_ss58 = coldkey_or_ss58
2582
+ else:
2583
+ wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name
2584
+ wallet = self.wallet_ask(
2585
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2586
+ )
2457
2587
 
2458
- [green]$[/green] btcli root list
2459
- """
2460
- self.verbosity_handler(quiet, verbose)
2461
2588
  return self._run_command(
2462
- root.root_list(subtensor=self.initialize_chain(network))
2589
+ stake.stake_list(
2590
+ wallet,
2591
+ coldkey_ss58,
2592
+ self.initialize_chain(network),
2593
+ live,
2594
+ verbose,
2595
+ no_prompt,
2596
+ )
2463
2597
  )
2464
2598
 
2465
- def root_set_weights(
2599
+ def stake_add(
2466
2600
  self,
2467
- network: Optional[list[str]] = Options.network,
2601
+ stake_all: bool = typer.Option(
2602
+ False,
2603
+ "--all-tokens",
2604
+ "--all",
2605
+ "-a",
2606
+ help="When set, the command stakes all the available TAO from the coldkey.",
2607
+ ),
2608
+ amount: float = typer.Option(
2609
+ 0.0, "--amount", help="The amount of TAO to stake"
2610
+ ),
2611
+ max_stake: float = typer.Option(
2612
+ 0.0,
2613
+ "--max-stake",
2614
+ "-m",
2615
+ help="Stake is sent to a hotkey only until the hotkey's total stake is less than or equal to this maximum staked TAO. If a hotkey already has stake greater than this amount, then stake is not added to this hotkey.",
2616
+ ),
2617
+ include_hotkeys: str = typer.Option(
2618
+ "",
2619
+ "--include-hotkeys",
2620
+ "-in",
2621
+ "--hotkey-ss58-address",
2622
+ help="Specifies hotkeys by name or ss58 address to stake to. For example, `-in hk1,hk2`",
2623
+ ),
2624
+ exclude_hotkeys: str = typer.Option(
2625
+ "",
2626
+ "--exclude-hotkeys",
2627
+ "-ex",
2628
+ help="Specifies hotkeys by name or ss58 address to not to stake to (use this option only with `--all-hotkeys`)"
2629
+ " i.e. `--all-hotkeys -ex hk3,hk4`",
2630
+ ),
2631
+ all_hotkeys: bool = typer.Option(
2632
+ False,
2633
+ help="When set, this command stakes to all hotkeys associated with the wallet. Do not use if specifying "
2634
+ "hotkeys in `--include-hotkeys`.",
2635
+ ),
2636
+ netuid: Optional[int] = Options.netuid_not_req,
2637
+ all_netuids: bool = Options.all_netuids,
2468
2638
  wallet_name: str = Options.wallet_name,
2469
2639
  wallet_path: str = Options.wallet_path,
2470
2640
  wallet_hotkey: str = Options.wallet_hotkey,
2471
- netuids=typer.Option(
2472
- None,
2473
- "--netuids",
2474
- "--netuid",
2475
- "-n",
2476
- help="Set the netuid(s) to set weights to. Separate multiple netuids with a comma, for example: `-n 0,1,2`.",
2477
- ),
2478
- weights: str = Options.weights,
2641
+ network: Optional[list[str]] = Options.network,
2479
2642
  prompt: bool = Options.prompt,
2480
2643
  quiet: bool = Options.quiet,
2481
2644
  verbose: bool = Options.verbose,
2482
2645
  ):
2483
2646
  """
2484
- Set the weights for different subnets, by setting them in the root network.
2485
-
2486
- To use this command, you should specify the netuids and corresponding weights you wish to assign. This command is used by validators registered to the root subnet to influence the distribution of subnet rewards and responsibilities.
2487
-
2488
- You must have a comprehensive understanding of the dynamics of the subnets to use this command. It is a powerful tool that directly impacts the subnet's operational mechanics and reward distribution.
2489
-
2490
- EXAMPLE
2491
-
2492
- With no spaces between the passed values:
2493
-
2494
- [green]$[/green] btcli root set-weights --netuids 1,2 --weights 0.2,0.3
2495
-
2496
- or
2497
-
2498
- Include double quotes to include spaces between the passed values:
2499
-
2500
- [green]$[/green] btcli root set-weights --netuids "1, 2" --weights "0.2, 0.3"
2501
- """
2502
- self.verbosity_handler(quiet, verbose)
2503
-
2504
- if netuids:
2505
- netuids = parse_to_list(
2506
- netuids,
2507
- int,
2508
- "Netuids must be a comma-separated list of ints, e.g., `--netuid 1,2,3,4`.",
2509
- )
2510
- else:
2511
- netuids = list_prompt(netuids, int, "Enter netuids (e.g: 1, 4, 6)")
2512
-
2513
- if weights:
2514
- weights = parse_to_list(
2515
- weights,
2516
- float,
2517
- "Weights must be a comma-separated list of floats, e.g., `--weights 0.3,0.4,0.3`.",
2518
- )
2519
- else:
2520
- weights = list_prompt(
2521
- weights, float, "Enter weights (e.g. 0.02, 0.03, 0.01)"
2522
- )
2523
-
2524
- if len(netuids) != len(weights):
2525
- raise typer.BadParameter(
2526
- "The number of netuids and weights must be the same."
2527
- )
2528
-
2529
- wallet = self.wallet_ask(
2530
- wallet_name,
2531
- wallet_path,
2532
- wallet_hotkey,
2533
- ask_for=[WO.HOTKEY, WO.NAME],
2534
- validate=WV.WALLET_AND_HOTKEY,
2535
- )
2536
- self._run_command(
2537
- root.set_weights(
2538
- wallet, self.initialize_chain(network), netuids, weights, prompt
2539
- )
2540
- )
2541
-
2542
- def root_get_weights(
2543
- self,
2544
- network: Optional[list[str]] = Options.network,
2545
- limit_min_col: Optional[int] = typer.Option(
2546
- None,
2547
- "--limit-min-col",
2548
- "--min",
2549
- help="Limit the left display of the table to this column.",
2550
- ),
2551
- limit_max_col: Optional[int] = typer.Option(
2552
- None,
2553
- "--limit-max-col",
2554
- "--max",
2555
- help="Limit the right display of the table to this column.",
2556
- ),
2557
- reuse_last: bool = Options.reuse_last,
2558
- html_output: bool = Options.html_output,
2559
- quiet: bool = Options.quiet,
2560
- verbose: bool = Options.verbose,
2561
- ):
2562
- """
2563
- Shows a table listing the weights assigned to each subnet in the root network.
2564
-
2565
- This command provides visibility into how network responsibilities and rewards are distributed among various subnets. This information is crucial for understanding the current influence and reward distribution across different subnets. Use this command if you are interested in the governance and operational dynamics of the Bittensor network.
2566
-
2567
- EXAMPLE
2568
-
2569
- [green]$[/green] btcli root get_weights
2570
- """
2571
- self.verbosity_handler(quiet, verbose)
2572
- if (reuse_last or html_output) and self.config.get("use_cache") is False:
2573
- err_console.print(
2574
- "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'."
2575
- "Change it to 'False' using `btcli config set`."
2576
- )
2577
- raise typer.Exit()
2578
- if not reuse_last:
2579
- subtensor = self.initialize_chain(network)
2580
- else:
2581
- subtensor = None
2582
- return self._run_command(
2583
- root.get_weights(
2584
- subtensor,
2585
- limit_min_col,
2586
- limit_max_col,
2587
- reuse_last,
2588
- html_output,
2589
- not self.config.get("use_cache", True),
2590
- )
2591
- )
2592
-
2593
- def root_boost(
2594
- self,
2595
- network: Optional[list[str]] = Options.network,
2596
- wallet_name: str = Options.wallet_name,
2597
- wallet_path: Optional[str] = Options.wallet_path,
2598
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2599
- netuid: int = Options.netuid,
2600
- amount: float = typer.Option(
2601
- None,
2602
- "--amount",
2603
- "--increase",
2604
- "-a",
2605
- prompt="Enter the boost amount (added to existing weight)",
2606
- help="Amount (float) to boost (added to the existing weight), (e.g. 0.01)",
2607
- ),
2608
- prompt: bool = Options.prompt,
2609
- quiet: bool = Options.quiet,
2610
- verbose: bool = Options.verbose,
2611
- ):
2612
- """
2613
- Increase (boost) the weights for a specific subnet in the root network. Any amount provided will be added to the subnet's existing weight.
2614
-
2615
- EXAMPLE
2616
-
2617
- [green]$[/green] btcli root boost --netuid 1 --increase 0.01
2618
- """
2619
- self.verbosity_handler(quiet, verbose)
2620
- wallet = self.wallet_ask(
2621
- wallet_name,
2622
- wallet_path,
2623
- wallet_hotkey,
2624
- ask_for=[WO.NAME, WO.HOTKEY],
2625
- validate=WV.WALLET_AND_HOTKEY,
2626
- )
2627
- return self._run_command(
2628
- root.set_boost(
2629
- wallet, self.initialize_chain(network), netuid, amount, prompt
2630
- )
2631
- )
2632
-
2633
- def root_slash(
2634
- self,
2635
- network: Optional[list[str]] = Options.network,
2636
- wallet_name: str = Options.wallet_name,
2637
- wallet_path: Optional[str] = Options.wallet_path,
2638
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2639
- netuid: int = Options.netuid,
2640
- amount: float = typer.Option(
2641
- None,
2642
- "--amount",
2643
- "--decrease",
2644
- "-a",
2645
- prompt="Enter the slash amount (subtracted from the existing weight)",
2646
- help="Amount (float) to slash (subtract from the existing weight), (e.g. 0.01)",
2647
- ),
2648
- prompt: bool = Options.prompt,
2649
- quiet: bool = Options.quiet,
2650
- verbose: bool = Options.verbose,
2651
- ):
2652
- """
2653
- Decrease (slash) the weights for a specific subnet in the root network. Any amount provided will be subtracted from the subnet's existing weight.
2654
-
2655
- EXAMPLE
2656
-
2657
- [green]$[/green] btcli root slash --netuid 1 --decrease 0.01
2658
-
2659
- """
2660
- self.verbosity_handler(quiet, verbose)
2661
- wallet = self.wallet_ask(
2662
- wallet_name,
2663
- wallet_path,
2664
- wallet_hotkey,
2665
- ask_for=[WO.NAME, WO.HOTKEY],
2666
- validate=WV.WALLET_AND_HOTKEY,
2667
- )
2668
- return self._run_command(
2669
- root.set_slash(
2670
- wallet, self.initialize_chain(network), netuid, amount, prompt
2671
- )
2672
- )
2673
-
2674
- def root_senate_vote(
2675
- self,
2676
- network: Optional[list[str]] = Options.network,
2677
- wallet_name: Optional[str] = Options.wallet_name,
2678
- wallet_path: Optional[str] = Options.wallet_path,
2679
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2680
- proposal: str = typer.Option(
2681
- None,
2682
- "--proposal",
2683
- "--proposal-hash",
2684
- prompt="Enter the proposal hash",
2685
- help="The hash of the proposal to vote on.",
2686
- ),
2687
- prompt: bool = Options.prompt,
2688
- quiet: bool = Options.quiet,
2689
- verbose: bool = Options.verbose,
2690
- vote: bool = typer.Option(
2691
- None,
2692
- "--vote-aye/--vote-nay",
2693
- prompt="Enter y to vote Aye, or enter n to vote Nay",
2694
- help="The vote casted on the proposal",
2695
- ),
2696
- ):
2697
- """
2698
- Cast a vote on an active proposal in Bittensor's governance protocol.
2699
-
2700
- This command is used by Senate members to vote on various proposals that shape the network's future. Use `btcli root proposals` to see the active proposals and their hashes.
2701
-
2702
- USAGE
2703
-
2704
- The user must specify the hash of the proposal they want to vote on. The command then allows the Senate member to cast a 'Yes' or 'No' vote, contributing to the decision-making process on the proposal. This command is crucial for Senate members to exercise their voting rights on key proposals. It plays a vital role in the governance and evolution of the Bittensor network.
2705
-
2706
- EXAMPLE
2707
-
2708
- [green]$[/green] btcli root senate_vote --proposal <proposal_hash>
2709
- """
2710
- self.verbosity_handler(quiet, verbose)
2711
- wallet = self.wallet_ask(
2712
- wallet_name,
2713
- wallet_path,
2714
- wallet_hotkey,
2715
- ask_for=[WO.NAME, WO.HOTKEY],
2716
- validate=WV.WALLET_AND_HOTKEY,
2717
- )
2718
- return self._run_command(
2719
- root.senate_vote(
2720
- wallet, self.initialize_chain(network), proposal, vote, prompt
2721
- )
2722
- )
2723
-
2724
- def root_senate(
2725
- self,
2726
- network: Optional[list[str]] = Options.network,
2727
- quiet: bool = Options.quiet,
2728
- verbose: bool = Options.verbose,
2729
- ):
2730
- """
2731
- Shows the Senate members of the Bittensor's governance protocol.
2732
-
2733
- This command lists the delegates involved in the decision-making process of the Bittensor network, showing their names and wallet addresses. This information is crucial for understanding who holds governance roles within the network.
2734
-
2735
- EXAMPLE
2736
-
2737
- [green]$[/green] btcli root senate
2738
- """
2739
- self.verbosity_handler(quiet, verbose)
2740
- return self._run_command(root.get_senate(self.initialize_chain(network)))
2741
-
2742
- def root_register(
2743
- self,
2744
- network: Optional[list[str]] = Options.network,
2745
- wallet_name: Optional[str] = Options.wallet_name,
2746
- wallet_path: Optional[str] = Options.wallet_path,
2747
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2748
- prompt: bool = Options.prompt,
2749
- quiet: bool = Options.quiet,
2750
- verbose: bool = Options.verbose,
2751
- ):
2752
- """
2753
- Register a neuron to the root subnet by recycling some TAO to cover for the registration cost.
2754
-
2755
- This command adds a new neuron as a validator on the root network. This will allow the neuron owner to set subnet weights.
2756
-
2757
- # Usage:
2758
-
2759
- Before registering, the command checks if the specified subnet exists and whether the TAO balance in the user's wallet is sufficient to cover the registration cost. The registration cost is determined by the current recycle amount for the specified subnet. If the balance is insufficient or the subnet does not exist, the command will exit with an appropriate error message.
2760
-
2761
- # Example usage:
2762
-
2763
- [green]$[/green] btcli subnets register --netuid 1
2764
- """
2765
- self.verbosity_handler(quiet, verbose)
2766
- wallet = self.wallet_ask(
2767
- wallet_name,
2768
- wallet_path,
2769
- wallet_hotkey,
2770
- ask_for=[WO.NAME, WO.HOTKEY],
2771
- validate=WV.WALLET_AND_HOTKEY,
2772
- )
2773
- return self._run_command(
2774
- root.register(wallet, self.initialize_chain(network), prompt)
2775
- )
2776
-
2777
- def root_proposals(
2778
- self,
2779
- network: Optional[list[str]] = Options.network,
2780
- quiet: bool = Options.quiet,
2781
- verbose: bool = Options.verbose,
2782
- ):
2783
- """
2784
- View active proposals for the senate in the Bittensor's governance protocol.
2785
-
2786
- This command displays the details of ongoing proposals, including proposal hashes, votes, thresholds, and proposal data.
2787
-
2788
- EXAMPLE
2789
-
2790
- [green]$[/green] btcli root proposals
2791
- """
2792
- self.verbosity_handler(quiet, verbose)
2793
- return self._run_command(root.proposals(self.initialize_chain(network)))
2794
-
2795
- def root_set_take(
2796
- self,
2797
- network: Optional[list[str]] = Options.network,
2798
- wallet_name: Optional[str] = Options.wallet_name,
2799
- wallet_path: Optional[str] = Options.wallet_path,
2800
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2801
- take: float = typer.Option(None, help="The new take value."),
2802
- quiet: bool = Options.quiet,
2803
- verbose: bool = Options.verbose,
2804
- ):
2805
- """
2806
- Allows users to change their delegate take percentage.
2807
-
2808
- This command can be used to update the delegate takes individually for every subnet. To run the command, the user must have a configured wallet with both hotkey and coldkey. The command performs the below checks:
2809
-
2810
- 1. The provided hotkey is already a delegate.
2811
- 2. The new take value is within 0-18% range.
2812
-
2813
- EXAMPLE
2814
-
2815
- [green]$[/green] btcli root set_take --wallet-name my_wallet --wallet-hotkey my_hotkey
2816
- """
2817
- max_value = 0.18
2818
- min_value = 0.00
2819
- self.verbosity_handler(quiet, verbose)
2820
-
2821
- if not take:
2822
- max_value_style = typer.style(f"Max: {max_value}", fg="magenta")
2823
- min_value_style = typer.style(f"Min: {min_value}", fg="magenta")
2824
- prompt_text = typer.style(
2825
- "Enter take value (0.18 for 18%)", fg="bright_cyan", bold=True
2826
- )
2827
- take = FloatPrompt.ask(f"{prompt_text} {min_value_style} {max_value_style}")
2828
-
2829
- if not (min_value <= take <= max_value):
2830
- print_error(
2831
- f"Take value must be between {min_value} and {max_value}. Provided value: {take}"
2832
- )
2833
- raise typer.Exit()
2834
-
2835
- wallet = self.wallet_ask(
2836
- wallet_name,
2837
- wallet_path,
2838
- wallet_hotkey,
2839
- ask_for=[WO.NAME, WO.HOTKEY],
2840
- validate=WV.WALLET_AND_HOTKEY,
2841
- )
2842
-
2843
- return self._run_command(
2844
- root.set_take(wallet, self.initialize_chain(network), take)
2845
- )
2846
-
2847
- def root_delegate_stake(
2848
- self,
2849
- delegate_ss58key: str = typer.Option(
2850
- None,
2851
- help="The ss58 address of the delegate hotkey to stake TAO to.",
2852
- prompt="Enter the hotkey ss58 address you want to delegate TAO to.",
2853
- ),
2854
- amount: Optional[float] = typer.Option(
2855
- None, help="The amount of TAO to stake. Do no specify if using `--all`"
2856
- ),
2857
- stake_all: Optional[bool] = typer.Option(
2858
- False,
2859
- "--all",
2860
- "-a",
2861
- help="If specified, the command stakes all available TAO. Do not specify if using"
2862
- " `--amount`",
2863
- ),
2864
- wallet_name: Optional[str] = Options.wallet_name,
2865
- wallet_path: Optional[str] = Options.wallet_path,
2866
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2867
- network: Optional[list[str]] = Options.network,
2868
- prompt: bool = Options.prompt,
2869
- quiet: bool = Options.quiet,
2870
- verbose: bool = Options.verbose,
2871
- ):
2872
- """
2873
- Stakes TAO to a specified delegate hotkey.
2874
-
2875
- This command allocates the user's TAO to the specified hotkey of a delegate, potentially earning staking rewards in return. If the
2876
- `--all` flag is used, it delegates the entire TAO balance available in the user's wallet.
2877
-
2878
- This command should be run by a TAO holder. Compare this command with "btcli stake add" that is typically run by a subnet validator to add stake to their own delegate hotkey.
2879
-
2880
- EXAMPLE
2881
-
2882
- [green]$[/green] btcli root delegate-stake --delegate_ss58key <SS58_ADDRESS> --amount <AMOUNT>
2883
-
2884
- [green]$[/green] btcli root delegate-stake --delegate_ss58key <SS58_ADDRESS> --all
2885
-
2886
- [blue bold]Note[/blue bold]: This command modifies the blockchain state and may incur transaction fees. The user should ensure the delegate's address and the amount to be staked are correct before executing the command.
2887
- """
2888
- self.verbosity_handler(quiet, verbose)
2889
- if amount and stake_all:
2890
- err_console.print(
2891
- "Both `--amount` and `--all` are specified. Choose one or the other."
2892
- )
2893
- if not stake_all and not amount:
2894
- while True:
2895
- amount = FloatPrompt.ask(
2896
- "Amount to [blue]stake (TAO τ)[/blue]", console=console
2897
- )
2898
- confirmation = FloatPrompt.ask(
2899
- "Confirm the amount to stake [blue](TAO τ)[/blue]",
2900
- console=console,
2901
- )
2902
- if amount == confirmation:
2903
- break
2904
- else:
2905
- err_console.print(
2906
- "[red]The amounts do not match. Please try again.[/red]"
2907
- )
2908
-
2909
- wallet = self.wallet_ask(
2910
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2911
- )
2912
- return self._run_command(
2913
- root.delegate_stake(
2914
- wallet,
2915
- self.initialize_chain(network),
2916
- amount,
2917
- delegate_ss58key,
2918
- prompt,
2919
- )
2920
- )
2921
-
2922
- def root_undelegate_stake(
2923
- self,
2924
- delegate_ss58key: str = typer.Option(
2925
- None,
2926
- help="The ss58 address of the delegate to undelegate from.",
2927
- prompt="Enter the hotkey ss58 address you want to undelegate from",
2928
- ),
2929
- amount: Optional[float] = typer.Option(
2930
- None, help="The amount of TAO to unstake. Do no specify if using `--all`"
2931
- ),
2932
- unstake_all: Optional[bool] = typer.Option(
2933
- False,
2934
- "--all",
2935
- "-a",
2936
- help="If specified, the command undelegates all staked TAO from the delegate. Do not specify if using"
2937
- " `--amount`",
2938
- ),
2939
- wallet_name: Optional[str] = Options.wallet_name,
2940
- wallet_path: Optional[str] = Options.wallet_path,
2941
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2942
- network: Optional[list[str]] = Options.network,
2943
- prompt: bool = Options.prompt,
2944
- quiet: bool = Options.quiet,
2945
- verbose: bool = Options.verbose,
2946
- ):
2947
- """
2948
- Allows users to withdraw their staked TAO from a delegate.
2949
-
2950
- The user must provide the delegate hotkey's ss58 address and the amount of TAO to undelegate. The function will then send a transaction to the blockchain to process the undelegation. This command can result in a change to the blockchain state and may incur transaction fees.
2951
-
2952
- EXAMPLE
2953
-
2954
- [green]$[/green] btcli undelegate --delegate_ss58key <SS58_ADDRESS> --amount <AMOUNT>
2955
-
2956
- [green]$[/green] btcli undelegate --delegate_ss58key <SS58_ADDRESS> --all
2957
- """
2958
- self.verbosity_handler(quiet, verbose)
2959
- if amount and unstake_all:
2960
- err_console.print(
2961
- "Both `--amount` and `--all` are specified. Choose one or the other."
2962
- )
2963
- if not unstake_all and not amount:
2964
- while True:
2965
- amount = FloatPrompt.ask(
2966
- "Amount to [blue]unstake (TAO τ)[/blue]", console=console
2967
- )
2968
- confirmation = FloatPrompt.ask(
2969
- "Confirm the amount to unstake [blue](TAO τ)[/blue]",
2970
- console=console,
2971
- )
2972
- if amount == confirmation:
2973
- break
2974
- else:
2975
- err_console.print(
2976
- "[red]The amounts do not match. Please try again.[/red]"
2977
- )
2978
-
2979
- wallet = self.wallet_ask(
2980
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2981
- )
2982
- self._run_command(
2983
- root.delegate_unstake(
2984
- wallet,
2985
- self.initialize_chain(network),
2986
- amount,
2987
- delegate_ss58key,
2988
- prompt,
2989
- )
2990
- )
2991
-
2992
- def root_my_delegates(
2993
- self,
2994
- network: Optional[list[str]] = Options.network,
2995
- wallet_name: Optional[str] = Options.wallet_name,
2996
- wallet_path: Optional[str] = Options.wallet_path,
2997
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2998
- all_wallets: bool = typer.Option(
2999
- False,
3000
- "--all-wallets",
3001
- "--all",
3002
- "-a",
3003
- help="If specified, the command aggregates information across all the wallets.",
3004
- ),
3005
- quiet: bool = Options.quiet,
3006
- verbose: bool = Options.verbose,
3007
- ):
3008
- """
3009
- Shows a table with the details on the user's delegates.
3010
-
3011
- The table output includes the following columns:
3012
-
3013
- - Wallet: The name of the user's wallet (coldkey).
3014
-
3015
- - OWNER: The name of the delegate who owns the hotkey.
3016
-
3017
- - SS58: The truncated SS58 address of the delegate's hotkey.
3018
-
3019
- - Delegation: The amount of TAO staked by the user to the delegate.
3020
-
3021
- - τ/24h: The earnings from the delegate to the user over the past 24 hours.
3022
-
3023
- - NOMS: The number of nominators for the delegate.
3024
-
3025
- - OWNER STAKE(τ): The stake amount owned by the delegate.
3026
-
3027
- - TOTAL STAKE(τ): The total stake amount held by the delegate.
3028
-
3029
- - SUBNETS: The list of subnets the delegate is a part of.
3030
-
3031
- - VPERMIT: Validator permits held by the delegate for various subnets.
3032
-
3033
- - 24h/kτ: Earnings per 1000 TAO staked over the last 24 hours.
3034
-
3035
- - Desc: A description of the delegate.
3036
-
3037
- The command also sums and prints the total amount of TAO delegated across all wallets.
3038
-
3039
- EXAMPLE
3040
-
3041
- [green]$[/green] btcli root my-delegates
3042
- [green]$[/green] btcli root my-delegates --all
3043
- [green]$[/green] btcli root my-delegates --wallet-name my_wallet
3044
-
3045
- [blue bold]Note[/blue bold]: This command is not intended to be used directly in user code.
3046
- """
3047
- self.verbosity_handler(quiet, verbose)
3048
- wallet = self.wallet_ask(
3049
- wallet_name,
3050
- wallet_path,
3051
- wallet_hotkey,
3052
- ask_for=([WO.NAME] if not all_wallets else []),
3053
- validate=WV.WALLET if not all_wallets else WV.NONE,
3054
- )
3055
- self._run_command(
3056
- root.my_delegates(wallet, self.initialize_chain(network), all_wallets)
3057
- )
3058
-
3059
- def root_list_delegates(
3060
- self,
3061
- network: Optional[list[str]] = Options.network,
3062
- quiet: bool = Options.quiet,
3063
- verbose: bool = Options.verbose,
3064
- ):
3065
- """
3066
- Displays a table of Bittensor network-wide delegates, providing a comprehensive overview of delegate statistics and information.
3067
-
3068
- This table helps users make informed decisions on which delegates to allocate their TAO stake.
3069
-
3070
- The table columns include:
3071
-
3072
- - INDEX: The delegate's index in the sorted list.
3073
-
3074
- - DELEGATE: The name of the delegate.
3075
-
3076
- - SS58: The delegate's unique ss58 address (truncated for display).
3077
-
3078
- - NOMINATORS: The count of nominators backing the delegate.
3079
-
3080
- - OWN STAKE(τ): The amount of delegate's own stake (not the TAO delegated from any nominators).
3081
-
3082
- - TOTAL STAKE(τ): The delegate's total stake, i.e., the sum of delegate's own stake and nominators' stakes.
3083
-
3084
- - CHANGE/(4h): The percentage change in the delegate's stake over the last four hours.
3085
-
3086
- - SUBNETS: The subnets in which the delegate is registered.
3087
-
3088
- - VPERMIT: Indicates the subnets in which the delegate has validator permits.
3089
-
3090
- - NOMINATOR/(24h)/kτ: The earnings per 1000 τ staked by nominators in the last 24 hours.
3091
-
3092
- - DELEGATE/(24h): The total earnings of the delegate in the last 24 hours.
3093
-
3094
- - DESCRIPTION: A brief description of the delegate's purpose and operations.
3095
-
3096
- [blue bold]NOTES:[/blue bold]
3097
-
3098
- - Sorting is done based on the `TOTAL STAKE` column in descending order.
3099
- - Changes in stake are shown as: increases in green and decreases in red.
3100
- - Entries with no previous data are marked with `NA`.
3101
- - Each delegate's name is a hyperlink to more information, if available.
3102
-
3103
- EXAMPLE
3104
-
3105
- [green]$[/green] btcli root list_delegates
3106
-
3107
- [green]$[/green] btcli root list_delegates --subtensor.network finney # can also be `test` or `local`
3108
-
3109
- [blue bold]NOTE[/blue bold]: This command is intended for use within a
3110
- console application. It prints directly to the console and does not return any value.
3111
- """
3112
- self.verbosity_handler(quiet, verbose)
3113
-
3114
- if network:
3115
- if "finney" in network:
3116
- network = ["wss://archive.chain.opentensor.ai:443"]
3117
- elif (conf_net := self.config.get("network")) == "finney":
3118
- network = ["wss://archive.chain.opentensor.ai:443"]
3119
- elif conf_net:
3120
- network = [conf_net]
3121
- else:
3122
- network = ["wss://archive.chain.opentensor.ai:443"]
3123
-
3124
- sub = self.initialize_chain(network)
3125
- return self._run_command(root.list_delegates(sub))
3126
-
3127
- # TODO: Confirm if we need a command for this - currently registering to root auto makes u delegate
3128
- def root_nominate(
3129
- self,
3130
- wallet_name: Optional[str] = Options.wallet_name,
3131
- wallet_path: Optional[str] = Options.wallet_path,
3132
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3133
- network: Optional[list[str]] = Options.network,
3134
- prompt: bool = Options.prompt,
3135
- quiet: bool = Options.quiet,
3136
- verbose: bool = Options.verbose,
3137
- ):
3138
- """
3139
- Enables a wallet's hotkey to become a delegate.
3140
-
3141
- This command handles the nomination process, including wallet unlocking and verification of the hotkey's current delegate status.
3142
-
3143
- The command performs several checks:
3144
-
3145
- - Verifies that the hotkey is not already a delegate to prevent redundant nominations.
3146
-
3147
- - Tries to nominate the wallet and reports success or failure.
3148
-
3149
- Upon success, the wallet's hotkey is registered as a delegate on the network.
3150
-
3151
- To run the command, the user must have a configured wallet with both hotkey and coldkey. If the wallet is not already nominated, this command will initiate the process.
3152
-
3153
- EXAMPLE
3154
-
3155
- [green]$[/green] btcli root nominate
3156
-
3157
- [green]$[/green] btcli root nominate --wallet-name my_wallet --wallet-hotkey my_hotkey
3158
-
3159
- [blue bold]Note[/blue bold]: This command prints the output directly to the console. It should not be called programmatically in user code due to its interactive nature and side effects on the network state.
3160
- """
3161
- self.verbosity_handler(quiet, verbose)
3162
- wallet = self.wallet_ask(
3163
- wallet_name,
3164
- wallet_path,
3165
- wallet_hotkey,
3166
- ask_for=[WO.NAME, WO.HOTKEY],
3167
- validate=WV.WALLET_AND_HOTKEY,
3168
- )
3169
- return self._run_command(
3170
- root.nominate(wallet, self.initialize_chain(network), prompt)
3171
- )
3172
-
3173
- def stake_show(
3174
- self,
3175
- all_wallets: bool = typer.Option(
3176
- False,
3177
- "--all",
3178
- "--all-wallets",
3179
- "-a",
3180
- help="When set, the command checks all the coldkey wallets of the user instead of just the specified wallet.",
3181
- ),
3182
- network: Optional[list[str]] = Options.network,
3183
- wallet_name: Optional[str] = Options.wallet_name,
3184
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3185
- wallet_path: Optional[str] = Options.wallet_path,
3186
- reuse_last: bool = Options.reuse_last,
3187
- html_output: bool = Options.html_output,
3188
- quiet: bool = Options.quiet,
3189
- verbose: bool = Options.verbose,
3190
- ):
3191
- """
3192
- Lists all the stake accounts associated with a user's wallet.
3193
-
3194
- This command provides a comprehensive view of the stakes associated with the user's coldkeys. It shows both the user's own hotkeys and also the hotkeys of the delegates to which this user has staked.
3195
-
3196
- The command lists all the stake accounts for a specified wallet or all wallets in the user's configuration directory. It displays the coldkey, balance, hotkey details (own hotkey and delegate hotkey), stake amount, and the rate of return.
3197
-
3198
- The command shows a table with the below columns:
3199
-
3200
- - Coldkey: The coldkey associated with the wallet.
3201
-
3202
- - Balance: The balance of the coldkey.
3203
-
3204
- - Hotkey: The names of the coldkey's own hotkeys and the delegate hotkeys to which this coldkey has staked.
3205
-
3206
- - Stake: The amount of TAO staked to all the hotkeys.
3207
-
3208
- - Rate: The rate of return on the stake, shown in TAO per day.
3209
-
3210
- EXAMPLE
3211
-
3212
- [green]$[/green] btcli stake show --all
3213
- """
3214
- self.verbosity_handler(quiet, verbose)
3215
- if (reuse_last or html_output) and self.config.get("use_cache") is False:
3216
- err_console.print(
3217
- "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. "
3218
- "Please change the config to 'False' using `btcli config set`"
3219
- )
3220
- raise typer.Exit()
3221
- if not reuse_last:
3222
- subtensor = self.initialize_chain(network)
3223
- else:
3224
- subtensor = None
3225
-
3226
- if all_wallets:
3227
- wallet = self.wallet_ask(
3228
- wallet_name,
3229
- wallet_path,
3230
- wallet_hotkey,
3231
- ask_for=[],
3232
- validate=WV.NONE,
3233
- )
3234
- else:
3235
- wallet = self.wallet_ask(
3236
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3237
- )
3238
-
3239
- return self._run_command(
3240
- stake.show(
3241
- wallet,
3242
- subtensor,
3243
- all_wallets,
3244
- reuse_last,
3245
- html_output,
3246
- not self.config.get("use_cache", True),
3247
- )
3248
- )
3249
-
3250
- def stake_add(
3251
- self,
3252
- stake_all: bool = typer.Option(
3253
- False,
3254
- "--all-tokens",
3255
- "--all",
3256
- "-a",
3257
- help="When set, the command stakes all the available TAO from the coldkey.",
3258
- ),
3259
- amount: float = typer.Option(
3260
- 0.0, "--amount", help="The amount of TAO to stake"
3261
- ),
3262
- max_stake: float = typer.Option(
3263
- 0.0,
3264
- "--max-stake",
3265
- "-m",
3266
- help="Stake is sent to a hotkey only until the hotkey's total stake is less than or equal to this maximum staked TAO. If a hotkey already has stake greater than this amount, then stake is not added to this hotkey.",
3267
- ),
3268
- hotkey_ss58_address: str = typer.Option(
3269
- "",
3270
- help="The ss58 address of the hotkey to stake to.",
3271
- ),
3272
- include_hotkeys: str = typer.Option(
3273
- "",
3274
- "--include-hotkeys",
3275
- "-in",
3276
- help="Specifies hotkeys by name or ss58 address to stake to. For example, `-in hk1,hk2`",
3277
- ),
3278
- exclude_hotkeys: str = typer.Option(
3279
- "",
3280
- "--exclude-hotkeys",
3281
- "-ex",
3282
- help="Specifies hotkeys by name or ss58 address to not to stake to (use this option only with `--all-hotkeys`)"
3283
- " i.e. `--all-hotkeys -ex hk3,hk4`",
3284
- ),
3285
- all_hotkeys: bool = typer.Option(
3286
- False,
3287
- help="When set, this command stakes to all hotkeys associated with the wallet. Do not use if specifying "
3288
- "hotkeys in `--include-hotkeys`.",
3289
- ),
3290
- wallet_name: str = Options.wallet_name,
3291
- wallet_path: str = Options.wallet_path,
3292
- wallet_hotkey: str = Options.wallet_hotkey,
3293
- network: Optional[list[str]] = Options.network,
3294
- prompt: bool = Options.prompt,
3295
- quiet: bool = Options.quiet,
3296
- verbose: bool = Options.verbose,
3297
- ):
3298
- """
3299
- Stake TAO to one or more hotkeys associated with the user's coldkey.
2647
+ Stake TAO to one or more hotkeys associated with the user's coldkey.
3300
2648
 
3301
2649
  This command is used by a subnet validator to stake to their own hotkey. Compare this command with "btcli root delegate" that is typically run by a TAO holder to delegate their TAO to a delegate's hotkey.
3302
2650
 
@@ -3307,16 +2655,14 @@ class CLIManager:
3307
2655
  [green]$[/green] btcli stake add --amount 100 --wallet-name <my_wallet> --wallet-hotkey <my_hotkey>
3308
2656
  """
3309
2657
  self.verbosity_handler(quiet, verbose)
2658
+ netuid = get_optional_netuid(netuid, all_netuids)
3310
2659
 
3311
2660
  if stake_all and amount:
3312
- print_error(
2661
+ err_console.print(
3313
2662
  "Cannot specify an amount and 'stake-all'. Choose one or the other."
3314
2663
  )
3315
2664
  raise typer.Exit()
3316
2665
 
3317
- if not stake_all and not amount and not max_stake:
3318
- amount = FloatPrompt.ask("Amount to [blue]stake (TAO τ)[/blue]")
3319
-
3320
2666
  if stake_all and not amount:
3321
2667
  if not Confirm.ask("Stake all the available TAO tokens?", default=False):
3322
2668
  raise typer.Exit()
@@ -3334,40 +2680,66 @@ class CLIManager:
3334
2680
  )
3335
2681
  raise typer.Exit()
3336
2682
 
3337
- if (
3338
- not wallet_hotkey
3339
- and not all_hotkeys
3340
- and not include_hotkeys
3341
- and not hotkey_ss58_address
3342
- ):
3343
- hotkey_or_ss58 = Prompt.ask(
3344
- "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to",
3345
- )
3346
- if is_valid_ss58_address(hotkey_or_ss58):
3347
- hotkey_ss58_address = hotkey_or_ss58
2683
+ if not wallet_hotkey and not all_hotkeys and not include_hotkeys:
2684
+ if not wallet_name:
2685
+ wallet_name = Prompt.ask(
2686
+ "Enter the [blue]wallet name[/blue]",
2687
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2688
+ )
2689
+ if netuid is not None:
2690
+ hotkey_or_ss58 = Prompt.ask(
2691
+ "Enter the [blue]wallet hotkey[/blue] name or [blue]ss58 address[/blue] to stake to [dim](or Press Enter to view delegates)[/dim]",
2692
+ )
2693
+ else:
2694
+ hotkey_or_ss58 = Prompt.ask(
2695
+ "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to",
2696
+ default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey,
2697
+ )
2698
+
2699
+ if hotkey_or_ss58 == "":
3348
2700
  wallet = self.wallet_ask(
3349
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2701
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2702
+ )
2703
+ selected_hotkey = self._run_command(
2704
+ subnets.show(
2705
+ subtensor=self.initialize_chain(network),
2706
+ netuid=netuid,
2707
+ max_rows=12,
2708
+ prompt=False,
2709
+ delegate_selection=True,
2710
+ ),
2711
+ exit_early=False,
2712
+ )
2713
+ if selected_hotkey is None:
2714
+ print_error("No delegate selected. Exiting.")
2715
+ raise typer.Exit()
2716
+ include_hotkeys = selected_hotkey
2717
+ elif is_valid_ss58_address(hotkey_or_ss58):
2718
+ wallet = self.wallet_ask(
2719
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3350
2720
  )
2721
+ include_hotkeys = hotkey_or_ss58
3351
2722
  else:
3352
2723
  wallet_hotkey = hotkey_or_ss58
3353
2724
  wallet = self.wallet_ask(
3354
2725
  wallet_name,
3355
2726
  wallet_path,
3356
2727
  wallet_hotkey,
3357
- ask_for=[WO.NAME, WO.HOTKEY],
2728
+ ask_for=[WO.NAME, WO.HOTKEY, WO.PATH],
3358
2729
  validate=WV.WALLET_AND_HOTKEY,
3359
2730
  )
2731
+ include_hotkeys = wallet.hotkey.ss58_address
3360
2732
 
3361
- elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address:
2733
+ elif all_hotkeys or include_hotkeys or exclude_hotkeys:
3362
2734
  wallet = self.wallet_ask(
3363
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2735
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3364
2736
  )
3365
2737
  else:
3366
2738
  wallet = self.wallet_ask(
3367
2739
  wallet_name,
3368
2740
  wallet_path,
3369
2741
  wallet_hotkey,
3370
- ask_for=[WO.NAME, WO.HOTKEY],
2742
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3371
2743
  validate=WV.WALLET_AND_HOTKEY,
3372
2744
  )
3373
2745
 
@@ -3375,8 +2747,8 @@ class CLIManager:
3375
2747
  included_hotkeys = parse_to_list(
3376
2748
  include_hotkeys,
3377
2749
  str,
3378
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3379
- "`--include-hotkeys 5Grw....,5Grw....`.",
2750
+ "Hotkeys must be a comma-separated list of ss58s, e.g., `--include-hotkeys 5Grw....,5Grw....`.",
2751
+ is_ss58=True,
3380
2752
  )
3381
2753
  else:
3382
2754
  included_hotkeys = []
@@ -3385,24 +2757,54 @@ class CLIManager:
3385
2757
  excluded_hotkeys = parse_to_list(
3386
2758
  exclude_hotkeys,
3387
2759
  str,
3388
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3389
- "`--exclude-hotkeys 5Grw....,5Grw....`.",
2760
+ "Hotkeys must be a comma-separated list of ss58s, e.g., `--exclude-hotkeys 5Grw....,5Grw....`.",
2761
+ is_ss58=True,
3390
2762
  )
3391
2763
  else:
3392
2764
  excluded_hotkeys = []
3393
2765
 
2766
+ # TODO: Ask amount for each subnet explicitly if more than one
2767
+ if not stake_all and not amount and not max_stake:
2768
+ free_balance, staked_balance = self._run_command(
2769
+ wallets.wallet_balance(
2770
+ wallet, self.initialize_chain(network), False, None
2771
+ ),
2772
+ exit_early=False,
2773
+ )
2774
+ if free_balance == Balance.from_tao(0):
2775
+ print_error("You dont have any balance to stake.")
2776
+ raise typer.Exit()
2777
+ if netuid is not None:
2778
+ amount = FloatPrompt.ask(
2779
+ f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake (TAO τ)"
2780
+ )
2781
+ else:
2782
+ amount = FloatPrompt.ask(
2783
+ f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake to each netuid (TAO τ)"
2784
+ )
2785
+
2786
+ if amount <= 0:
2787
+ print_error(f"You entered an incorrect stake amount: {amount}")
2788
+ raise typer.Exit()
2789
+ if Balance.from_tao(amount) > free_balance:
2790
+ print_error(
2791
+ f"You dont have enough balance to stake. Current free Balance: {free_balance}."
2792
+ )
2793
+ raise typer.Exit()
2794
+
3394
2795
  return self._run_command(
3395
2796
  stake.stake_add(
3396
2797
  wallet,
3397
2798
  self.initialize_chain(network),
3398
- amount,
2799
+ netuid,
3399
2800
  stake_all,
2801
+ amount,
2802
+ False,
2803
+ prompt,
3400
2804
  max_stake,
2805
+ all_hotkeys,
3401
2806
  included_hotkeys,
3402
2807
  excluded_hotkeys,
3403
- all_hotkeys,
3404
- prompt,
3405
- hotkey_ss58_address,
3406
2808
  )
3407
2809
  )
3408
2810
 
@@ -3412,11 +2814,21 @@ class CLIManager:
3412
2814
  wallet_name: str = Options.wallet_name,
3413
2815
  wallet_path: str = Options.wallet_path,
3414
2816
  wallet_hotkey: str = Options.wallet_hotkey,
2817
+ netuid: Optional[int] = Options.netuid_not_req,
2818
+ all_netuids: bool = Options.all_netuids,
3415
2819
  unstake_all: bool = typer.Option(
3416
2820
  False,
3417
2821
  "--unstake-all",
3418
2822
  "--all",
3419
- help="When set, this commmand unstakes all staked TAO from the specified hotkeys.",
2823
+ hidden=True,
2824
+ help="When set, this command unstakes all staked TAO + Alpha from the all hotkeys.",
2825
+ ),
2826
+ unstake_all_alpha: bool = typer.Option(
2827
+ False,
2828
+ "--unstake-all-alpha",
2829
+ "--all-alpha",
2830
+ hidden=True,
2831
+ help="When set, this command unstakes all staked Alpha from the all hotkeys.",
3420
2832
  ),
3421
2833
  amount: float = typer.Option(
3422
2834
  0.0, "--amount", "-a", help="The amount of TAO to unstake."
@@ -3450,6 +2862,12 @@ class CLIManager:
3450
2862
  "hotkeys in `--include-hotkeys`.",
3451
2863
  ),
3452
2864
  prompt: bool = Options.prompt,
2865
+ interactive: bool = typer.Option(
2866
+ False,
2867
+ "--interactive",
2868
+ "-i",
2869
+ help="Enter interactive mode for unstaking.",
2870
+ ),
3453
2871
  quiet: bool = Options.quiet,
3454
2872
  verbose: bool = Options.verbose,
3455
2873
  ):
@@ -3465,31 +2883,45 @@ class CLIManager:
3465
2883
  [blue bold]Note[/blue bold]: This command is for users who wish to reallocate their stake or withdraw them from the network. It allows for flexible management of TAO stake across different neurons (hotkeys) on the network.
3466
2884
  """
3467
2885
  self.verbosity_handler(quiet, verbose)
2886
+ # TODO: Coldkey related unstakes need to be updated. Patching for now.
2887
+ unstake_all_alpha = False
2888
+ unstake_all = False
3468
2889
 
3469
- if all_hotkeys and include_hotkeys:
2890
+ if interactive and any(
2891
+ [hotkey_ss58_address, include_hotkeys, exclude_hotkeys, all_hotkeys]
2892
+ ):
3470
2893
  err_console.print(
3471
- "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag"
3472
- "should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`."
2894
+ "Interactive mode cannot be used with hotkey selection options like --include-hotkeys, --exclude-hotkeys, --all-hotkeys, or --hotkey."
3473
2895
  )
3474
2896
  raise typer.Exit()
3475
2897
 
3476
- if include_hotkeys and exclude_hotkeys:
3477
- err_console.print(
3478
- "You have specified both including and excluding hotkeys options. Select one or the other."
3479
- )
2898
+ if unstake_all and unstake_all_alpha:
2899
+ err_console.print("Cannot specify both unstake-all and unstake-all-alpha.")
3480
2900
  raise typer.Exit()
3481
2901
 
3482
- if unstake_all and amount:
3483
- err_console.print(
3484
- "Cannot specify both a specific amount and 'unstake-all'. Choose one or the other."
3485
- )
3486
- raise typer.Exit()
2902
+ if not interactive and not unstake_all and not unstake_all_alpha:
2903
+ netuid = get_optional_netuid(netuid, all_netuids)
2904
+ if all_hotkeys and include_hotkeys:
2905
+ err_console.print(
2906
+ "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag"
2907
+ " should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`."
2908
+ )
2909
+ raise typer.Exit()
2910
+
2911
+ if include_hotkeys and exclude_hotkeys:
2912
+ err_console.print(
2913
+ "You have specified both including and excluding hotkeys options. Select one or the other."
2914
+ )
2915
+ raise typer.Exit()
3487
2916
 
3488
- if not unstake_all and not amount and not keep_stake:
3489
- amount = FloatPrompt.ask("Amount to [blue]unstake (TAO τ)[/blue]")
2917
+ if unstake_all and amount:
2918
+ err_console.print(
2919
+ "Cannot specify both a specific amount and 'unstake-all'. Choose one or the other."
2920
+ )
2921
+ raise typer.Exit()
3490
2922
 
3491
- if unstake_all and not amount and prompt:
3492
- if not Confirm.ask("Unstake all staked TAO tokens?", default=False):
2923
+ if amount and amount <= 0:
2924
+ print_error(f"You entered an incorrect unstake amount: {amount}")
3493
2925
  raise typer.Exit()
3494
2926
 
3495
2927
  if (
@@ -3497,14 +2929,27 @@ class CLIManager:
3497
2929
  and not hotkey_ss58_address
3498
2930
  and not all_hotkeys
3499
2931
  and not include_hotkeys
2932
+ and not interactive
2933
+ and not unstake_all
2934
+ and not unstake_all_alpha
3500
2935
  ):
2936
+ if not wallet_name:
2937
+ wallet_name = Prompt.ask(
2938
+ "Enter the [blue]wallet name[/blue]",
2939
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2940
+ )
3501
2941
  hotkey_or_ss58 = Prompt.ask(
3502
- "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from"
2942
+ "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from [dim](or Press Enter to view existing staked hotkeys)[/dim]",
3503
2943
  )
3504
- if is_valid_ss58_address(hotkey_or_ss58):
2944
+ if hotkey_or_ss58 == "":
2945
+ wallet = self.wallet_ask(
2946
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2947
+ )
2948
+ interactive = True
2949
+ elif is_valid_ss58_address(hotkey_or_ss58):
3505
2950
  hotkey_ss58_address = hotkey_or_ss58
3506
2951
  wallet = self.wallet_ask(
3507
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2952
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3508
2953
  )
3509
2954
  else:
3510
2955
  wallet_hotkey = hotkey_or_ss58
@@ -3512,56 +2957,436 @@ class CLIManager:
3512
2957
  wallet_name,
3513
2958
  wallet_path,
3514
2959
  wallet_hotkey,
3515
- ask_for=[WO.NAME, WO.HOTKEY],
2960
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3516
2961
  validate=WV.WALLET_AND_HOTKEY,
3517
2962
  )
3518
2963
 
3519
- elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address:
2964
+ elif (
2965
+ all_hotkeys
2966
+ or include_hotkeys
2967
+ or exclude_hotkeys
2968
+ or hotkey_ss58_address
2969
+ or interactive
2970
+ or unstake_all
2971
+ or unstake_all_alpha
2972
+ ):
2973
+ wallet = self.wallet_ask(
2974
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2975
+ )
2976
+ else:
3520
2977
  wallet = self.wallet_ask(
3521
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2978
+ wallet_name,
2979
+ wallet_path,
2980
+ wallet_hotkey,
2981
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
2982
+ validate=WV.WALLET_AND_HOTKEY,
2983
+ )
2984
+
2985
+ if include_hotkeys:
2986
+ included_hotkeys = parse_to_list(
2987
+ include_hotkeys,
2988
+ str,
2989
+ "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--include-hotkeys hk1,hk2`.",
2990
+ is_ss58=False,
2991
+ )
2992
+ else:
2993
+ included_hotkeys = []
2994
+
2995
+ if exclude_hotkeys:
2996
+ excluded_hotkeys = parse_to_list(
2997
+ exclude_hotkeys,
2998
+ str,
2999
+ "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--exclude-hotkeys hk3,hk4`.",
3000
+ is_ss58=False,
3001
+ )
3002
+ else:
3003
+ excluded_hotkeys = []
3004
+
3005
+ return self._run_command(
3006
+ stake.unstake(
3007
+ wallet,
3008
+ self.initialize_chain(network),
3009
+ hotkey_ss58_address,
3010
+ all_hotkeys,
3011
+ included_hotkeys,
3012
+ excluded_hotkeys,
3013
+ amount,
3014
+ keep_stake,
3015
+ unstake_all,
3016
+ prompt,
3017
+ interactive,
3018
+ netuid=netuid,
3019
+ unstake_all_alpha=unstake_all_alpha,
3020
+ )
3021
+ )
3022
+
3023
+ def stake_move(
3024
+ self,
3025
+ network: Optional[list[str]] = Options.network,
3026
+ wallet_name=Options.wallet_name,
3027
+ wallet_path=Options.wallet_path,
3028
+ wallet_hotkey=Options.wallet_hotkey,
3029
+ origin_netuid: Optional[int] = typer.Option(
3030
+ None, "--origin-netuid", help="Origin netuid"
3031
+ ),
3032
+ destination_netuid: Optional[int] = typer.Option(
3033
+ None, "--dest-netuid", help="Destination netuid"
3034
+ ),
3035
+ destination_hotkey: Optional[str] = typer.Option(
3036
+ None, "--dest-ss58", "--dest", help="Destination hotkey", prompt=False
3037
+ ),
3038
+ amount: float = typer.Option(
3039
+ None,
3040
+ "--amount",
3041
+ help="The amount of TAO to stake",
3042
+ prompt=False,
3043
+ ),
3044
+ stake_all: bool = typer.Option(
3045
+ False, "--stake-all", "--all", help="Stake all", prompt=False
3046
+ ),
3047
+ prompt: bool = Options.prompt,
3048
+ ):
3049
+ """
3050
+ Move staked TAO between hotkeys while keeping the same coldkey ownership.
3051
+
3052
+ This command allows you to:
3053
+ - Move stake from one hotkey to another hotkey
3054
+ - Move stake between different subnets
3055
+ - Keep the same coldkey ownership
3056
+
3057
+ You can specify:
3058
+ - The origin subnet (--origin-netuid)
3059
+ - The destination subnet (--dest-netuid)
3060
+ - The destination hotkey (--dest-hotkey)
3061
+ - The amount to move (--amount)
3062
+
3063
+ If no arguments are provided, an interactive selection menu will be shown.
3064
+
3065
+ EXAMPLE
3066
+
3067
+ [green]$[/green] btcli stake move
3068
+ """
3069
+ console.print(
3070
+ "[dim]This command moves stake from one hotkey to another hotkey while keeping the same coldkey.[/dim]"
3071
+ )
3072
+ if not destination_hotkey:
3073
+ dest_wallet_or_ss58 = Prompt.ask(
3074
+ "Enter the [blue]destination wallet[/blue] where destination hotkey is located or [blue]ss58 address[/blue]"
3075
+ )
3076
+ if is_valid_ss58_address(dest_wallet_or_ss58):
3077
+ destination_hotkey = dest_wallet_or_ss58
3078
+ else:
3079
+ dest_wallet = self.wallet_ask(
3080
+ dest_wallet_or_ss58,
3081
+ wallet_path,
3082
+ None,
3083
+ ask_for=[WO.NAME, WO.PATH],
3084
+ validate=WV.WALLET,
3085
+ )
3086
+ destination_hotkey = Prompt.ask(
3087
+ "Enter the [blue]destination hotkey[/blue] name",
3088
+ default=dest_wallet.hotkey_str,
3089
+ )
3090
+ destination_wallet = self.wallet_ask(
3091
+ dest_wallet_or_ss58,
3092
+ wallet_path,
3093
+ destination_hotkey,
3094
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3095
+ validate=WV.WALLET_AND_HOTKEY,
3096
+ )
3097
+ destination_hotkey = destination_wallet.hotkey.ss58_address
3098
+ else:
3099
+ if is_valid_ss58_address(destination_hotkey):
3100
+ destination_hotkey = destination_hotkey
3101
+ else:
3102
+ print_error(
3103
+ "Invalid destination hotkey ss58 address. Please enter a valid ss58 address or wallet name."
3104
+ )
3105
+ raise typer.Exit()
3106
+
3107
+ if not wallet_name:
3108
+ wallet_name = Prompt.ask(
3109
+ "Enter the [blue]origin wallet name[/blue]",
3110
+ default=self.config.get("wallet_name") or defaults.wallet.name,
3111
+ )
3112
+ wallet = self.wallet_ask(
3113
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3114
+ )
3115
+
3116
+ interactive_selection = False
3117
+ if not wallet_hotkey:
3118
+ origin_hotkey = Prompt.ask(
3119
+ "Enter the [blue]origin hotkey[/blue] name or "
3120
+ "[blue]ss58 address[/blue] where the stake will be moved from "
3121
+ "[dim](or Press Enter to view existing stakes)[/dim]"
3122
+ )
3123
+ if origin_hotkey == "":
3124
+ interactive_selection = True
3125
+
3126
+ elif is_valid_ss58_address(origin_hotkey):
3127
+ origin_hotkey = origin_hotkey
3128
+ else:
3129
+ wallet = self.wallet_ask(
3130
+ wallet_name,
3131
+ wallet_path,
3132
+ origin_hotkey,
3133
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3134
+ validate=WV.WALLET_AND_HOTKEY,
3135
+ )
3136
+ origin_hotkey = wallet.hotkey.ss58_address
3137
+ else:
3138
+ if is_valid_ss58_address(wallet_hotkey):
3139
+ origin_hotkey = wallet_hotkey
3140
+ else:
3141
+ wallet = self.wallet_ask(
3142
+ wallet_name,
3143
+ wallet_path,
3144
+ wallet_hotkey,
3145
+ ask_for=[],
3146
+ validate=WV.WALLET_AND_HOTKEY,
3147
+ )
3148
+ origin_hotkey = wallet.hotkey.ss58_address
3149
+
3150
+ if not interactive_selection:
3151
+ if origin_netuid is None:
3152
+ origin_netuid = IntPrompt.ask(
3153
+ "Enter the [blue]origin subnet[/blue] (netuid) to move stake from"
3154
+ )
3155
+
3156
+ if destination_netuid is None:
3157
+ destination_netuid = IntPrompt.ask(
3158
+ "Enter the [blue]destination subnet[/blue] (netuid) to move stake to"
3159
+ )
3160
+
3161
+ return self._run_command(
3162
+ move.move_stake(
3163
+ subtensor=self.initialize_chain(network),
3164
+ wallet=wallet,
3165
+ origin_netuid=origin_netuid,
3166
+ origin_hotkey=origin_hotkey,
3167
+ destination_netuid=destination_netuid,
3168
+ destination_hotkey=destination_hotkey,
3169
+ amount=amount,
3170
+ stake_all=stake_all,
3171
+ interactive_selection=interactive_selection,
3172
+ prompt=prompt,
3173
+ )
3174
+ )
3175
+
3176
+ def stake_transfer(
3177
+ self,
3178
+ network: Optional[list[str]] = Options.network,
3179
+ wallet_name: Optional[str] = Options.wallet_name,
3180
+ wallet_path: Optional[str] = Options.wallet_path,
3181
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3182
+ origin_netuid: Optional[int] = typer.Option(
3183
+ None,
3184
+ "--origin-netuid",
3185
+ help="The netuid to transfer stake from",
3186
+ ),
3187
+ dest_netuid: Optional[int] = typer.Option(
3188
+ None,
3189
+ "--dest-netuid",
3190
+ help="The netuid to transfer stake to",
3191
+ ),
3192
+ dest_ss58: Optional[str] = typer.Option(
3193
+ None,
3194
+ "--dest-ss58",
3195
+ "--dest",
3196
+ "--dest-coldkey",
3197
+ help="The destination wallet name or SS58 address to transfer stake to",
3198
+ ),
3199
+ amount: float = typer.Option(
3200
+ None,
3201
+ "--amount",
3202
+ "-a",
3203
+ help="Amount of stake to transfer",
3204
+ ),
3205
+ prompt: bool = Options.prompt,
3206
+ quiet: bool = Options.quiet,
3207
+ verbose: bool = Options.verbose,
3208
+ ):
3209
+ """
3210
+ Transfer stake between coldkeys while keeping the same hotkey ownership.
3211
+
3212
+ This command allows you to:
3213
+ - Transfer stake from one coldkey to another coldkey
3214
+ - Keep the same hotkey ownership
3215
+ - Transfer stake between different subnets
3216
+
3217
+ You can specify:
3218
+ - The origin subnet (--origin-netuid)
3219
+ - The destination subnet (--dest-netuid)
3220
+ - The destination wallet/address (--dest)
3221
+ - The amount to transfer (--amount)
3222
+
3223
+ If no arguments are provided, an interactive selection menu will be shown.
3224
+
3225
+ EXAMPLE
3226
+
3227
+ Transfer 100 TAO from subnet 1 to subnet 2:
3228
+ [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest wallet2 --amount 100
3229
+
3230
+ Using SS58 address:
3231
+ [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngEP9H7qjkKgHLcK --amount 100
3232
+ """
3233
+ console.print(
3234
+ "[dim]This command transfers stake from one coldkey to another while keeping the same hotkey.[/dim]"
3235
+ )
3236
+ self.verbosity_handler(quiet, verbose)
3237
+
3238
+ wallet = self.wallet_ask(
3239
+ wallet_name,
3240
+ wallet_path,
3241
+ wallet_hotkey,
3242
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3243
+ validate=WV.WALLET_AND_HOTKEY,
3244
+ )
3245
+
3246
+ if not dest_ss58:
3247
+ dest_ss58 = Prompt.ask(
3248
+ "Enter the [blue]destination wallet name[/blue] or [blue]coldkey SS58 address[/blue]"
3522
3249
  )
3523
3250
 
3251
+ if is_valid_ss58_address(dest_ss58):
3252
+ dest_ss58 = dest_ss58
3524
3253
  else:
3525
- wallet = self.wallet_ask(
3526
- wallet_name,
3254
+ dest_wallet = self.wallet_ask(
3255
+ dest_ss58,
3527
3256
  wallet_path,
3528
- wallet_hotkey,
3529
- ask_for=[WO.NAME, WO.HOTKEY],
3530
- validate=WV.WALLET_AND_HOTKEY,
3257
+ None,
3258
+ ask_for=[WO.NAME, WO.PATH],
3259
+ validate=WV.WALLET,
3531
3260
  )
3261
+ dest_ss58 = dest_wallet.coldkeypub.ss58_address
3532
3262
 
3533
- if include_hotkeys:
3534
- included_hotkeys = parse_to_list(
3535
- include_hotkeys,
3536
- str,
3537
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3538
- "`--include-hotkeys 5Grw....,5Grw....`.",
3539
- )
3263
+ interactive_selection = False
3264
+ if origin_netuid is None and dest_netuid is None and not amount:
3265
+ interactive_selection = True
3540
3266
  else:
3541
- included_hotkeys = []
3267
+ if origin_netuid is None:
3268
+ origin_netuid = IntPrompt.ask(
3269
+ "Enter the [blue]origin subnet[/blue] (netuid)"
3270
+ )
3271
+ if not amount:
3272
+ amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to transfer")
3542
3273
 
3543
- if exclude_hotkeys:
3544
- excluded_hotkeys = parse_to_list(
3545
- exclude_hotkeys,
3546
- str,
3547
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3548
- "`--exclude-hotkeys 5Grw....,5Grw....`.",
3274
+ if dest_netuid is None:
3275
+ dest_netuid = IntPrompt.ask(
3276
+ "Enter the [blue]destination subnet[/blue] (netuid)"
3277
+ )
3278
+
3279
+ return self._run_command(
3280
+ move.transfer_stake(
3281
+ wallet=wallet,
3282
+ subtensor=self.initialize_chain(network),
3283
+ origin_netuid=origin_netuid,
3284
+ dest_netuid=dest_netuid,
3285
+ dest_coldkey_ss58=dest_ss58,
3286
+ amount=amount,
3287
+ interactive_selection=interactive_selection,
3288
+ prompt=prompt,
3549
3289
  )
3290
+ )
3291
+
3292
+ def stake_swap(
3293
+ self,
3294
+ network: Optional[list[str]] = Options.network,
3295
+ wallet_name: Optional[str] = Options.wallet_name,
3296
+ wallet_path: Optional[str] = Options.wallet_path,
3297
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3298
+ origin_netuid: Optional[int] = typer.Option(
3299
+ None,
3300
+ "--origin-netuid",
3301
+ "-o",
3302
+ "--origin",
3303
+ help="The netuid to swap stake from",
3304
+ ),
3305
+ dest_netuid: Optional[int] = typer.Option(
3306
+ None,
3307
+ "--dest-netuid",
3308
+ "-d",
3309
+ "--dest",
3310
+ help="The netuid to swap stake to",
3311
+ ),
3312
+ amount: float = typer.Option(
3313
+ None,
3314
+ "--amount",
3315
+ "-a",
3316
+ help="Amount of stake to swap",
3317
+ ),
3318
+ swap_all: bool = typer.Option(
3319
+ False,
3320
+ "--swap-all",
3321
+ "--all",
3322
+ help="Swap all available stake",
3323
+ ),
3324
+ prompt: bool = Options.prompt,
3325
+ wait_for_inclusion: bool = Options.wait_for_inclusion,
3326
+ wait_for_finalization: bool = Options.wait_for_finalization,
3327
+ quiet: bool = Options.quiet,
3328
+ verbose: bool = Options.verbose,
3329
+ ):
3330
+ """
3331
+ Swap stake between different subnets while keeping the same coldkey-hotkey pair ownership.
3332
+
3333
+ This command allows you to:
3334
+ - Move stake from one subnet to another subnet
3335
+ - Keep the same coldkey ownership
3336
+ - Keep the same hotkey ownership
3337
+
3338
+ You can specify:
3339
+ - The origin subnet (--origin-netuid)
3340
+ - The destination subnet (--dest-netuid)
3341
+ - The amount to swap (--amount)
3342
+
3343
+ If no arguments are provided, an interactive selection menu will be shown.
3344
+
3345
+ EXAMPLE
3346
+
3347
+ Swap 100 TAO from subnet 1 to subnet 2:
3348
+ [green]$[/green] btcli stake swap --wallet-name default --wallet-hotkey default --origin-netuid 1 --dest-netuid 2 --amount 100
3349
+ """
3350
+ console.print(
3351
+ "[dim]This command moves stake from one subnet to another subnet while keeping the same coldkey-hotkey pair.[/dim]"
3352
+ )
3353
+ self.verbosity_handler(quiet, verbose)
3354
+
3355
+ wallet = self.wallet_ask(
3356
+ wallet_name,
3357
+ wallet_path,
3358
+ wallet_hotkey,
3359
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3360
+ validate=WV.WALLET_AND_HOTKEY,
3361
+ )
3362
+
3363
+ interactive_selection = False
3364
+ if origin_netuid is None and dest_netuid is None and not amount:
3365
+ interactive_selection = True
3550
3366
  else:
3551
- excluded_hotkeys = []
3367
+ if origin_netuid is None:
3368
+ origin_netuid = IntPrompt.ask(
3369
+ "Enter the [blue]origin subnet[/blue] (netuid)"
3370
+ )
3371
+ if dest_netuid is None:
3372
+ dest_netuid = IntPrompt.ask(
3373
+ "Enter the [blue]destination subnet[/blue] (netuid)"
3374
+ )
3375
+ if not amount and not swap_all:
3376
+ amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to swap")
3552
3377
 
3553
3378
  return self._run_command(
3554
- stake.unstake(
3555
- wallet,
3556
- self.initialize_chain(network),
3557
- hotkey_ss58_address,
3558
- all_hotkeys,
3559
- included_hotkeys,
3560
- excluded_hotkeys,
3561
- amount,
3562
- keep_stake,
3563
- unstake_all,
3564
- prompt,
3379
+ move.swap_stake(
3380
+ wallet=wallet,
3381
+ subtensor=self.initialize_chain(network),
3382
+ origin_netuid=origin_netuid,
3383
+ destination_netuid=dest_netuid,
3384
+ amount=amount,
3385
+ swap_all=swap_all,
3386
+ interactive_selection=interactive_selection,
3387
+ prompt=prompt,
3388
+ wait_for_inclusion=wait_for_inclusion,
3389
+ wait_for_finalization=wait_for_finalization,
3565
3390
  )
3566
3391
  )
3567
3392
 
@@ -3601,7 +3426,7 @@ class CLIManager:
3601
3426
  wallet_name,
3602
3427
  wallet_path,
3603
3428
  wallet_hotkey,
3604
- ask_for=[WO.NAME, WO.HOTKEY],
3429
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3605
3430
  validate=WV.WALLET_AND_HOTKEY,
3606
3431
  )
3607
3432
 
@@ -3632,18 +3457,8 @@ class CLIManager:
3632
3457
  wallet_hotkey: str = Options.wallet_hotkey,
3633
3458
  wallet_path: str = Options.wallet_path,
3634
3459
  network: Optional[list[str]] = Options.network,
3635
- netuid: Optional[int] = typer.Option(
3636
- None,
3637
- help="The netuid of the subnet, (e.g. 4)",
3638
- prompt=False,
3639
- ),
3640
- all_netuids: bool = typer.Option(
3641
- False,
3642
- "--all-netuids",
3643
- "--all",
3644
- "--allnetuids",
3645
- help="When this flag is used it sets child hotkeys on all subnets.",
3646
- ),
3460
+ netuid: Optional[int] = Options.netuid_not_req,
3461
+ all_netuids: bool = Options.all_netuids,
3647
3462
  proportions: list[float] = typer.Option(
3648
3463
  [],
3649
3464
  "--proportions",
@@ -3656,10 +3471,9 @@ class CLIManager:
3656
3471
  wait_for_finalization: bool = Options.wait_for_finalization,
3657
3472
  quiet: bool = Options.quiet,
3658
3473
  verbose: bool = Options.verbose,
3659
- prompt: bool = Options.prompt,
3660
3474
  ):
3661
3475
  """
3662
- Set child hotkeys on a specified subnet (or all). Overrides currently set children.
3476
+ Set child hotkeys on specified subnets.
3663
3477
 
3664
3478
  Users can specify the 'proportion' to delegate to child hotkeys (ss58 address). The sum of proportions cannot be greater than 1.
3665
3479
 
@@ -3670,15 +3484,8 @@ class CLIManager:
3670
3484
  [green]$[/green] btcli stake child set -c 5FCL3gmjtQV4xxxxuEPEFQVhyyyyqYgNwX7drFLw7MSdBnxP -c 5Hp5dxxxxtGg7pu8dN2btyyyyVA1vELmM9dy8KQv3LxV8PA7 --hotkey default --netuid 1 -p 0.3 -p 0.7
3671
3485
  """
3672
3486
  self.verbosity_handler(quiet, verbose)
3673
- if all_netuids and netuid:
3674
- err_console.print("Specify either a netuid or `--all`, not both.")
3675
- raise typer.Exit()
3676
- if all_netuids:
3677
- netuid = None
3678
- elif not netuid:
3679
- netuid = IntPrompt.ask(
3680
- "Enter a netuid (leave blank for all)", default=None, show_default=True
3681
- )
3487
+ netuid = get_optional_netuid(netuid, all_netuids)
3488
+
3682
3489
  children = list_prompt(
3683
3490
  children,
3684
3491
  str,
@@ -3703,7 +3510,7 @@ class CLIManager:
3703
3510
  wallet_name,
3704
3511
  wallet_path,
3705
3512
  wallet_hotkey,
3706
- ask_for=[WO.NAME, WO.HOTKEY],
3513
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3707
3514
  validate=WV.WALLET_AND_HOTKEY,
3708
3515
  )
3709
3516
  return self._run_command(
@@ -3715,7 +3522,6 @@ class CLIManager:
3715
3522
  proportions=proportions,
3716
3523
  wait_for_finalization=wait_for_finalization,
3717
3524
  wait_for_inclusion=wait_for_inclusion,
3718
- prompt=prompt,
3719
3525
  )
3720
3526
  )
3721
3527
 
@@ -3741,10 +3547,9 @@ class CLIManager:
3741
3547
  wait_for_finalization: bool = Options.wait_for_finalization,
3742
3548
  quiet: bool = Options.quiet,
3743
3549
  verbose: bool = Options.verbose,
3744
- prompt: bool = Options.prompt,
3745
3550
  ):
3746
3551
  """
3747
- Remove all children hotkeys on a specified subnet (or all).
3552
+ Remove all children hotkeys on a specified subnet.
3748
3553
 
3749
3554
  This command is used to remove delegated authority from all child hotkeys, removing their position and influence on the subnet.
3750
3555
 
@@ -3757,7 +3562,7 @@ class CLIManager:
3757
3562
  wallet_name,
3758
3563
  wallet_path,
3759
3564
  wallet_hotkey,
3760
- ask_for=[WO.NAME, WO.HOTKEY],
3565
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3761
3566
  validate=WV.WALLET_AND_HOTKEY,
3762
3567
  )
3763
3568
  if all_netuids and netuid:
@@ -3776,7 +3581,6 @@ class CLIManager:
3776
3581
  netuid,
3777
3582
  wait_for_inclusion,
3778
3583
  wait_for_finalization,
3779
- prompt=prompt,
3780
3584
  )
3781
3585
  )
3782
3586
 
@@ -3813,153 +3617,308 @@ class CLIManager:
3813
3617
  verbose: bool = Options.verbose,
3814
3618
  ):
3815
3619
  """
3816
- Get and set your child hotkey take on a specified subnet.
3620
+ Get and set your child hotkey take on a specified subnet.
3621
+
3622
+ The child hotkey take must be between 0 - 18%.
3623
+
3624
+ EXAMPLE
3625
+
3626
+ To get the current take value, do not use the '--take' option:
3627
+
3628
+ [green]$[/green] btcli stake child take --hotkey <child_hotkey> --netuid 1
3629
+
3630
+ To set a new take value, use the '--take' option:
3631
+
3632
+ [green]$[/green] btcli stake child take --hotkey <child_hotkey> --take 0.12 --netuid 1
3633
+ """
3634
+ self.verbosity_handler(quiet, verbose)
3635
+ wallet = self.wallet_ask(
3636
+ wallet_name,
3637
+ wallet_path,
3638
+ wallet_hotkey,
3639
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3640
+ validate=WV.WALLET_AND_HOTKEY,
3641
+ )
3642
+ if all_netuids and netuid:
3643
+ err_console.print("Specify either a netuid or '--all', not both.")
3644
+ raise typer.Exit()
3645
+ if all_netuids:
3646
+ netuid = None
3647
+ elif not netuid:
3648
+ netuid = IntPrompt.ask(
3649
+ "Enter netuid (leave blank for all)", default=None, show_default=True
3650
+ )
3651
+ return self._run_command(
3652
+ children_hotkeys.childkey_take(
3653
+ wallet=wallet,
3654
+ subtensor=self.initialize_chain(network),
3655
+ netuid=netuid,
3656
+ take=take,
3657
+ hotkey=hotkey,
3658
+ wait_for_inclusion=wait_for_inclusion,
3659
+ wait_for_finalization=wait_for_finalization,
3660
+ prompt=prompt,
3661
+ )
3662
+ )
3663
+
3664
+ def sudo_set(
3665
+ self,
3666
+ network: Optional[list[str]] = Options.network,
3667
+ wallet_name: str = Options.wallet_name,
3668
+ wallet_path: str = Options.wallet_path,
3669
+ wallet_hotkey: str = Options.wallet_hotkey,
3670
+ netuid: int = Options.netuid,
3671
+ param_name: str = typer.Option(
3672
+ "", "--param", "--parameter", help="The subnet hyperparameter to set"
3673
+ ),
3674
+ param_value: str = typer.Option(
3675
+ "", "--value", help="Value to set the hyperparameter to."
3676
+ ),
3677
+ quiet: bool = Options.quiet,
3678
+ verbose: bool = Options.verbose,
3679
+ ):
3680
+ """
3681
+ Used to set hyperparameters for a specific subnet.
3682
+
3683
+ This command allows subnet owners to modify hyperparameters such as its tempo, emission rates, and other hyperparameters.
3684
+
3685
+ EXAMPLE
3686
+
3687
+ [green]$[/green] btcli sudo set --netuid 1 --param tempo --value 400
3688
+ """
3689
+ self.verbosity_handler(quiet, verbose)
3690
+
3691
+ hyperparams = self._run_command(
3692
+ sudo.get_hyperparameters(self.initialize_chain(network), netuid),
3693
+ exit_early=False,
3694
+ )
3695
+
3696
+ if not hyperparams:
3697
+ raise typer.Exit()
3698
+
3699
+ if not param_name:
3700
+ hyperparam_list = [field.name for field in fields(SubnetHyperparameters)]
3701
+ console.print("Available hyperparameters:\n")
3702
+ for idx, param in enumerate(hyperparam_list, start=1):
3703
+ console.print(f" {idx}. {param}")
3704
+ console.print()
3705
+ choice = IntPrompt.ask(
3706
+ "Enter the [bold]number[/bold] of the hyperparameter",
3707
+ choices=[str(i) for i in range(1, len(hyperparam_list) + 1)],
3708
+ show_choices=False,
3709
+ )
3710
+ param_name = hyperparam_list[choice - 1]
3711
+
3712
+ if not param_value:
3713
+ param_value = Prompt.ask(
3714
+ f"Enter the new value for [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{param_name}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] in the VALUE column format"
3715
+ )
3716
+
3717
+ wallet = self.wallet_ask(
3718
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3719
+ )
3720
+ return self._run_command(
3721
+ sudo.sudo_set_hyperparameter(
3722
+ wallet,
3723
+ self.initialize_chain(network),
3724
+ netuid,
3725
+ param_name,
3726
+ param_value,
3727
+ )
3728
+ )
3729
+
3730
+ def sudo_get(
3731
+ self,
3732
+ network: Optional[list[str]] = Options.network,
3733
+ netuid: int = Options.netuid,
3734
+ quiet: bool = Options.quiet,
3735
+ verbose: bool = Options.verbose,
3736
+ ):
3737
+ """
3738
+ Shows a list of the hyperparameters for the specified subnet.
3739
+
3740
+ The output of this command is the same as that of `btcli subnets hyperparameters`.
3741
+
3742
+ EXAMPLE
3743
+
3744
+ [green]$[/green] btcli sudo get --netuid 1
3745
+ """
3746
+ self.verbosity_handler(quiet, verbose)
3747
+ return self._run_command(
3748
+ sudo.get_hyperparameters(self.initialize_chain(network), netuid)
3749
+ )
3750
+
3751
+ def sudo_senate(
3752
+ self,
3753
+ network: Optional[list[str]] = Options.network,
3754
+ quiet: bool = Options.quiet,
3755
+ verbose: bool = Options.verbose,
3756
+ ):
3757
+ """
3758
+ Shows the Senate members of the Bittensor's governance protocol.
3759
+
3760
+ This command lists the delegates involved in the decision-making process of the Bittensor network, showing their names and wallet addresses. This information is crucial for understanding who holds governance roles within the network.
3761
+
3762
+ EXAMPLE
3763
+ [green]$[/green] btcli sudo senate
3764
+ """
3765
+ self.verbosity_handler(quiet, verbose)
3766
+ return self._run_command(sudo.get_senate(self.initialize_chain(network)))
3767
+
3768
+ def sudo_proposals(
3769
+ self,
3770
+ network: Optional[list[str]] = Options.network,
3771
+ quiet: bool = Options.quiet,
3772
+ verbose: bool = Options.verbose,
3773
+ ):
3774
+ """
3775
+ View active proposals for the senate in the Bittensor's governance protocol.
3817
3776
 
3818
- The child hotkey take must be between 0 - 18%.
3777
+ This command displays the details of ongoing proposals, including proposal hashes, votes, thresholds, and proposal data.
3819
3778
 
3820
3779
  EXAMPLE
3780
+ [green]$[/green] btcli sudo proposals
3781
+ """
3782
+ self.verbosity_handler(quiet, verbose)
3783
+ return self._run_command(sudo.proposals(self.initialize_chain(network)))
3821
3784
 
3822
- To get the current take value, do not use the '--take' option:
3785
+ def sudo_senate_vote(
3786
+ self,
3787
+ network: Optional[list[str]] = Options.network,
3788
+ wallet_name: Optional[str] = Options.wallet_name,
3789
+ wallet_path: Optional[str] = Options.wallet_path,
3790
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3791
+ proposal: str = typer.Option(
3792
+ None,
3793
+ "--proposal",
3794
+ "--proposal-hash",
3795
+ prompt="Enter the proposal hash",
3796
+ help="The hash of the proposal to vote on.",
3797
+ ),
3798
+ prompt: bool = Options.prompt,
3799
+ quiet: bool = Options.quiet,
3800
+ verbose: bool = Options.verbose,
3801
+ vote: bool = typer.Option(
3802
+ None,
3803
+ "--vote-aye/--vote-nay",
3804
+ prompt="Enter y to vote Aye, or enter n to vote Nay",
3805
+ help="The vote casted on the proposal",
3806
+ ),
3807
+ ):
3808
+ """
3809
+ Cast a vote on an active proposal in Bittensor's governance protocol.
3823
3810
 
3824
- [green]$[/green] btcli stake child take --hotkey <child_hotkey> --netuid 1
3811
+ This command is used by Senate members to vote on various proposals that shape the network's future. Use `btcli sudo proposals` to see the active proposals and their hashes.
3825
3812
 
3826
- To set a new take value, use the '--take' option:
3813
+ USAGE
3814
+ The user must specify the hash of the proposal they want to vote on. The command then allows the Senate member to cast a 'Yes' or 'No' vote, contributing to the decision-making process on the proposal. This command is crucial for Senate members to exercise their voting rights on key proposals. It plays a vital role in the governance and evolution of the Bittensor network.
3827
3815
 
3828
- [green]$[/green] btcli stake child take --hotkey <child_hotkey> --take 0.12 --netuid 1
3816
+ EXAMPLE
3817
+ [green]$[/green] btcli sudo senate_vote --proposal <proposal_hash>
3829
3818
  """
3830
3819
  self.verbosity_handler(quiet, verbose)
3831
3820
  wallet = self.wallet_ask(
3832
3821
  wallet_name,
3833
3822
  wallet_path,
3834
3823
  wallet_hotkey,
3835
- ask_for=[WO.NAME, WO.HOTKEY],
3824
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3836
3825
  validate=WV.WALLET_AND_HOTKEY,
3837
3826
  )
3838
- if all_netuids and netuid:
3839
- err_console.print("Specify either a netuid or '--all', not both.")
3840
- raise typer.Exit()
3841
- if all_netuids:
3842
- netuid = None
3843
- elif not netuid:
3844
- netuid = IntPrompt.ask(
3845
- "Enter netuid (leave blank for all)", default=None, show_default=True
3846
- )
3847
3827
  return self._run_command(
3848
- children_hotkeys.childkey_take(
3849
- wallet=wallet,
3850
- subtensor=self.initialize_chain(network),
3851
- netuid=netuid,
3852
- take=take,
3853
- hotkey=hotkey,
3854
- wait_for_inclusion=wait_for_inclusion,
3855
- wait_for_finalization=wait_for_finalization,
3856
- prompt=prompt,
3828
+ sudo.senate_vote(
3829
+ wallet, self.initialize_chain(network), proposal, vote, prompt
3857
3830
  )
3858
3831
  )
3859
3832
 
3860
- def sudo_set(
3833
+ def sudo_set_take(
3861
3834
  self,
3862
3835
  network: Optional[list[str]] = Options.network,
3863
- wallet_name: str = Options.wallet_name,
3864
- wallet_path: str = Options.wallet_path,
3865
- wallet_hotkey: str = Options.wallet_hotkey,
3866
- netuid: int = Options.netuid,
3867
- param_name: str = typer.Option(
3868
- "", "--param", "--parameter", help="The subnet hyperparameter to set"
3869
- ),
3870
- param_value: str = typer.Option(
3871
- "", "--value", help="Value to set the hyperparameter to."
3872
- ),
3836
+ wallet_name: Optional[str] = Options.wallet_name,
3837
+ wallet_path: Optional[str] = Options.wallet_path,
3838
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3839
+ take: float = typer.Option(None, help="The new take value."),
3873
3840
  quiet: bool = Options.quiet,
3874
3841
  verbose: bool = Options.verbose,
3875
3842
  ):
3876
3843
  """
3877
- Used to set hyperparameters for a specific subnet.
3844
+ Allows users to change their delegate take percentage.
3878
3845
 
3879
- This command allows subnet owners to modify hyperparameters such as its tempo, emission rates, and other hyperparameters.
3846
+ This command can be used to update the delegate takes. To run the command, the user must have a configured wallet with both hotkey and coldkey.
3847
+ The command makes sure the new take value is within 0-18% range.
3880
3848
 
3881
3849
  EXAMPLE
3882
-
3883
- [green]$[/green] btcli sudo set --netuid 1 --param tempo --value 400
3850
+ [green]$[/green] btcli sudo set-take --wallet-name my_wallet --wallet-hotkey my_hotkey
3884
3851
  """
3852
+ max_value = 0.18
3853
+ min_value = 0.00
3885
3854
  self.verbosity_handler(quiet, verbose)
3886
3855
 
3887
- if not param_name or not param_value:
3888
- hyperparams = self._run_command(
3889
- sudo.get_hyperparameters(self.initialize_chain(network), netuid)
3890
- )
3891
- if not hyperparams:
3892
- raise typer.Exit()
3856
+ wallet = self.wallet_ask(
3857
+ wallet_name,
3858
+ wallet_path,
3859
+ wallet_hotkey,
3860
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3861
+ validate=WV.WALLET_AND_HOTKEY,
3862
+ )
3893
3863
 
3894
- if not param_name:
3895
- hyperparam_list = [field.name for field in fields(SubnetHyperparameters)]
3896
- console.print("Available hyperparameters:\n")
3897
- for idx, param in enumerate(hyperparam_list, start=1):
3898
- console.print(f" {idx}. {param}")
3899
- console.print()
3900
- choice = IntPrompt.ask(
3901
- "Enter the [bold]number[/bold] of the hyperparameter",
3902
- choices=[str(i) for i in range(1, len(hyperparam_list) + 1)],
3903
- show_choices=False,
3904
- )
3905
- param_name = hyperparam_list[choice - 1]
3864
+ self._run_command(
3865
+ sudo.display_current_take(self.initialize_chain(network), wallet),
3866
+ exit_early=False,
3867
+ )
3906
3868
 
3907
- if param_name in ["alpha_high", "alpha_low"]:
3908
- param_name = "alpha_values"
3909
- low_val = FloatPrompt.ask(
3910
- "Enter the new value for [dark_orange]alpha_low[/dark_orange]"
3911
- )
3912
- high_val = FloatPrompt.ask(
3913
- "Enter the new value for [dark_orange]alpha_high[/dark_orange]"
3869
+ if not take:
3870
+ take = FloatPrompt.ask(
3871
+ f"Enter [blue]take value[/blue] (0.18 for 18%) [blue]Min: {min_value} Max: {max_value}"
3914
3872
  )
3915
- param_value = f"{low_val},{high_val}"
3916
-
3917
- if not param_value:
3918
- param_value = Prompt.ask(
3919
- f"Enter the new value for [dark_orange]{param_name}[/dark_orange] in the VALUE column format"
3873
+ if not (min_value <= take <= max_value):
3874
+ print_error(
3875
+ f"Take value must be between {min_value} and {max_value}. Provided value: {take}"
3920
3876
  )
3877
+ raise typer.Exit()
3921
3878
 
3922
- wallet = self.wallet_ask(
3923
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3924
- )
3925
3879
  return self._run_command(
3926
- sudo.sudo_set_hyperparameter(
3927
- wallet,
3928
- self.initialize_chain(network),
3929
- netuid,
3930
- param_name,
3931
- param_value,
3932
- )
3880
+ sudo.set_take(wallet, self.initialize_chain(network), take)
3933
3881
  )
3934
3882
 
3935
- def sudo_get(
3883
+ def sudo_get_take(
3936
3884
  self,
3937
3885
  network: Optional[list[str]] = Options.network,
3938
- netuid: int = Options.netuid,
3886
+ wallet_name: Optional[str] = Options.wallet_name,
3887
+ wallet_path: Optional[str] = Options.wallet_path,
3888
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3939
3889
  quiet: bool = Options.quiet,
3940
3890
  verbose: bool = Options.verbose,
3941
3891
  ):
3942
3892
  """
3943
- Shows a list of the hyperparameters for the specified subnet.
3893
+ Allows users to check their delegate take percentage.
3944
3894
 
3945
- The output of this command is the same as that of `btcli subnets hyperparameters`.
3895
+ This command can be used to fetch the delegate take of your hotkey.
3946
3896
 
3947
3897
  EXAMPLE
3948
-
3949
- [green]$[/green] btcli sudo get --netuid 1
3898
+ [green]$[/green] btcli sudo get-take --wallet-name my_wallet --wallet-hotkey my_hotkey
3950
3899
  """
3951
3900
  self.verbosity_handler(quiet, verbose)
3952
- return self._run_command(
3953
- sudo.get_hyperparameters(self.initialize_chain(network), netuid)
3901
+
3902
+ wallet = self.wallet_ask(
3903
+ wallet_name,
3904
+ wallet_path,
3905
+ wallet_hotkey,
3906
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3907
+ validate=WV.WALLET_AND_HOTKEY,
3908
+ )
3909
+
3910
+ self._run_command(
3911
+ sudo.display_current_take(self.initialize_chain(network), wallet)
3954
3912
  )
3955
3913
 
3956
3914
  def subnets_list(
3957
3915
  self,
3958
3916
  network: Optional[list[str]] = Options.network,
3959
- reuse_last: bool = Options.reuse_last,
3960
- html_output: bool = Options.html_output,
3917
+ # reuse_last: bool = Options.reuse_last,
3918
+ # html_output: bool = Options.html_output,
3961
3919
  quiet: bool = Options.quiet,
3962
3920
  verbose: bool = Options.verbose,
3921
+ live_mode: bool = Options.live,
3963
3922
  ):
3964
3923
  """
3965
3924
  List all subnets and their detailed information.
@@ -3980,48 +3939,189 @@ class CLIManager:
3980
3939
  [green]$[/green] btcli subnets list
3981
3940
  """
3982
3941
  self.verbosity_handler(quiet, verbose)
3983
- if (reuse_last or html_output) and self.config.get("use_cache") is False:
3984
- err_console.print(
3985
- "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. "
3986
- "Change the config to 'False' using `btcli config set`."
3987
- )
3988
- raise typer.Exit()
3989
- if reuse_last:
3990
- subtensor = None
3991
- else:
3992
- subtensor = self.initialize_chain(network)
3942
+ # if (reuse_last or html_output) and self.config.get("use_cache") is False:
3943
+ # err_console.print(
3944
+ # "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. "
3945
+ # "Change the config to 'False' using `btcli config set`."
3946
+ # )
3947
+ # raise typer.Exit()
3948
+ # if reuse_last:
3949
+ # subtensor = None
3950
+ # else:
3951
+ subtensor = self.initialize_chain(network)
3993
3952
  return self._run_command(
3994
3953
  subnets.subnets_list(
3995
3954
  subtensor,
3996
- reuse_last,
3997
- html_output,
3955
+ False, # reuse-last
3956
+ False, # html-output
3998
3957
  not self.config.get("use_cache", True),
3958
+ verbose,
3959
+ live_mode,
3960
+ )
3961
+ )
3962
+
3963
+ def subnets_price(
3964
+ self,
3965
+ network: Optional[list[str]] = Options.network,
3966
+ netuids: str = typer.Option(
3967
+ None,
3968
+ "--netuids",
3969
+ "--netuid",
3970
+ "-n",
3971
+ help="Netuid(s) to show the price for.",
3972
+ ),
3973
+ interval_hours: int = typer.Option(
3974
+ 24,
3975
+ "--interval-hours",
3976
+ "--interval",
3977
+ help="The number of hours to show the historical price for.",
3978
+ ),
3979
+ all_netuids: bool = typer.Option(
3980
+ False,
3981
+ "--all-netuids",
3982
+ "--all",
3983
+ help="Show the price for all subnets.",
3984
+ ),
3985
+ log_scale: bool = typer.Option(
3986
+ False,
3987
+ "--log-scale",
3988
+ "--log",
3989
+ help="Show the price in log scale.",
3990
+ ),
3991
+ html_output: bool = Options.html_output,
3992
+ ):
3993
+ """
3994
+ Shows the historical price of a subnet for the past 24 hours.
3995
+
3996
+ This command displays the historical price of a subnet for the past 24 hours.
3997
+ If the `--all` flag is used, the command will display the price for all subnets in html format.
3998
+ If the `--html` flag is used, the command will display the price in an HTML chart.
3999
+ If the `--log-scale` flag is used, the command will display the price in log scale.
4000
+ If no html flag is used, the command will display the price in the cli.
4001
+
4002
+ EXAMPLE
4003
+
4004
+ [green]$[/green] btcli subnets price --netuid 1
4005
+ [green]$[/green] btcli subnets price --netuid 1 --html --log
4006
+ [green]$[/green] btcli subnets price --all --html
4007
+ [green]$[/green] btcli subnets price --netuids 1,2,3,4 --html
4008
+ """
4009
+ if netuids:
4010
+ netuids = parse_to_list(
4011
+ netuids,
4012
+ int,
4013
+ "Netuids must be a comma-separated list of ints, e.g., `--netuids 1,2,3,4`.",
4014
+ )
4015
+ if all_netuids and netuids:
4016
+ print_error("Cannot specify both --netuid and --all-netuids")
4017
+ raise typer.Exit()
4018
+
4019
+ if not netuids and not all_netuids:
4020
+ netuids = Prompt.ask(
4021
+ "Enter the [blue]netuid(s)[/blue] to view the price of in comma-separated format [dim](or Press Enter to view all subnets)[/dim]",
4022
+ )
4023
+ if not netuids:
4024
+ all_netuids = True
4025
+ html_output = True
4026
+ else:
4027
+ netuids = parse_to_list(
4028
+ netuids,
4029
+ int,
4030
+ "Netuids must be a comma-separated list of ints, e.g., `--netuids 1,2,3,4`.",
4031
+ )
4032
+
4033
+ if all_netuids:
4034
+ html_output = True
4035
+
4036
+ if html_output and is_linux():
4037
+ print_linux_dependency_message()
4038
+
4039
+ return self._run_command(
4040
+ price.price(
4041
+ self.initialize_chain(network),
4042
+ netuids,
4043
+ all_netuids,
4044
+ interval_hours,
4045
+ html_output,
4046
+ log_scale,
4047
+ )
4048
+ )
4049
+
4050
+ def subnets_show(
4051
+ self,
4052
+ network: Optional[list[str]] = Options.network,
4053
+ netuid: int = Options.netuid,
4054
+ quiet: bool = Options.quiet,
4055
+ verbose: bool = Options.verbose,
4056
+ prompt: bool = Options.prompt,
4057
+ ):
4058
+ """
4059
+ Displays detailed information about a subnet including participants and their state.
4060
+
4061
+ EXAMPLE
4062
+
4063
+ [green]$[/green] btcli subnets list
4064
+ """
4065
+ self.verbosity_handler(quiet, verbose)
4066
+ subtensor = self.initialize_chain(network)
4067
+ return self._run_command(
4068
+ subnets.show(
4069
+ subtensor,
4070
+ netuid,
4071
+ verbose=verbose,
4072
+ prompt=prompt,
3999
4073
  )
4000
4074
  )
4001
4075
 
4002
- def subnets_lock_cost(
4076
+ def subnets_burn_cost(
4003
4077
  self,
4004
4078
  network: Optional[list[str]] = Options.network,
4005
4079
  quiet: bool = Options.quiet,
4006
4080
  verbose: bool = Options.verbose,
4007
4081
  ):
4008
4082
  """
4009
- Shows the required amount of TAO to be locked for creating a new subnet, i.e., cost of registering a new subnet.
4083
+ Shows the required amount of TAO to be recycled for creating a new subnet, i.e., cost of registering a new subnet.
4010
4084
 
4011
4085
  The current implementation anneals the cost of creating a subnet over a period of two days. If the displayed cost is unappealing to you, check back in a day or two to see if it has decreased to a more affordable level.
4012
4086
 
4013
4087
  EXAMPLE
4014
4088
 
4015
- [green]$[/green] btcli subnets lock_cost
4089
+ [green]$[/green] btcli subnets burn_cost
4016
4090
  """
4017
4091
  self.verbosity_handler(quiet, verbose)
4018
- return self._run_command(subnets.lock_cost(self.initialize_chain(network)))
4092
+ return self._run_command(subnets.burn_cost(self.initialize_chain(network)))
4019
4093
 
4020
4094
  def subnets_create(
4021
4095
  self,
4022
4096
  wallet_name: str = Options.wallet_name,
4023
4097
  wallet_path: str = Options.wallet_path,
4098
+ wallet_hotkey: str = Options.wallet_hotkey,
4024
4099
  network: Optional[list[str]] = Options.network,
4100
+ subnet_name: Optional[str] = typer.Option(
4101
+ None, "--subnet-name", "--name", help="Name of the subnet"
4102
+ ),
4103
+ github_repo: Optional[str] = typer.Option(
4104
+ None, "--github-repo", "--repo", help="GitHub repository URL"
4105
+ ),
4106
+ subnet_contact: Optional[str] = typer.Option(
4107
+ None,
4108
+ "--subnet-contact",
4109
+ "--contact",
4110
+ "--email",
4111
+ help="Contact email for subnet",
4112
+ ),
4113
+ subnet_url: Optional[str] = typer.Option(
4114
+ None, "--subnet-url", "--url", help="Subnet URL"
4115
+ ),
4116
+ discord: Optional[str] = typer.Option(
4117
+ None, "--discord-handle", "--discord", help="Discord handle"
4118
+ ),
4119
+ description: Optional[str] = typer.Option(
4120
+ None, "--description", help="Description"
4121
+ ),
4122
+ additional_info: Optional[str] = typer.Option(
4123
+ None, "--additional-info", help="Additional information"
4124
+ ),
4025
4125
  prompt: bool = Options.prompt,
4026
4126
  quiet: bool = Options.quiet,
4027
4127
  verbose: bool = Options.verbose,
@@ -4037,13 +4137,44 @@ class CLIManager:
4037
4137
  wallet = self.wallet_ask(
4038
4138
  wallet_name,
4039
4139
  wallet_path,
4040
- None,
4041
- ask_for=[WO.NAME],
4042
- validate=WV.WALLET,
4140
+ wallet_hotkey,
4141
+ ask_for=[
4142
+ WO.NAME,
4143
+ WO.HOTKEY,
4144
+ WO.PATH,
4145
+ ],
4146
+ validate=WV.WALLET_AND_HOTKEY,
4043
4147
  )
4044
- return self._run_command(
4045
- subnets.create(wallet, self.initialize_chain(network), prompt)
4148
+ identity = prompt_for_subnet_identity(
4149
+ subnet_name=subnet_name,
4150
+ github_repo=github_repo,
4151
+ subnet_contact=subnet_contact,
4152
+ subnet_url=subnet_url,
4153
+ discord=discord,
4154
+ description=description,
4155
+ additional=additional_info,
4046
4156
  )
4157
+ success = self._run_command(
4158
+ subnets.create(wallet, self.initialize_chain(network), identity, prompt),
4159
+ exit_early=False,
4160
+ )
4161
+
4162
+ if success and prompt:
4163
+ set_id = Confirm.ask(
4164
+ "[dark_sea_green3]Do you want to set/update your identity?",
4165
+ default=False,
4166
+ show_default=True,
4167
+ )
4168
+ if set_id:
4169
+ self.wallet_set_id(
4170
+ wallet_name=wallet.name,
4171
+ wallet_hotkey=wallet.hotkey,
4172
+ wallet_path=wallet.path,
4173
+ network=network,
4174
+ prompt=prompt,
4175
+ quiet=quiet,
4176
+ verbose=verbose,
4177
+ )
4047
4178
 
4048
4179
  def subnets_pow_register(
4049
4180
  self,
@@ -4092,7 +4223,6 @@ class CLIManager:
4092
4223
  "-tbp",
4093
4224
  help="Set the number of threads per block for CUDA.",
4094
4225
  ),
4095
- prompt: bool = Options.prompt,
4096
4226
  ):
4097
4227
  """
4098
4228
  Register a neuron (a subnet validator or a subnet miner) using Proof of Work (POW).
@@ -4118,7 +4248,7 @@ class CLIManager:
4118
4248
  wallet_name,
4119
4249
  wallet_path,
4120
4250
  wallet_hotkey,
4121
- ask_for=[WO.NAME, WO.HOTKEY],
4251
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4122
4252
  validate=WV.WALLET_AND_HOTKEY,
4123
4253
  ),
4124
4254
  self.initialize_chain(network),
@@ -4130,7 +4260,6 @@ class CLIManager:
4130
4260
  use_cuda,
4131
4261
  dev_id,
4132
4262
  threads_per_block,
4133
- prompt=prompt,
4134
4263
  )
4135
4264
  )
4136
4265
 
@@ -4161,7 +4290,7 @@ class CLIManager:
4161
4290
  wallet_name,
4162
4291
  wallet_path,
4163
4292
  wallet_hotkey,
4164
- ask_for=[WO.NAME, WO.HOTKEY],
4293
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4165
4294
  validate=WV.WALLET_AND_HOTKEY,
4166
4295
  )
4167
4296
  return self._run_command(
@@ -4247,6 +4376,12 @@ class CLIManager:
4247
4376
  )
4248
4377
  raise typer.Exit()
4249
4378
 
4379
+ # For Rao games
4380
+ effective_network = get_effective_network(self.config, network)
4381
+ if is_rao_network(effective_network):
4382
+ print_error("This command is disabled on the 'rao' network.")
4383
+ raise typer.Exit()
4384
+
4250
4385
  if reuse_last:
4251
4386
  if netuid is not None:
4252
4387
  console.print("Cannot specify netuid when using `--reuse-last`")
@@ -4290,7 +4425,6 @@ class CLIManager:
4290
4425
  ),
4291
4426
  quiet: bool = Options.quiet,
4292
4427
  verbose: bool = Options.verbose,
4293
- prompt: bool = Options.prompt,
4294
4428
  ):
4295
4429
  """
4296
4430
  Reveal weights for a specific subnet.
@@ -4349,7 +4483,7 @@ class CLIManager:
4349
4483
  wallet_name,
4350
4484
  wallet_path,
4351
4485
  wallet_hotkey,
4352
- ask_for=[WO.NAME, WO.HOTKEY],
4486
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4353
4487
  validate=WV.WALLET_AND_HOTKEY,
4354
4488
  )
4355
4489
 
@@ -4362,7 +4496,6 @@ class CLIManager:
4362
4496
  weights,
4363
4497
  salt,
4364
4498
  __version_as_int__,
4365
- prompt=prompt,
4366
4499
  )
4367
4500
  )
4368
4501
 
@@ -4388,7 +4521,6 @@ class CLIManager:
4388
4521
  ),
4389
4522
  quiet: bool = Options.quiet,
4390
4523
  verbose: bool = Options.verbose,
4391
- prompt: bool = Options.prompt,
4392
4524
  ):
4393
4525
  """
4394
4526
 
@@ -4447,7 +4579,7 @@ class CLIManager:
4447
4579
  wallet_name,
4448
4580
  wallet_path,
4449
4581
  wallet_hotkey,
4450
- ask_for=[WO.NAME, WO.HOTKEY],
4582
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4451
4583
  validate=WV.WALLET_AND_HOTKEY,
4452
4584
  )
4453
4585
  return self._run_command(
@@ -4459,7 +4591,6 @@ class CLIManager:
4459
4591
  weights,
4460
4592
  salt,
4461
4593
  __version_as_int__,
4462
- prompt=prompt,
4463
4594
  )
4464
4595
  )
4465
4596