bittensor-cli 8.4.4__py3-none-any.whl → 9.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. bittensor_cli/__init__.py +1 -1
  2. bittensor_cli/cli.py +1827 -1394
  3. bittensor_cli/src/__init__.py +623 -168
  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 +129 -23
  7. bittensor_cli/src/bittensor/extrinsics/root.py +3 -3
  8. bittensor_cli/src/bittensor/extrinsics/transfer.py +6 -11
  9. bittensor_cli/src/bittensor/minigraph.py +46 -8
  10. bittensor_cli/src/bittensor/subtensor_interface.py +567 -250
  11. bittensor_cli/src/bittensor/utils.py +370 -25
  12. bittensor_cli/src/commands/stake/__init__.py +154 -0
  13. bittensor_cli/src/commands/stake/add.py +625 -0
  14. bittensor_cli/src/commands/stake/children_hotkeys.py +103 -75
  15. bittensor_cli/src/commands/stake/list.py +687 -0
  16. bittensor_cli/src/commands/stake/move.py +1000 -0
  17. bittensor_cli/src/commands/stake/remove.py +1146 -0
  18. bittensor_cli/src/commands/subnets/__init__.py +0 -0
  19. bittensor_cli/src/commands/subnets/price.py +867 -0
  20. bittensor_cli/src/commands/subnets/subnets.py +2028 -0
  21. bittensor_cli/src/commands/sudo.py +554 -12
  22. bittensor_cli/src/commands/wallets.py +225 -531
  23. bittensor_cli/src/commands/weights.py +2 -2
  24. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/METADATA +7 -4
  25. bittensor_cli-9.0.0.dist-info/RECORD +34 -0
  26. bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
  27. bittensor_cli/src/commands/root.py +0 -1787
  28. bittensor_cli/src/commands/stake/stake.py +0 -1448
  29. bittensor_cli/src/commands/subnets.py +0 -897
  30. bittensor_cli-8.4.4.dist-info/RECORD +0 -31
  31. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/WHEEL +0 -0
  32. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/entry_points.txt +0 -0
  33. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/top_level.txt +0 -0
bittensor_cli/cli.py CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  import asyncio
3
- import binascii
4
3
  import curses
5
- from functools import partial
4
+ import importlib
6
5
  import os.path
7
6
  import re
8
7
  import ssl
@@ -19,21 +18,28 @@ from bittensor_wallet import Wallet
19
18
  from rich import box
20
19
  from rich.prompt import Confirm, FloatPrompt, Prompt, IntPrompt
21
20
  from rich.table import Column, Table
21
+ from rich.tree import Tree
22
22
  from bittensor_cli.src import (
23
23
  defaults,
24
24
  HELP_PANELS,
25
25
  WalletOptions as WO,
26
26
  WalletValidationTypes as WV,
27
27
  Constants,
28
+ COLOR_PALETTE,
28
29
  )
29
30
  from bittensor_cli.src.bittensor import utils
30
31
  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
32
+ from async_substrate_interface.errors import SubstrateRequestException
33
+ from bittensor_cli.src.commands import sudo, wallets
35
34
  from bittensor_cli.src.commands import weights as weights_cmds
36
- from bittensor_cli.src.commands.stake import children_hotkeys, stake
35
+ from bittensor_cli.src.commands.subnets import price, subnets
36
+ from bittensor_cli.src.commands.stake import (
37
+ children_hotkeys,
38
+ list as list_stake,
39
+ move as move_stake,
40
+ add as add_stake,
41
+ remove as remove_stake,
42
+ )
37
43
  from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
38
44
  from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
39
45
  from bittensor_cli.src.bittensor.utils import (
@@ -43,22 +49,30 @@ from bittensor_cli.src.bittensor.utils import (
43
49
  is_valid_ss58_address,
44
50
  print_error,
45
51
  validate_chain_endpoint,
46
- retry_prompt,
52
+ validate_netuid,
53
+ is_rao_network,
54
+ get_effective_network,
55
+ prompt_for_identity,
56
+ validate_uri,
57
+ prompt_for_subnet_identity,
58
+ print_linux_dependency_message,
59
+ is_linux,
60
+ validate_rate_tolerance,
47
61
  )
48
62
  from typing_extensions import Annotated
49
- from textwrap import dedent
50
- from websockets import ConnectionClosed
63
+ from websockets import ConnectionClosed, InvalidHandshake
51
64
  from yaml import safe_dump, safe_load
52
65
 
53
66
  try:
54
67
  from git import Repo, GitError
55
68
  except ImportError:
69
+ Repo = None
56
70
 
57
71
  class GitError(Exception):
58
72
  pass
59
73
 
60
74
 
61
- __version__ = "8.4.4"
75
+ __version__ = "9.0.0"
62
76
 
63
77
 
64
78
  _core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0)
@@ -155,8 +169,19 @@ class Options:
155
169
  )
156
170
  netuid = typer.Option(
157
171
  None,
158
- help="The netuid of the subnet in the root network, (e.g. 1).",
172
+ help="The netuid of the subnet in the network, (e.g. 1).",
159
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 network, (e.g. 1).",
179
+ prompt=False,
180
+ )
181
+ all_netuids = typer.Option(
182
+ False,
183
+ help="Use all netuids",
184
+ prompt=False,
160
185
  )
161
186
  weights = typer.Option(
162
187
  None,
@@ -202,6 +227,39 @@ class Options:
202
227
  "--quiet",
203
228
  help="Display only critical information on the console.",
204
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
+ )
241
+ rate_tolerance = typer.Option(
242
+ None,
243
+ "--slippage",
244
+ "--slippage-tolerance",
245
+ "--tolerance",
246
+ help="Set the rate tolerance percentage for transactions (default: 0.05%).",
247
+ callback=validate_rate_tolerance,
248
+ )
249
+ safe_staking = typer.Option(
250
+ None,
251
+ "--safe-staking/--no-safe-staking",
252
+ "--safe/--unsafe",
253
+ help="Enable or disable safe staking mode (default: enabled).",
254
+ )
255
+ allow_partial_stake = typer.Option(
256
+ None,
257
+ "--allow-partial-stake/--no-allow-partial-stake",
258
+ "--partial/--no-partial",
259
+ "--allow/--not-allow",
260
+ "--allow-partial/--not-partial",
261
+ help="Enable or disable partial stake mode (default: disabled).",
262
+ )
205
263
 
206
264
 
207
265
  def list_prompt(init_var: list, list_type: type, help_text: str) -> list:
@@ -244,7 +302,8 @@ def parse_to_list(
244
302
  def verbosity_console_handler(verbosity_level: int = 1) -> None:
245
303
  """
246
304
  Sets verbosity level of console output
247
- :param verbosity_level: int corresponding to verbosity level of console output (0 is quiet, 1 is normal, 2 is verbose)
305
+ :param verbosity_level: int corresponding to verbosity level of console output (0 is quiet, 1 is normal, 2 is
306
+ verbose)
248
307
  """
249
308
  if verbosity_level not in range(3):
250
309
  raise ValueError(
@@ -264,6 +323,32 @@ def verbosity_console_handler(verbosity_level: int = 1) -> None:
264
323
  verbose_console.quiet = False
265
324
 
266
325
 
326
+ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[int]:
327
+ """
328
+ Parses options to determine if the user wants to use a specific netuid or all netuids (None)
329
+
330
+ Returns:
331
+ None if using all netuids, otherwise int for the netuid to use
332
+ """
333
+ if netuid is None and all_netuids is True:
334
+ return None
335
+ elif netuid is None and all_netuids is False:
336
+ answer = Prompt.ask(
337
+ f"Enter the [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]netuid"
338
+ f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}] to use. Leave blank for all netuids",
339
+ default=None,
340
+ show_default=False,
341
+ )
342
+ if answer is None:
343
+ return None
344
+ if answer.lower() == "all":
345
+ return None
346
+ else:
347
+ return int(answer)
348
+ else:
349
+ return netuid
350
+
351
+
267
352
  def get_n_words(n_words: Optional[int]) -> int:
268
353
  """
269
354
  Prompts the user to select the number of words used in the mnemonic if not supplied or not within the
@@ -413,12 +498,21 @@ def version_callback(value: bool):
413
498
  raise typer.Exit()
414
499
 
415
500
 
501
+ def commands_callback(value: bool):
502
+ """
503
+ Prints a tree of commands for the app
504
+ """
505
+ if value:
506
+ cli = CLIManager()
507
+ console.print(cli.generate_command_tree())
508
+ raise typer.Exit()
509
+
510
+
416
511
  class CLIManager:
417
512
  """
418
513
  :var app: the main CLI Typer app
419
514
  :var config_app: the Typer app as it relates to config commands
420
515
  :var wallet_app: the Typer app as it relates to wallet commands
421
- :var root_app: the Typer app as it relates to root commands
422
516
  :var stake_app: the Typer app as it relates to stake commands
423
517
  :var sudo_app: the Typer app as it relates to sudo commands
424
518
  :var subnets_app: the Typer app as it relates to subnets commands
@@ -429,10 +523,10 @@ class CLIManager:
429
523
  app: typer.Typer
430
524
  config_app: typer.Typer
431
525
  wallet_app: typer.Typer
432
- root_app: typer.Typer
433
526
  subnets_app: typer.Typer
434
527
  weights_app: typer.Typer
435
528
  utils_app = typer.Typer(epilog=_epilog)
529
+ asyncio_runner = asyncio
436
530
 
437
531
  def __init__(self):
438
532
  self.config = {
@@ -441,23 +535,29 @@ class CLIManager:
441
535
  "wallet_hotkey": None,
442
536
  "network": None,
443
537
  "use_cache": True,
444
- "metagraph_cols": {
445
- "UID": True,
446
- "STAKE": True,
447
- "RANK": True,
448
- "TRUST": True,
449
- "CONSENSUS": True,
450
- "INCENTIVE": True,
451
- "DIVIDENDS": True,
452
- "EMISSION": True,
453
- "VTRUST": True,
454
- "VAL": True,
455
- "UPDATED": True,
456
- "ACTIVE": True,
457
- "AXON": True,
458
- "HOTKEY": True,
459
- "COLDKEY": True,
460
- },
538
+ "rate_tolerance": None,
539
+ "safe_staking": True,
540
+ "allow_partial_stake": False,
541
+ # Commenting this out as this needs to get updated
542
+ # "metagraph_cols": {
543
+ # "UID": True,
544
+ # "GLOBAL_STAKE": True,
545
+ # "LOCAL_STAKE": True,
546
+ # "STAKE_WEIGHT": True,
547
+ # "RANK": True,
548
+ # "TRUST": True,
549
+ # "CONSENSUS": True,
550
+ # "INCENTIVE": True,
551
+ # "DIVIDENDS": True,
552
+ # "EMISSION": True,
553
+ # "VTRUST": True,
554
+ # "VAL": True,
555
+ # "UPDATED": True,
556
+ # "ACTIVE": True,
557
+ # "AXON": True,
558
+ # "HOTKEY": True,
559
+ # "COLDKEY": True,
560
+ # },
461
561
  }
462
562
  self.subtensor = None
463
563
  self.config_base_path = os.path.expanduser(defaults.config.base_path)
@@ -471,7 +571,6 @@ class CLIManager:
471
571
  )
472
572
  self.config_app = typer.Typer(epilog=_epilog)
473
573
  self.wallet_app = typer.Typer(epilog=_epilog)
474
- self.root_app = typer.Typer(epilog=_epilog)
475
574
  self.stake_app = typer.Typer(epilog=_epilog)
476
575
  self.sudo_app = typer.Typer(epilog=_epilog)
477
576
  self.subnets_app = typer.Typer(epilog=_epilog)
@@ -501,15 +600,6 @@ class CLIManager:
501
600
  self.wallet_app, name="wallets", hidden=True, no_args_is_help=True
502
601
  )
503
602
 
504
- # root aliases
505
- self.app.add_typer(
506
- self.root_app,
507
- name="root",
508
- short_help="Root commands, alias: `r`",
509
- no_args_is_help=True,
510
- )
511
- self.app.add_typer(self.root_app, name="r", hidden=True, no_args_is_help=True)
512
-
513
603
  # stake aliases
514
604
  self.app.add_typer(
515
605
  self.stake_app,
@@ -558,13 +648,15 @@ class CLIManager:
558
648
  )
559
649
 
560
650
  # utils app
561
- self.app.add_typer(self.utils_app, name="utils", no_args_is_help=True)
651
+ self.app.add_typer(
652
+ self.utils_app, name="utils", no_args_is_help=True, hidden=True
653
+ )
562
654
 
563
655
  # config commands
564
656
  self.config_app.command("set")(self.set_config)
565
657
  self.config_app.command("get")(self.get_config)
566
658
  self.config_app.command("clear")(self.del_config)
567
- self.config_app.command("metagraph")(self.metagraph_config)
659
+ self.config_app.command("metagraph", hidden=True)(self.metagraph_config)
568
660
 
569
661
  # wallet commands
570
662
  self.wallet_app.command(
@@ -595,16 +687,21 @@ class CLIManager:
595
687
  "balance", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
596
688
  )(self.wallet_balance)
597
689
  self.wallet_app.command(
598
- "history", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
690
+ "history",
691
+ rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"],
692
+ hidden=True,
599
693
  )(self.wallet_history)
600
694
  self.wallet_app.command(
601
- "overview", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
695
+ "overview",
696
+ rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"],
602
697
  )(self.wallet_overview)
603
698
  self.wallet_app.command(
604
699
  "transfer", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
605
700
  )(self.wallet_transfer)
606
701
  self.wallet_app.command(
607
- "inspect", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"]
702
+ "inspect",
703
+ rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"],
704
+ hidden=True,
608
705
  )(self.wallet_inspect)
609
706
  self.wallet_app.command(
610
707
  "faucet", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
@@ -619,59 +716,25 @@ class CLIManager:
619
716
  "sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
620
717
  )(self.wallet_sign)
621
718
 
622
- # root commands
623
- self.root_app.command("list")(self.root_list)
624
- self.root_app.command(
625
- "set-weights", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
626
- )(self.root_set_weights)
627
- self.root_app.command(
628
- "get-weights", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
629
- )(self.root_get_weights)
630
- self.root_app.command(
631
- "boost", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
632
- )(self.root_boost)
633
- self.root_app.command(
634
- "slash", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"]
635
- )(self.root_slash)
636
- self.root_app.command(
637
- "senate", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
638
- )(self.root_senate)
639
- self.root_app.command(
640
- "senate-vote", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
641
- )(self.root_senate_vote)
642
- self.root_app.command("register")(self.root_register)
643
- self.root_app.command(
644
- "proposals", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
645
- )(self.root_proposals)
646
- self.root_app.command(
647
- "set-take", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
648
- )(self.root_set_take)
649
- self.root_app.command(
650
- "delegate-stake", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
651
- )(self.root_delegate_stake)
652
- self.root_app.command(
653
- "undelegate-stake", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
654
- )(self.root_undelegate_stake)
655
- self.root_app.command(
656
- "my-delegates", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
657
- )(self.root_my_delegates)
658
- self.root_app.command(
659
- "list-delegates", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"]
660
- )(self.root_list_delegates)
661
- self.root_app.command(
662
- "nominate", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"]
663
- )(self.root_nominate)
664
-
665
719
  # stake commands
666
- self.stake_app.command(
667
- "show", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
668
- )(self.stake_show)
669
720
  self.stake_app.command(
670
721
  "add", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
671
722
  )(self.stake_add)
672
723
  self.stake_app.command(
673
724
  "remove", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
674
725
  )(self.stake_remove)
726
+ self.stake_app.command(
727
+ "list", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"]
728
+ )(self.stake_list)
729
+ self.stake_app.command(
730
+ "move", rich_help_panel=HELP_PANELS["STAKE"]["MOVEMENT"]
731
+ )(self.stake_move)
732
+ self.stake_app.command(
733
+ "transfer", rich_help_panel=HELP_PANELS["STAKE"]["MOVEMENT"]
734
+ )(self.stake_transfer)
735
+ self.stake_app.command(
736
+ "swap", rich_help_panel=HELP_PANELS["STAKE"]["MOVEMENT"]
737
+ )(self.stake_swap)
675
738
 
676
739
  # stake-children commands
677
740
  children_app = typer.Typer()
@@ -697,6 +760,21 @@ class CLIManager:
697
760
  self.sudo_app.command("get", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])(
698
761
  self.sudo_get
699
762
  )
763
+ self.sudo_app.command(
764
+ "senate", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"]
765
+ )(self.sudo_senate)
766
+ self.sudo_app.command(
767
+ "proposals", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"]
768
+ )(self.sudo_proposals)
769
+ self.sudo_app.command(
770
+ "senate-vote", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"]
771
+ )(self.sudo_senate_vote)
772
+ self.sudo_app.command("set-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])(
773
+ self.sudo_set_take
774
+ )
775
+ self.sudo_app.command("get-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])(
776
+ self.sudo_get_take
777
+ )
700
778
 
701
779
  # subnets commands
702
780
  self.subnets_app.command(
@@ -706,8 +784,8 @@ class CLIManager:
706
784
  "list", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
707
785
  )(self.subnets_list)
708
786
  self.subnets_app.command(
709
- "lock-cost", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"]
710
- )(self.subnets_lock_cost)
787
+ "burn-cost", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"]
788
+ )(self.subnets_burn_cost)
711
789
  self.subnets_app.command(
712
790
  "create", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"]
713
791
  )(self.subnets_create)
@@ -718,8 +796,14 @@ class CLIManager:
718
796
  "register", rich_help_panel=HELP_PANELS["SUBNETS"]["REGISTER"]
719
797
  )(self.subnets_register)
720
798
  self.subnets_app.command(
721
- "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
722
- )(self.subnets_metagraph)
799
+ "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"], hidden=True
800
+ )(self.subnets_show) # Aliased to `s show` for now
801
+ self.subnets_app.command(
802
+ "show", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
803
+ )(self.subnets_show)
804
+ self.subnets_app.command(
805
+ "price", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"]
806
+ )(self.subnets_price)
723
807
 
724
808
  # weights commands
725
809
  self.weights_app.command(
@@ -764,22 +848,49 @@ class CLIManager:
764
848
  hidden=True,
765
849
  )(self.wallet_get_id)
766
850
 
767
- # Root
768
- self.root_app.command("set_weights", hidden=True)(self.root_set_weights)
769
- self.root_app.command("get_weights", hidden=True)(self.root_get_weights)
770
- self.root_app.command("senate_vote", hidden=True)(self.root_senate_vote)
771
- self.root_app.command("set_take", hidden=True)(self.root_set_take)
772
- self.root_app.command("delegate_stake", hidden=True)(self.root_delegate_stake)
773
- self.root_app.command("undelegate_stake", hidden=True)(
774
- self.root_undelegate_stake
775
- )
776
- self.root_app.command("my_delegates", hidden=True)(self.root_my_delegates)
777
- self.root_app.command("list_delegates", hidden=True)(self.root_list_delegates)
778
-
779
851
  # Subnets
780
- self.subnets_app.command("lock_cost", hidden=True)(self.subnets_lock_cost)
852
+ self.subnets_app.command("burn_cost", hidden=True)(self.subnets_burn_cost)
781
853
  self.subnets_app.command("pow_register", hidden=True)(self.subnets_pow_register)
782
854
 
855
+ # Sudo
856
+ self.sudo_app.command("senate_vote", hidden=True)(self.sudo_senate_vote)
857
+ self.sudo_app.command("get_take", hidden=True)(self.sudo_get_take)
858
+ self.sudo_app.command("set_take", hidden=True)(self.sudo_set_take)
859
+
860
+ def generate_command_tree(self) -> Tree:
861
+ """
862
+ Generates a rich.Tree of the commands, subcommands, and groups of this app
863
+ """
864
+
865
+ def build_rich_tree(data: dict, parent: Tree):
866
+ for group, content in data.get("groups", {}).items():
867
+ group_node = parent.add(
868
+ f"[bold cyan]{group}[/]"
869
+ ) # Add group to the tree
870
+ for command in content.get("commands", []):
871
+ group_node.add(f"[green]{command}[/]") # Add commands to the group
872
+ build_rich_tree(content, group_node) # Recurse for subgroups
873
+
874
+ def traverse_group(group: typer.Typer) -> dict:
875
+ tree = {}
876
+ if commands := [
877
+ cmd.name for cmd in group.registered_commands if not cmd.hidden
878
+ ]:
879
+ tree["commands"] = commands
880
+ for group in group.registered_groups:
881
+ if "groups" not in tree:
882
+ tree["groups"] = {}
883
+ if not group.hidden:
884
+ if group_transversal := traverse_group(group.typer_instance):
885
+ tree["groups"][group.name] = group_transversal
886
+
887
+ return tree
888
+
889
+ groups_and_commands = traverse_group(self.app)
890
+ root = Tree("[bold magenta]BTCLI Commands[/]") # Root node
891
+ build_rich_tree(groups_and_commands, root)
892
+ return root
893
+
783
894
  def initialize_chain(
784
895
  self,
785
896
  network: Optional[list[str]] = None,
@@ -810,13 +921,13 @@ class CLIManager:
810
921
  elif self.config["network"]:
811
922
  self.subtensor = SubtensorInterface(self.config["network"])
812
923
  console.print(
813
- f"Using the specified network [dark_orange]{self.config['network']}[/dark_orange] from config"
924
+ f"Using the specified network [{COLOR_PALETTE['GENERAL']['LINKS']}]{self.config['network']}[/{COLOR_PALETTE['GENERAL']['LINKS']}] from config"
814
925
  )
815
926
  else:
816
927
  self.subtensor = SubtensorInterface(defaults.subtensor.network)
817
928
  return self.subtensor
818
929
 
819
- def _run_command(self, cmd: Coroutine):
930
+ def _run_command(self, cmd: Coroutine, exit_early: bool = True):
820
931
  """
821
932
  Runs the supplied coroutine with `asyncio.run`
822
933
  """
@@ -832,16 +943,19 @@ class CLIManager:
832
943
  initiated = True
833
944
  result = await cmd
834
945
  return result
835
- except (ConnectionRefusedError, ssl.SSLError):
946
+ except (ConnectionRefusedError, ssl.SSLError, InvalidHandshake):
836
947
  err_console.print(f"Unable to connect to the chain: {self.subtensor}")
837
948
  verbose_console.print(traceback.format_exc())
838
949
  except (
839
950
  ConnectionClosed,
840
951
  SubstrateRequestException,
841
952
  KeyboardInterrupt,
953
+ RuntimeError,
842
954
  ) as e:
843
955
  if isinstance(e, SubstrateRequestException):
844
956
  err_console.print(str(e))
957
+ elif isinstance(e, RuntimeError):
958
+ pass # Temporarily to handle loop bound issues
845
959
  verbose_console.print(traceback.format_exc())
846
960
  except Exception as e:
847
961
  err_console.print(f"An unknown error has occurred: {e}")
@@ -849,23 +963,35 @@ class CLIManager:
849
963
  finally:
850
964
  if initiated is False:
851
965
  asyncio.create_task(cmd).cancel()
852
- raise typer.Exit()
966
+ if (
967
+ exit_early is True
968
+ ): # temporarily to handle multiple run commands in one session
969
+ try:
970
+ raise typer.Exit()
971
+ except Exception as e: # ensures we always exit cleanly
972
+ if not isinstance(e, (typer.Exit, RuntimeError)):
973
+ err_console.print(f"An unknown error has occurred: {e}")
853
974
 
854
- if sys.version_info < (3, 10):
855
- # For Python 3.9 or lower
856
- return asyncio.get_event_loop().run_until_complete(_run())
857
- else:
858
- # For Python 3.10 or higher
859
- return asyncio.run(_run())
975
+ return self.asyncio_runner(_run())
860
976
 
861
977
  def main_callback(
862
978
  self,
863
979
  version: Annotated[
864
- Optional[bool], typer.Option("--version", callback=version_callback)
980
+ Optional[bool],
981
+ typer.Option(
982
+ "--version", callback=version_callback, help="Show BTCLI version"
983
+ ),
984
+ ] = None,
985
+ commands: Annotated[
986
+ Optional[bool],
987
+ typer.Option(
988
+ "--commands", callback=commands_callback, help="Show BTCLI commands"
989
+ ),
865
990
  ] = None,
866
991
  ):
867
992
  """
868
- 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.
993
+ Command line interface (CLI) for Bittensor. Uses the values in the configuration file. These values can be
994
+ overriden by passing them explicitly in the command line.
869
995
  """
870
996
  # Load or create the config file
871
997
  if os.path.exists(self.config_path):
@@ -897,6 +1023,20 @@ class CLIManager:
897
1023
  if k in self.config.keys():
898
1024
  self.config[k] = v
899
1025
 
1026
+ if sys.version_info < (3, 10):
1027
+ # For Python 3.9 or lower
1028
+ self.asyncio_runner = asyncio.get_event_loop().run_until_complete
1029
+ else:
1030
+ try:
1031
+ uvloop = importlib.import_module("uvloop")
1032
+ if sys.version_info >= (3, 11):
1033
+ self.asyncio_runner = uvloop.run
1034
+ else:
1035
+ uvloop.install()
1036
+ self.asyncio_runner = asyncio.run
1037
+ except ModuleNotFoundError:
1038
+ self.asyncio_runner = asyncio.run
1039
+
900
1040
  def verbosity_handler(self, quiet: bool, verbose: bool):
901
1041
  if quiet and verbose:
902
1042
  err_console.print("Cannot specify both `--quiet` and `--verbose`")
@@ -953,9 +1093,44 @@ class CLIManager:
953
1093
  help="Disable caching of some commands. This will disable the `--reuse-last` and `--html` flags on "
954
1094
  "commands such as `subnets metagraph`, `stake show` and `subnets list`.",
955
1095
  ),
1096
+ rate_tolerance: Optional[float] = typer.Option(
1097
+ None,
1098
+ "--slippage",
1099
+ "--slippage-tolerance",
1100
+ "--tolerance",
1101
+ help="Set the rate tolerance percentage for transactions (e.g. 0.1 for 0.1%).",
1102
+ ),
1103
+ safe_staking: Optional[bool] = typer.Option(
1104
+ None,
1105
+ "--safe-staking/--no-safe-staking",
1106
+ "--safe/--unsafe",
1107
+ help="Enable or disable safe staking mode.",
1108
+ ),
1109
+ allow_partial_stake: Optional[bool] = typer.Option(
1110
+ None,
1111
+ "--allow-partial-stake/--no-allow-partial-stake",
1112
+ "--partial/--no-partial",
1113
+ "--allow/--not-allow",
1114
+ ),
956
1115
  ):
957
1116
  """
958
- Sets the values in the config file. To set the metagraph configuration, use the command `btcli config metagraph`
1117
+ Sets or updates configuration values in the BTCLI config file.
1118
+
1119
+ This command allows you to set default values that will be used across all BTCLI commands.
1120
+
1121
+ USAGE
1122
+ Interactive mode:
1123
+ [green]$[/green] btcli config set
1124
+
1125
+ Set specific values:
1126
+ [green]$[/green] btcli config set --wallet-name default --network finney
1127
+ [green]$[/green] btcli config set --safe-staking --rate-tolerance 0.1
1128
+
1129
+ [bold]NOTE[/bold]:
1130
+ - Network values can be network names (e.g., 'finney', 'test') or websocket URLs
1131
+ - Rate tolerance is specified as a decimal (e.g., 0.05 for 0.05%)
1132
+ - Changes are saved to ~/.bittensor/btcli.yaml
1133
+ - Use '[green]$[/green] btcli config get' to view current settings
959
1134
  """
960
1135
  args = {
961
1136
  "wallet_name": wallet_name,
@@ -963,8 +1138,11 @@ class CLIManager:
963
1138
  "wallet_hotkey": wallet_hotkey,
964
1139
  "network": network,
965
1140
  "use_cache": use_cache,
1141
+ "rate_tolerance": rate_tolerance,
1142
+ "safe_staking": safe_staking,
1143
+ "allow_partial_stake": allow_partial_stake,
966
1144
  }
967
- bools = ["use_cache"]
1145
+ bools = ["use_cache", "safe_staking", "allow_partial_stake"]
968
1146
  if all(v is None for v in args.values()):
969
1147
  # Print existing configs
970
1148
  self.get_config()
@@ -988,6 +1166,20 @@ class CLIManager:
988
1166
  default=True,
989
1167
  )
990
1168
  self.config[arg] = nc
1169
+
1170
+ elif arg == "rate_tolerance":
1171
+ while True:
1172
+ val = FloatPrompt.ask(
1173
+ f"What percentage would you like to set for [red]{arg}[/red]?\nValues are percentages (e.g. 0.05 for 5%)",
1174
+ default=0.05,
1175
+ )
1176
+ try:
1177
+ validated_val = validate_rate_tolerance(val)
1178
+ self.config[arg] = validated_val
1179
+ break
1180
+ except typer.BadParameter as e:
1181
+ print_error(str(e))
1182
+ continue
991
1183
  else:
992
1184
  val = Prompt.ask(
993
1185
  f"What value would you like to assign to [red]{arg}[/red]?"
@@ -1045,6 +1237,18 @@ class CLIManager:
1045
1237
  wallet_hotkey: bool = typer.Option(False, *Options.wallet_hotkey.param_decls),
1046
1238
  network: bool = typer.Option(False, *Options.network.param_decls),
1047
1239
  use_cache: bool = typer.Option(False, "--cache"),
1240
+ rate_tolerance: bool = typer.Option(
1241
+ False, "--slippage", "--slippage-tolerance", "--tolerance"
1242
+ ),
1243
+ safe_staking: bool = typer.Option(
1244
+ False, "--safe-staking/--no-safe-staking", "--safe/--unsafe"
1245
+ ),
1246
+ allow_partial_stake: bool = typer.Option(
1247
+ False,
1248
+ "--allow-partial-stake/--no-allow-partial-stake",
1249
+ "--partial/--no-partial",
1250
+ "--allow/--not-allow",
1251
+ ),
1048
1252
  all_items: bool = typer.Option(False, "--all"),
1049
1253
  ):
1050
1254
  """
@@ -1074,6 +1278,9 @@ class CLIManager:
1074
1278
  "wallet_hotkey": wallet_hotkey,
1075
1279
  "network": network,
1076
1280
  "use_cache": use_cache,
1281
+ "rate_tolerance": rate_tolerance,
1282
+ "safe_staking": safe_staking,
1283
+ "allow_partial_stake": allow_partial_stake,
1077
1284
  }
1078
1285
 
1079
1286
  # If no specific argument is provided, iterate over all
@@ -1135,6 +1342,8 @@ class CLIManager:
1135
1342
  else:
1136
1343
  if value in Constants.networks:
1137
1344
  value = value + f" ({Constants.network_map[value]})"
1345
+ if key == "rate_tolerance":
1346
+ value = f"{value} ({value*100}%)" if value is not None else "None"
1138
1347
 
1139
1348
  elif key in deprecated_configs:
1140
1349
  continue
@@ -1147,13 +1356,112 @@ class CLIManager:
1147
1356
  table.add_row(str(key), str(value), "")
1148
1357
 
1149
1358
  console.print(table)
1150
- console.print(
1151
- dedent(
1152
- """
1153
- [red]Deprecation notice[/red]: The chain endpoint config is now deprecated. You can use the network config to pass chain endpoints.
1154
- """
1359
+
1360
+ def ask_rate_tolerance(
1361
+ self,
1362
+ rate_tolerance: Optional[float],
1363
+ ) -> float:
1364
+ """
1365
+ Gets rate tolerance from args, config, or default.
1366
+
1367
+ Args:
1368
+ rate_tolerance (Optional[float]): Explicitly provided slippage value
1369
+
1370
+ Returns:
1371
+ float: rate tolerance value
1372
+ """
1373
+ if rate_tolerance is not None:
1374
+ console.print(
1375
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{rate_tolerance} ({rate_tolerance*100}%)[/bold cyan]."
1155
1376
  )
1156
- )
1377
+ return rate_tolerance
1378
+ elif self.config.get("rate_tolerance") is not None:
1379
+ config_slippage = self.config["rate_tolerance"]
1380
+ console.print(
1381
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{config_slippage} ({config_slippage*100}%)[/bold cyan] (from config)."
1382
+ )
1383
+ return config_slippage
1384
+ else:
1385
+ console.print(
1386
+ "[dim][blue]Rate tolerance[/blue]: "
1387
+ + f"[bold cyan]{defaults.rate_tolerance} ({defaults.rate_tolerance*100}%)[/bold cyan] "
1388
+ + "by default. Set this using "
1389
+ + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1390
+ + "or "
1391
+ + "[dark_sea_green3 italic]`--tolerance`[/dark_sea_green3 italic] flag[/dim]"
1392
+ )
1393
+ return defaults.rate_tolerance
1394
+
1395
+ def ask_safe_staking(
1396
+ self,
1397
+ safe_staking: Optional[bool],
1398
+ ) -> bool:
1399
+ """
1400
+ Gets safe staking setting from args, config, or default.
1401
+
1402
+ Args:
1403
+ safe_staking (Optional[bool]): Explicitly provided safe staking value
1404
+
1405
+ Returns:
1406
+ bool: Safe staking setting
1407
+ """
1408
+ if safe_staking is not None:
1409
+ console.print(
1410
+ f"[dim][blue]Safe staking[/blue]: [bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan]."
1411
+ )
1412
+ return safe_staking
1413
+ elif self.config.get("safe_staking") is not None:
1414
+ safe_staking = self.config["safe_staking"]
1415
+ console.print(
1416
+ f"[dim][blue]Safe staking[/blue]: [bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan] (from config)."
1417
+ )
1418
+ return safe_staking
1419
+ else:
1420
+ safe_staking = True
1421
+ console.print(
1422
+ "[dim][blue]Safe staking[/blue]: "
1423
+ + f"[bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan] "
1424
+ + "by default. Set this using "
1425
+ + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1426
+ + "or "
1427
+ + "[dark_sea_green3 italic]`--safe/--unsafe`[/dark_sea_green3 italic] flag[/dim]"
1428
+ )
1429
+ return safe_staking
1430
+
1431
+ def ask_partial_stake(
1432
+ self,
1433
+ allow_partial_stake: Optional[bool],
1434
+ ) -> bool:
1435
+ """
1436
+ Gets partial stake setting from args, config, or default.
1437
+
1438
+ Args:
1439
+ allow_partial_stake (Optional[bool]): Explicitly provided partial stake value
1440
+
1441
+ Returns:
1442
+ bool: Partial stake setting
1443
+ """
1444
+ if allow_partial_stake is not None:
1445
+ console.print(
1446
+ f"[dim][blue]Partial staking[/blue]: [bold cyan]{'enabled' if allow_partial_stake else 'disabled'}[/bold cyan]."
1447
+ )
1448
+ return allow_partial_stake
1449
+ elif self.config.get("allow_partial_stake") is not None:
1450
+ config_partial = self.config["allow_partial_stake"]
1451
+ console.print(
1452
+ f"[dim][blue]Partial staking[/blue]: [bold cyan]{'enabled' if config_partial else 'disabled'}[/bold cyan] (from config)."
1453
+ )
1454
+ return config_partial
1455
+ else:
1456
+ console.print(
1457
+ "[dim][blue]Partial staking[/blue]: "
1458
+ + f"[bold cyan]{'enabled' if allow_partial_stake else 'disabled'}[/bold cyan] "
1459
+ + "by default. Set this using "
1460
+ + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1461
+ + "or "
1462
+ + "[dark_sea_green3 italic]`--partial/--no-partial`[/dark_sea_green3 italic] flag[/dim]"
1463
+ )
1464
+ return False
1157
1465
 
1158
1466
  def wallet_ask(
1159
1467
  self,
@@ -1173,39 +1481,16 @@ class CLIManager:
1173
1481
  :return: created Wallet object
1174
1482
  """
1175
1483
  # Prompt for missing attributes specified in ask_for
1176
-
1177
- if wallet_path:
1178
- if wallet_path == "default":
1179
- wallet_path = defaults.wallet.path
1180
-
1181
- elif self.config.get("wallet_path"):
1182
- wallet_path = self.config.get("wallet_path")
1183
- console.print(
1184
- f"Using the wallet path from config:[bold magenta] {wallet_path}"
1185
- )
1186
-
1187
- if WO.PATH in ask_for and not wallet_path:
1188
- wallet_path = Prompt.ask(
1189
- "Enter the [blue]wallet path[/blue]"
1190
- + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-path`)[/dark_sea_green3 italic]",
1191
- default=defaults.wallet.path,
1192
- )
1193
- if wallet_path:
1194
- wallet_path = os.path.expanduser(wallet_path)
1195
- else:
1196
- wallet_path = os.path.expanduser(defaults.wallet.path)
1197
- console.print(f"Using default wallet path: ({defaults.wallet.path})")
1198
-
1199
1484
  if WO.NAME in ask_for and not wallet_name:
1200
1485
  if self.config.get("wallet_name"):
1201
1486
  wallet_name = self.config.get("wallet_name")
1202
1487
  console.print(
1203
- f"Using the wallet name from config:[bold cyan] {wallet_name}"
1488
+ f"Using the [blue]wallet name[/blue] from config:[bold cyan] {wallet_name}"
1204
1489
  )
1205
1490
  else:
1206
1491
  wallet_name = Prompt.ask(
1207
1492
  "Enter the [blue]wallet name[/blue]"
1208
- + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-name`)[/dark_sea_green3 italic]",
1493
+ + f" [{COLOR_PALETTE['GENERAL']['HINT']} italic](Hint: You can set this with `btcli config set --wallet-name`)",
1209
1494
  default=defaults.wallet.name,
1210
1495
  )
1211
1496
 
@@ -1213,7 +1498,7 @@ class CLIManager:
1213
1498
  if self.config.get("wallet_hotkey"):
1214
1499
  wallet_hotkey = self.config.get("wallet_hotkey")
1215
1500
  console.print(
1216
- f"Using the wallet hotkey from config:[bold cyan] {wallet_hotkey}"
1501
+ f"Using the [blue]wallet hotkey[/blue] from config:[bold cyan] {wallet_hotkey}"
1217
1502
  )
1218
1503
  else:
1219
1504
  wallet_hotkey = Prompt.ask(
@@ -1221,8 +1506,27 @@ class CLIManager:
1221
1506
  + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-hotkey`)[/dark_sea_green3 italic]",
1222
1507
  default=defaults.wallet.hotkey,
1223
1508
  )
1509
+ if wallet_path:
1510
+ if wallet_path == "default":
1511
+ wallet_path = defaults.wallet.path
1512
+
1513
+ elif self.config.get("wallet_path"):
1514
+ wallet_path = self.config.get("wallet_path")
1515
+ console.print(
1516
+ f"Using the [blue]wallet path[/blue] from config:[bold magenta] {wallet_path}"
1517
+ )
1518
+ else:
1519
+ wallet_path = defaults.wallet.path
1224
1520
 
1521
+ if WO.PATH in ask_for and not wallet_path:
1522
+ wallet_path = Prompt.ask(
1523
+ "Enter the [blue]wallet path[/blue]"
1524
+ + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-path`)[/dark_sea_green3 italic]",
1525
+ default=defaults.wallet.path,
1526
+ )
1225
1527
  # Create the Wallet object
1528
+ if wallet_path:
1529
+ wallet_path = os.path.expanduser(wallet_path)
1226
1530
  wallet = Wallet(name=wallet_name, path=wallet_path, hotkey=wallet_hotkey)
1227
1531
 
1228
1532
  # Validate the wallet if required
@@ -1252,7 +1556,7 @@ class CLIManager:
1252
1556
  """
1253
1557
  Displays all the wallets and their corresponding hotkeys that are located in the wallet path specified in the config.
1254
1558
 
1255
- The output display shows each wallet and its associated `ss58` addresses for the coldkey public key and any hotkeys. The output is presented in a hierarchical tree format, with each wallet as a root node and any associated hotkeys as child nodes. The `ss58` address is displayed for each coldkey and hotkey that is not encrypted and exists on the device.
1559
+ The output display shows each wallet and its associated `ss58` addresses for the coldkey public key and any hotkeys. The output is presented in a hierarchical tree format, with each wallet as a root node and any associated hotkeys as child nodes. The `ss58` address (or an `<ENCRYPTED>` marker, for encrypted hotkeys) is displayed for each coldkey and hotkey that exists on the device.
1256
1560
 
1257
1561
  Upon invocation, the command scans the wallet directory and prints a list of all the wallets, indicating whether the
1258
1562
  public keys are available (`?` denotes unavailable or encrypted keys).
@@ -1315,50 +1619,9 @@ class CLIManager:
1315
1619
 
1316
1620
  USAGE
1317
1621
 
1318
- The command offers various options to customize the output. Users can filter the displayed data by specific
1319
- netuid, sort by different criteria, and choose to include all the wallets in the user's wallet path location.
1320
- The output is presented in a tabular format with the following columns:
1321
-
1322
- - COLDKEY: The SS58 address of the coldkey.
1323
-
1324
- - HOTKEY: The SS58 address of the hotkey.
1325
-
1326
- - UID: Unique identifier of the neuron.
1327
-
1328
- - ACTIVE: Indicates if the neuron is active.
1329
-
1330
- - STAKE(τ): Amount of stake in the neuron, in TAO.
1331
-
1332
- - RANK: The rank of the neuron within the network.
1333
-
1334
- - TRUST: Trust score of the neuron.
1335
-
1336
- - CONSENSUS: Consensus score of the neuron.
1337
-
1338
- - INCENTIVE: Incentive score of the neuron.
1339
-
1340
- - DIVIDENDS: Dividends earned by the neuron.
1341
-
1342
- - EMISSION(p): Emission received by the neuron, expressed in rho.
1343
-
1344
- - VTRUST: Validator trust score of the neuron.
1345
-
1346
- - VPERMIT: Indicates if the neuron has a validator permit.
1347
-
1348
- - UPDATED: Time since last update.
1349
-
1350
- - AXON: IP address and port of the neuron.
1351
-
1352
- - HOTKEY_SS58: Human-readable representation of the hotkey.
1353
-
1354
-
1355
- # EXAMPLE:
1356
-
1357
1622
  [green]$[/green] btcli wallet overview
1358
1623
 
1359
- [green]$[/green] btcli wallet overview --all --sort-by stake --sort-order descending
1360
-
1361
- [green]$[/green] btcli wallet overview -in hk1,hk2 --sort-by stake
1624
+ [green]$[/green] btcli wallet overview --all
1362
1625
 
1363
1626
  [bold]NOTE[/bold]: This command is read-only and does not modify the blockchain state or account configuration.
1364
1627
  It provides a quick and comprehensive view of the user's network presence, making it useful for monitoring account status,
@@ -1408,6 +1671,7 @@ class CLIManager:
1408
1671
  include_hotkeys,
1409
1672
  exclude_hotkeys,
1410
1673
  netuids_filter=netuids,
1674
+ verbose=verbose,
1411
1675
  )
1412
1676
  )
1413
1677
 
@@ -1425,7 +1689,6 @@ class CLIManager:
1425
1689
  None,
1426
1690
  "--amount",
1427
1691
  "-a",
1428
- prompt=False,
1429
1692
  help="Amount (in TAO) to transfer.",
1430
1693
  ),
1431
1694
  transfer_all: bool = typer.Option(
@@ -1523,7 +1786,7 @@ class CLIManager:
1523
1786
  wallet_name,
1524
1787
  wallet_path,
1525
1788
  wallet_hotkey,
1526
- ask_for=[WO.NAME, WO.HOTKEY],
1789
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
1527
1790
  validate=WV.WALLET_AND_HOTKEY,
1528
1791
  )
1529
1792
  if not destination_hotkey_name:
@@ -1535,7 +1798,7 @@ class CLIManager:
1535
1798
  wallet_name,
1536
1799
  wallet_path,
1537
1800
  destination_hotkey_name,
1538
- ask_for=[WO.NAME, WO.HOTKEY],
1801
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
1539
1802
  validate=WV.WALLET_AND_HOTKEY,
1540
1803
  )
1541
1804
  self.initialize_chain(network)
@@ -1591,6 +1854,8 @@ class CLIManager:
1591
1854
 
1592
1855
  [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.
1593
1856
  """
1857
+ print_error("This command is disabled on the 'rao' network.")
1858
+ raise typer.Exit()
1594
1859
  self.verbosity_handler(quiet, verbose)
1595
1860
 
1596
1861
  if netuids:
@@ -1601,11 +1866,12 @@ class CLIManager:
1601
1866
  )
1602
1867
 
1603
1868
  # if all-wallets is entered, ask for path
1604
- ask_for = [WO.NAME] if not all_wallets else []
1869
+ ask_for = [WO.NAME, WO.PATH] if not all_wallets else [WO.PATH]
1605
1870
  validate = WV.WALLET if not all_wallets else WV.NONE
1606
1871
  wallet = self.wallet_ask(
1607
1872
  wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate
1608
1873
  )
1874
+
1609
1875
  self.initialize_chain(network)
1610
1876
  return self._run_command(
1611
1877
  wallets.inspect(
@@ -1691,7 +1957,7 @@ class CLIManager:
1691
1957
  wallet_name,
1692
1958
  wallet_path,
1693
1959
  wallet_hotkey,
1694
- ask_for=[WO.NAME],
1960
+ ask_for=[WO.NAME, WO.PATH],
1695
1961
  validate=WV.WALLET,
1696
1962
  )
1697
1963
  return self._run_command(
@@ -1750,7 +2016,8 @@ class CLIManager:
1750
2016
 
1751
2017
  if not wallet_name:
1752
2018
  wallet_name = Prompt.ask(
1753
- "Enter the name of the new wallet", default=defaults.wallet.name
2019
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
2020
+ default=defaults.wallet.name,
1754
2021
  )
1755
2022
 
1756
2023
  wallet = Wallet(wallet_name, wallet_hotkey, wallet_path)
@@ -1806,7 +2073,8 @@ class CLIManager:
1806
2073
 
1807
2074
  if not wallet_name:
1808
2075
  wallet_name = Prompt.ask(
1809
- "Enter the name of the new wallet", default=defaults.wallet.name
2076
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
2077
+ default=defaults.wallet.name,
1810
2078
  )
1811
2079
  wallet = Wallet(wallet_name, wallet_hotkey, wallet_path)
1812
2080
 
@@ -1898,6 +2166,7 @@ class CLIManager:
1898
2166
  False, # Overriden to False
1899
2167
  help="Set to 'True' to protect the generated Bittensor key with a password.",
1900
2168
  ),
2169
+ uri: Optional[str] = Options.uri,
1901
2170
  overwrite: bool = Options.overwrite,
1902
2171
  quiet: bool = Options.quiet,
1903
2172
  verbose: bool = Options.verbose,
@@ -1920,12 +2189,14 @@ class CLIManager:
1920
2189
 
1921
2190
  if not wallet_name:
1922
2191
  wallet_name = Prompt.ask(
1923
- "Enter the wallet name", default=defaults.wallet.name
2192
+ f"Enter the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]wallet name",
2193
+ default=defaults.wallet.name,
1924
2194
  )
1925
2195
 
1926
2196
  if not wallet_hotkey:
1927
2197
  wallet_hotkey = Prompt.ask(
1928
- "Enter the name of the new hotkey", default=defaults.wallet.hotkey
2198
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey",
2199
+ default=defaults.wallet.hotkey,
1929
2200
  )
1930
2201
 
1931
2202
  wallet = self.wallet_ask(
@@ -1935,9 +2206,10 @@ class CLIManager:
1935
2206
  ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
1936
2207
  validate=WV.WALLET,
1937
2208
  )
1938
- n_words = get_n_words(n_words)
2209
+ if not uri:
2210
+ n_words = get_n_words(n_words)
1939
2211
  return self._run_command(
1940
- wallets.new_hotkey(wallet, n_words, use_password, overwrite)
2212
+ wallets.new_hotkey(wallet, n_words, use_password, uri, overwrite)
1941
2213
  )
1942
2214
 
1943
2215
  def wallet_new_coldkey(
@@ -1952,6 +2224,7 @@ class CLIManager:
1952
2224
  help="The number of words used in the mnemonic. Options: [12, 15, 18, 21, 24]",
1953
2225
  ),
1954
2226
  use_password: Optional[bool] = Options.use_password,
2227
+ uri: Optional[str] = Options.uri,
1955
2228
  overwrite: bool = Options.overwrite,
1956
2229
  quiet: bool = Options.quiet,
1957
2230
  verbose: bool = Options.verbose,
@@ -1978,7 +2251,8 @@ class CLIManager:
1978
2251
 
1979
2252
  if not wallet_name:
1980
2253
  wallet_name = Prompt.ask(
1981
- "Enter the name of the new wallet", default=defaults.wallet.name
2254
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
2255
+ default=defaults.wallet.name,
1982
2256
  )
1983
2257
 
1984
2258
  wallet = self.wallet_ask(
@@ -1988,9 +2262,10 @@ class CLIManager:
1988
2262
  ask_for=[WO.NAME, WO.PATH],
1989
2263
  validate=WV.NONE,
1990
2264
  )
1991
- n_words = get_n_words(n_words)
2265
+ if not uri:
2266
+ n_words = get_n_words(n_words)
1992
2267
  return self._run_command(
1993
- wallets.new_coldkey(wallet, n_words, use_password, overwrite)
2268
+ wallets.new_coldkey(wallet, n_words, use_password, uri, overwrite)
1994
2269
  )
1995
2270
 
1996
2271
  def wallet_check_ck_swap(
@@ -2025,6 +2300,7 @@ class CLIManager:
2025
2300
  wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2026
2301
  n_words: Optional[int] = None,
2027
2302
  use_password: bool = Options.use_password,
2303
+ uri: Optional[str] = Options.uri,
2028
2304
  overwrite: bool = Options.overwrite,
2029
2305
  quiet: bool = Options.quiet,
2030
2306
  verbose: bool = Options.verbose,
@@ -2049,12 +2325,13 @@ class CLIManager:
2049
2325
 
2050
2326
  if not wallet_name:
2051
2327
  wallet_name = Prompt.ask(
2052
- "Enter the name of the new wallet (coldkey)",
2328
+ f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)",
2053
2329
  default=defaults.wallet.name,
2054
2330
  )
2055
2331
  if not wallet_hotkey:
2056
2332
  wallet_hotkey = Prompt.ask(
2057
- "Enter the the name of the new hotkey", default=defaults.wallet.hotkey
2333
+ f"Enter the the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey",
2334
+ default=defaults.wallet.hotkey,
2058
2335
  )
2059
2336
 
2060
2337
  self.verbosity_handler(quiet, verbose)
@@ -2065,14 +2342,10 @@ class CLIManager:
2065
2342
  ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
2066
2343
  validate=WV.NONE,
2067
2344
  )
2068
- n_words = get_n_words(n_words)
2345
+ if not uri:
2346
+ n_words = get_n_words(n_words)
2069
2347
  return self._run_command(
2070
- wallets.wallet_create(
2071
- wallet,
2072
- n_words,
2073
- use_password,
2074
- overwrite,
2075
- )
2348
+ wallets.wallet_create(wallet, n_words, use_password, uri, overwrite)
2076
2349
  )
2077
2350
 
2078
2351
  def wallet_balance(
@@ -2116,8 +2389,18 @@ class CLIManager:
2116
2389
 
2117
2390
  """
2118
2391
  self.verbosity_handler(quiet, verbose)
2119
-
2120
- if ss58_addresses:
2392
+ wallet = None
2393
+ if all_balances:
2394
+ ask_for = [WO.PATH]
2395
+ validate = WV.NONE
2396
+ wallet = self.wallet_ask(
2397
+ wallet_name,
2398
+ wallet_path,
2399
+ wallet_hotkey,
2400
+ ask_for=ask_for,
2401
+ validate=validate,
2402
+ )
2403
+ elif ss58_addresses:
2121
2404
  valid_ss58s = [
2122
2405
  ss58 for ss58 in set(ss58_addresses) if is_valid_ss58_address(ss58)
2123
2406
  ]
@@ -2127,20 +2410,45 @@ class CLIManager:
2127
2410
  print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.")
2128
2411
 
2129
2412
  if valid_ss58s:
2130
- wallet = None
2131
2413
  ss58_addresses = valid_ss58s
2132
2414
  else:
2133
2415
  raise typer.Exit()
2134
2416
  else:
2135
- ask_for = [] if all_balances else [WO.NAME]
2136
- validate = WV.NONE if all_balances else WV.WALLET
2137
- wallet = self.wallet_ask(
2138
- wallet_name,
2139
- wallet_path,
2140
- wallet_hotkey,
2141
- ask_for=ask_for,
2142
- validate=validate,
2143
- )
2417
+ if wallet_name:
2418
+ coldkey_or_ss58 = wallet_name
2419
+ else:
2420
+ coldkey_or_ss58 = Prompt.ask(
2421
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 addresses[/blue] (comma-separated)",
2422
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2423
+ )
2424
+
2425
+ # Split and validate ss58 addresses
2426
+ coldkey_or_ss58_list = [x.strip() for x in coldkey_or_ss58.split(",")]
2427
+ if any(is_valid_ss58_address(x) for x in coldkey_or_ss58_list):
2428
+ valid_ss58s = [
2429
+ ss58 for ss58 in coldkey_or_ss58_list if is_valid_ss58_address(ss58)
2430
+ ]
2431
+ invalid_ss58s = set(coldkey_or_ss58_list) - set(valid_ss58s)
2432
+ for invalid_ss58 in invalid_ss58s:
2433
+ print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.")
2434
+
2435
+ if valid_ss58s:
2436
+ ss58_addresses = valid_ss58s
2437
+ else:
2438
+ raise typer.Exit()
2439
+ else:
2440
+ wallet_name = (
2441
+ coldkey_or_ss58_list[0] if coldkey_or_ss58_list else wallet_name
2442
+ )
2443
+ ask_for = [WO.NAME, WO.PATH]
2444
+ validate = WV.WALLET
2445
+ wallet = self.wallet_ask(
2446
+ wallet_name,
2447
+ wallet_path,
2448
+ wallet_hotkey,
2449
+ ask_for=ask_for,
2450
+ validate=validate,
2451
+ )
2144
2452
  subtensor = self.initialize_chain(network)
2145
2453
  return self._run_command(
2146
2454
  wallets.wallet_balance(wallet, subtensor, all_balances, ss58_addresses)
@@ -2166,19 +2474,23 @@ class CLIManager:
2166
2474
  [green]$[/green] btcli wallet history
2167
2475
 
2168
2476
  """
2477
+ # TODO: Fetch effective network and redirect users accordingly - this only works on finney
2478
+ # no_use_config_str = "Using the network [dark_orange]finney[/dark_orange] and ignoring network/chain configs"
2169
2479
 
2170
- no_use_config_str = "Using the network [dark_orange]finney[/dark_orange] and ignoring network/chain configs"
2480
+ # if self.config.get("network"):
2481
+ # if self.config.get("network") != "finney":
2482
+ # console.print(no_use_config_str)
2171
2483
 
2172
- if self.config.get("network"):
2173
- if self.config.get("network") != "finney":
2174
- console.print(no_use_config_str)
2484
+ # For Rao games
2485
+ print_error("This command is disabled on the 'rao' network.")
2486
+ raise typer.Exit()
2175
2487
 
2176
2488
  self.verbosity_handler(quiet, verbose)
2177
2489
  wallet = self.wallet_ask(
2178
2490
  wallet_name,
2179
2491
  wallet_path,
2180
2492
  wallet_hotkey,
2181
- ask_for=[WO.NAME],
2493
+ ask_for=[WO.NAME, WO.PATH],
2182
2494
  validate=WV.WALLET,
2183
2495
  )
2184
2496
  return self._run_command(wallets.wallet_history(wallet))
@@ -2189,69 +2501,42 @@ class CLIManager:
2189
2501
  wallet_path: Optional[str] = Options.wallet_path,
2190
2502
  wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2191
2503
  network: Optional[list[str]] = Options.network,
2192
- display_name: str = typer.Option(
2504
+ name: str = typer.Option(
2193
2505
  "",
2194
- "--display-name",
2195
- "--display",
2506
+ "--name",
2196
2507
  help="The display name for the identity.",
2197
2508
  ),
2198
- legal_name: str = typer.Option(
2199
- "",
2200
- "--legal-name",
2201
- "--legal",
2202
- help="The legal name for the identity.",
2203
- ),
2204
2509
  web_url: str = typer.Option(
2205
2510
  "",
2206
2511
  "--web-url",
2207
2512
  "--web",
2208
2513
  help="The web URL for the identity.",
2209
2514
  ),
2210
- riot_handle: str = typer.Option(
2211
- "",
2212
- "--riot-handle",
2213
- "--riot",
2214
- help="The Riot handle for the identity.",
2215
- ),
2216
- email: str = typer.Option(
2217
- "",
2218
- help="The email address for the identity.",
2219
- ),
2220
- pgp_fingerprint: str = typer.Option(
2221
- "",
2222
- "--pgp-fingerprint",
2223
- "--pgp",
2224
- help="The PGP fingerprint for the identity.",
2225
- ),
2226
2515
  image_url: str = typer.Option(
2227
2516
  "",
2228
2517
  "--image-url",
2229
2518
  "--image",
2230
2519
  help="The image URL for the identity.",
2231
2520
  ),
2232
- info_: str = typer.Option(
2521
+ discord: str = typer.Option(
2233
2522
  "",
2234
- "--info",
2235
- "-i",
2236
- help="The info for the identity.",
2523
+ "--discord",
2524
+ help="The Discord handle for the identity.",
2237
2525
  ),
2238
- twitter_url: str = typer.Option(
2526
+ description: str = typer.Option(
2239
2527
  "",
2240
- "-x",
2241
- "-𝕏",
2242
- "--twitter-url",
2243
- "--twitter",
2244
- help="The 𝕏 (Twitter) URL for the identity.",
2528
+ "--description",
2529
+ help="The description for the identity.",
2245
2530
  ),
2246
- validator_id: Optional[bool] = typer.Option(
2247
- None,
2248
- "--validator/--not-validator",
2249
- help="Are you updating a validator hotkey identity?",
2531
+ additional: str = typer.Option(
2532
+ "",
2533
+ "--additional",
2534
+ help="Additional details for the identity.",
2250
2535
  ),
2251
- subnet_netuid: Optional[int] = typer.Option(
2252
- None,
2253
- "--netuid",
2254
- help="Netuid if you are updating identity of a subnet owner",
2536
+ github_repo: str = typer.Option(
2537
+ "",
2538
+ "--github",
2539
+ help="The GitHub repository for the identity.",
2255
2540
  ),
2256
2541
  quiet: bool = Options.quiet,
2257
2542
  verbose: bool = Options.verbose,
@@ -2279,94 +2564,67 @@ class CLIManager:
2279
2564
  wallet_name,
2280
2565
  wallet_path,
2281
2566
  wallet_hotkey,
2282
- ask_for=[WO.HOTKEY, WO.NAME],
2283
- validate=WV.WALLET_AND_HOTKEY,
2567
+ ask_for=[WO.NAME],
2568
+ validate=WV.WALLET,
2284
2569
  )
2285
2570
 
2286
- if not any(
2287
- [
2288
- display_name,
2289
- legal_name,
2290
- web_url,
2291
- riot_handle,
2292
- email,
2293
- pgp_fingerprint,
2294
- image_url,
2295
- info_,
2296
- twitter_url,
2297
- ]
2298
- ):
2299
- console.print(
2300
- "[yellow]All fields are optional. Press Enter to skip a field.[/yellow]"
2301
- )
2302
- text_rejection = partial(
2303
- retry_prompt,
2304
- rejection=lambda x: sys.getsizeof(x) > 113,
2305
- rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.",
2306
- )
2571
+ current_identity = self._run_command(
2572
+ wallets.get_id(
2573
+ self.initialize_chain(network),
2574
+ wallet.coldkeypub.ss58_address,
2575
+ "Current on-chain identity",
2576
+ ),
2577
+ exit_early=False,
2578
+ )
2307
2579
 
2308
- def pgp_check(s: str):
2309
- try:
2310
- if s.startswith("0x"):
2311
- s = s[2:] # Strip '0x'
2312
- pgp_fingerprint_encoded = binascii.unhexlify(s.replace(" ", ""))
2313
- except Exception:
2314
- return True
2315
- return True if len(pgp_fingerprint_encoded) != 20 else False
2316
-
2317
- display_name = display_name or text_rejection("Display name")
2318
- legal_name = legal_name or text_rejection("Legal name")
2319
- web_url = web_url or text_rejection("Web URL")
2320
- riot_handle = riot_handle or text_rejection("Riot handle")
2321
- email = email or text_rejection("Email address")
2322
- pgp_fingerprint = pgp_fingerprint or retry_prompt(
2323
- "PGP fingerprint (Eg: A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678)",
2324
- lambda s: False if not s else pgp_check(s),
2325
- "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes.",
2326
- )
2327
- image_url = image_url or text_rejection("Image URL")
2328
- info_ = info_ or text_rejection("Enter info")
2329
- twitter_url = twitter_url or text_rejection("𝕏 (Twitter) URL")
2330
-
2331
- validator_id = validator_id or Confirm.ask(
2332
- "Are you updating a [bold blue]validator hotkey[/bold blue] identity or a [bold blue]subnet "
2333
- "owner[/bold blue] identity?\n"
2334
- "Enter [bold green]Y[/bold green] for [bold]validator hotkey[/bold] or [bold red]N[/bold red] for "
2335
- "[bold]subnet owner[/bold]",
2336
- show_choices=True,
2337
- )
2580
+ if prompt:
2581
+ if not Confirm.ask(
2582
+ "Cost to register an [blue]Identity[/blue] is [blue]0.1 TAO[/blue],"
2583
+ " are you sure you wish to continue?"
2584
+ ):
2585
+ console.print(":cross_mark: Aborted!")
2586
+ raise typer.Exit()
2338
2587
 
2339
- if validator_id is False:
2340
- subnet_netuid = IntPrompt.ask("Enter the netuid of the subnet you own")
2588
+ identity = prompt_for_identity(
2589
+ current_identity,
2590
+ name,
2591
+ web_url,
2592
+ image_url,
2593
+ discord,
2594
+ description,
2595
+ additional,
2596
+ github_repo,
2597
+ )
2341
2598
 
2342
2599
  return self._run_command(
2343
2600
  wallets.set_id(
2344
2601
  wallet,
2345
2602
  self.initialize_chain(network),
2346
- display_name,
2347
- legal_name,
2348
- web_url,
2349
- pgp_fingerprint,
2350
- riot_handle,
2351
- email,
2352
- image_url,
2353
- twitter_url,
2354
- info_,
2355
- validator_id,
2356
- subnet_netuid,
2603
+ identity["name"],
2604
+ identity["url"],
2605
+ identity["image"],
2606
+ identity["discord"],
2607
+ identity["description"],
2608
+ identity["additional"],
2609
+ identity["github_repo"],
2357
2610
  prompt,
2358
2611
  )
2359
2612
  )
2360
2613
 
2361
2614
  def wallet_get_id(
2362
2615
  self,
2363
- target_ss58_address: str = typer.Option(
2364
- None,
2616
+ wallet_name: Optional[str] = Options.wallet_name,
2617
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2618
+ wallet_path: Optional[str] = Options.wallet_path,
2619
+ coldkey_ss58=typer.Option(
2620
+ None,
2621
+ "--ss58",
2622
+ "--coldkey_ss58",
2623
+ "--coldkey.ss58_address",
2624
+ "--coldkey.ss58",
2365
2625
  "--key",
2366
2626
  "-k",
2367
- "--ss58",
2368
- help="The coldkey or hotkey ss58 address to query.",
2369
- prompt=True,
2627
+ help="Coldkey address of the wallet",
2370
2628
  ),
2371
2629
  network: Optional[list[str]] = Options.network,
2372
2630
  quiet: bool = Options.quiet,
@@ -2389,13 +2647,28 @@ class CLIManager:
2389
2647
 
2390
2648
  [bold]Note[/bold]: This command is primarily used for informational purposes and has no side effects on the blockchain network state.
2391
2649
  """
2392
- if not is_valid_ss58_address(target_ss58_address):
2393
- print_error("You have entered an incorrect ss58 address. Please try again")
2394
- raise typer.Exit()
2650
+ wallet = None
2651
+ if coldkey_ss58:
2652
+ if not is_valid_ss58_address(coldkey_ss58):
2653
+ print_error("You entered an invalid ss58 address")
2654
+ raise typer.Exit()
2655
+ else:
2656
+ coldkey_or_ss58 = Prompt.ask(
2657
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]",
2658
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2659
+ )
2660
+ if is_valid_ss58_address(coldkey_or_ss58):
2661
+ coldkey_ss58 = coldkey_or_ss58
2662
+ else:
2663
+ wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name
2664
+ wallet = self.wallet_ask(
2665
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2666
+ )
2667
+ coldkey_ss58 = wallet.coldkeypub.ss58_address
2395
2668
 
2396
2669
  self.verbosity_handler(quiet, verbose)
2397
2670
  return self._run_command(
2398
- wallets.get_id(self.initialize_chain(network), target_ss58_address)
2671
+ wallets.get_id(self.initialize_chain(network), coldkey_ss58)
2399
2672
  )
2400
2673
 
2401
2674
  def wallet_sign(
@@ -2429,934 +2702,493 @@ class CLIManager:
2429
2702
  self.verbosity_handler(quiet, verbose)
2430
2703
  if use_hotkey is None:
2431
2704
  use_hotkey = Confirm.ask(
2432
- "Would you like to sign the transaction using your [red]hotkey[/red]?"
2433
- "\n[Type [red]y[/red] for [red]hotkey[/red] and [blue]n[/blue] for [blue]coldkey[/blue]] "
2434
- "(default is [blue]coldkey[/blue])",
2705
+ f"Would you like to sign the transaction using your [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]?"
2706
+ f"\n[Type [{COLOR_PALETTE['GENERAL']['HOTKEY']}]y[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] for [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]"
2707
+ 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']}])",
2435
2708
  default=False,
2436
2709
  )
2437
2710
 
2438
- ask_for = [WO.HOTKEY, WO.NAME] if use_hotkey else [WO.NAME]
2711
+ ask_for = [WO.HOTKEY, WO.PATH, WO.NAME] if use_hotkey else [WO.NAME, WO.PATH]
2439
2712
  validate = WV.WALLET_AND_HOTKEY if use_hotkey else WV.WALLET
2440
2713
 
2441
2714
  wallet = self.wallet_ask(
2442
2715
  wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate
2443
2716
  )
2444
2717
  if not message:
2445
- message = typer.prompt("Enter the message to encode and sign")
2718
+ message = Prompt.ask("Enter the [blue]message[/blue] to encode and sign")
2446
2719
 
2447
2720
  return self._run_command(wallets.sign(wallet, message, use_hotkey))
2448
2721
 
2449
- def root_list(
2722
+ def stake_list(
2450
2723
  self,
2451
2724
  network: Optional[list[str]] = Options.network,
2725
+ wallet_name: Optional[str] = Options.wallet_name,
2726
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2727
+ wallet_path: Optional[str] = Options.wallet_path,
2728
+ coldkey_ss58=typer.Option(
2729
+ None,
2730
+ "--ss58",
2731
+ "--coldkey_ss58",
2732
+ "--coldkey.ss58_address",
2733
+ "--coldkey.ss58",
2734
+ help="Coldkey address of the wallet",
2735
+ ),
2736
+ live: bool = Options.live,
2452
2737
  quiet: bool = Options.quiet,
2453
2738
  verbose: bool = Options.verbose,
2739
+ no_prompt: bool = Options.prompt,
2740
+ # TODO add: all-wallets, reuse_last, html_output
2454
2741
  ):
2455
2742
  """
2456
- Show the neurons (root network validators) in the root network (netuid = 0).
2743
+ Display detailed stake information for a wallet across all subnets.
2457
2744
 
2458
- USAGE
2745
+ Shows stake allocations, exchange rates, and emissions for each hotkey.
2459
2746
 
2460
- 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).
2747
+ [bold]Common Examples:[/bold]
2461
2748
 
2462
- 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.
2749
+ 1. Basic stake overview:
2750
+ [green]$[/green] btcli stake list --wallet.name my_wallet
2463
2751
 
2464
- EXAMPLE
2752
+ 2. Live updating view with refresh:
2753
+ [green]$[/green] btcli stake list --wallet.name my_wallet --live
2465
2754
 
2466
- [green]$[/green] btcli root list
2755
+ 3. View specific coldkey by address:
2756
+ [green]$[/green] btcli stake list --ss58 5Dk...X3q
2757
+
2758
+ 4. Verbose output with full values:
2759
+ [green]$[/green] btcli stake list --wallet.name my_wallet --verbose
2467
2760
  """
2468
2761
  self.verbosity_handler(quiet, verbose)
2762
+
2763
+ wallet = None
2764
+ if coldkey_ss58:
2765
+ if not is_valid_ss58_address(coldkey_ss58):
2766
+ print_error("You entered an invalid ss58 address")
2767
+ raise typer.Exit()
2768
+ else:
2769
+ if wallet_name:
2770
+ coldkey_or_ss58 = wallet_name
2771
+ else:
2772
+ coldkey_or_ss58 = Prompt.ask(
2773
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]",
2774
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2775
+ )
2776
+ if is_valid_ss58_address(coldkey_or_ss58):
2777
+ coldkey_ss58 = coldkey_or_ss58
2778
+ else:
2779
+ wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name
2780
+ wallet = self.wallet_ask(
2781
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2782
+ )
2783
+
2469
2784
  return self._run_command(
2470
- root.root_list(subtensor=self.initialize_chain(network))
2785
+ list_stake.stake_list(
2786
+ wallet,
2787
+ coldkey_ss58,
2788
+ self.initialize_chain(network),
2789
+ live,
2790
+ verbose,
2791
+ no_prompt,
2792
+ )
2471
2793
  )
2472
2794
 
2473
- def root_set_weights(
2795
+ def stake_add(
2474
2796
  self,
2475
- network: Optional[list[str]] = Options.network,
2797
+ stake_all: bool = typer.Option(
2798
+ False,
2799
+ "--all-tokens",
2800
+ "--all",
2801
+ "-a",
2802
+ help="When set, the command stakes all the available TAO from the coldkey.",
2803
+ ),
2804
+ amount: float = typer.Option(
2805
+ 0.0, "--amount", help="The amount of TAO to stake"
2806
+ ),
2807
+ include_hotkeys: str = typer.Option(
2808
+ "",
2809
+ "--include-hotkeys",
2810
+ "-in",
2811
+ "--hotkey-ss58-address",
2812
+ help="Specifies hotkeys by name or ss58 address to stake to. For example, `-in hk1,hk2`",
2813
+ ),
2814
+ exclude_hotkeys: str = typer.Option(
2815
+ "",
2816
+ "--exclude-hotkeys",
2817
+ "-ex",
2818
+ help="Specifies hotkeys by name or ss58 address to not to stake to (use this option only with `--all-hotkeys`)"
2819
+ " i.e. `--all-hotkeys -ex hk3,hk4`",
2820
+ ),
2821
+ all_hotkeys: bool = typer.Option(
2822
+ False,
2823
+ help="When set, this command stakes to all hotkeys associated with the wallet. Do not use if specifying "
2824
+ "hotkeys in `--include-hotkeys`.",
2825
+ ),
2826
+ netuid: Optional[int] = Options.netuid_not_req,
2827
+ all_netuids: bool = Options.all_netuids,
2476
2828
  wallet_name: str = Options.wallet_name,
2477
2829
  wallet_path: str = Options.wallet_path,
2478
2830
  wallet_hotkey: str = Options.wallet_hotkey,
2479
- netuids=typer.Option(
2480
- None,
2481
- "--netuids",
2482
- "--netuid",
2483
- "-n",
2484
- help="Set the netuid(s) to set weights to. Separate multiple netuids with a comma, for example: `-n 0,1,2`.",
2485
- ),
2486
- weights: str = Options.weights,
2831
+ network: Optional[list[str]] = Options.network,
2832
+ rate_tolerance: Optional[float] = Options.rate_tolerance,
2833
+ safe_staking: Optional[bool] = Options.safe_staking,
2834
+ allow_partial_stake: Optional[bool] = Options.allow_partial_stake,
2487
2835
  prompt: bool = Options.prompt,
2488
2836
  quiet: bool = Options.quiet,
2489
2837
  verbose: bool = Options.verbose,
2490
2838
  ):
2491
2839
  """
2492
- Set the weights for different subnets, by setting them in the root network.
2840
+ Stake TAO to one or more hotkeys on specific netuids with your coldkey.
2493
2841
 
2494
- 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.
2842
+ Stake is always added through your coldkey's free balance. For stake movement, please see `[green]$[/green] btcli stake move` command.
2495
2843
 
2496
- 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.
2844
+ [bold]Common Examples:[/bold]
2497
2845
 
2498
- EXAMPLE
2846
+ 1. Interactive staking (guided prompts):
2847
+ [green]$[/green] btcli stake add
2848
+
2849
+ 2. Safe staking with rate tolerance of 10% with partial transaction disabled:
2850
+ [green]$[/green] btcli stake add --amount 100 --netuid 1 --safe --tolerance 0.1 --no-partial
2851
+
2852
+ 3. Allow partial stake if rates change with tolerance of 10%:
2853
+ [green]$[/green] btcli stake add --amount 300 --safe --partial --tolerance 0.1
2499
2854
 
2500
- With no spaces between the passed values:
2855
+ 4. Unsafe staking with no rate protection:
2856
+ [green]$[/green] btcli stake add --amount 300 --netuid 1 --unsafe
2501
2857
 
2502
- [green]$[/green] btcli root set-weights --netuids 1,2 --weights 0.2,0.3
2858
+ 5. Stake to multiple hotkeys:
2859
+ [green]$[/green] btcli stake add --amount 200 --include-hotkeys hk_ss58_1,hk_ss58_2,hk_ss58_3
2503
2860
 
2504
- or
2861
+ 6. Stake all balance to a subnet:
2862
+ [green]$[/green] btcli stake add --all --netuid 3
2505
2863
 
2506
- Include double quotes to include spaces between the passed values:
2864
+ [bold]Safe Staking Parameters:[/bold]
2865
+ • [blue]--safe[/blue]: Enables rate tolerance checks
2866
+ • [blue]--tolerance[/blue]: Maximum % rate change allowed (0.05 = 5%)
2867
+ • [blue]--partial[/blue]: Complete partial stake if rates exceed tolerance
2507
2868
 
2508
- [green]$[/green] btcli root set-weights --netuids "1, 2" --weights "0.2, 0.3"
2509
2869
  """
2510
2870
  self.verbosity_handler(quiet, verbose)
2871
+ safe_staking = self.ask_safe_staking(safe_staking)
2872
+ if safe_staking:
2873
+ rate_tolerance = self.ask_rate_tolerance(rate_tolerance)
2874
+ allow_partial_stake = self.ask_partial_stake(allow_partial_stake)
2875
+ console.print("\n")
2876
+ netuid = get_optional_netuid(netuid, all_netuids)
2511
2877
 
2512
- if netuids:
2513
- netuids = parse_to_list(
2514
- netuids,
2515
- int,
2516
- "Netuids must be a comma-separated list of ints, e.g., `--netuid 1,2,3,4`.",
2878
+ if stake_all and amount:
2879
+ print_error(
2880
+ "Cannot specify an amount and 'stake-all'. Choose one or the other."
2517
2881
  )
2518
- else:
2519
- netuids = list_prompt(netuids, int, "Enter netuids (e.g: 1, 4, 6)")
2882
+ raise typer.Exit()
2520
2883
 
2521
- if weights:
2522
- weights = parse_to_list(
2523
- weights,
2524
- float,
2525
- "Weights must be a comma-separated list of floats, e.g., `--weights 0.3,0.4,0.3`.",
2526
- )
2527
- else:
2528
- weights = list_prompt(
2529
- weights, float, "Enter weights (e.g. 0.02, 0.03, 0.01)"
2530
- )
2884
+ if stake_all and not amount:
2885
+ if not Confirm.ask("Stake all the available TAO tokens?", default=False):
2886
+ raise typer.Exit()
2531
2887
 
2532
- if len(netuids) != len(weights):
2533
- raise typer.BadParameter(
2534
- "The number of netuids and weights must be the same."
2888
+ if all_hotkeys and include_hotkeys:
2889
+ print_error(
2890
+ "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag"
2891
+ "should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`."
2535
2892
  )
2893
+ raise typer.Exit()
2536
2894
 
2537
- wallet = self.wallet_ask(
2538
- wallet_name,
2539
- wallet_path,
2540
- wallet_hotkey,
2541
- ask_for=[WO.HOTKEY, WO.NAME],
2542
- validate=WV.WALLET_AND_HOTKEY,
2543
- )
2544
- self._run_command(
2545
- root.set_weights(
2546
- wallet, self.initialize_chain(network), netuids, weights, prompt
2895
+ if include_hotkeys and exclude_hotkeys:
2896
+ print_error(
2897
+ "You have specified options for both including and excluding hotkeys. Select one or the other."
2547
2898
  )
2548
- )
2899
+ raise typer.Exit()
2549
2900
 
2550
- def root_get_weights(
2551
- self,
2552
- network: Optional[list[str]] = Options.network,
2553
- limit_min_col: Optional[int] = typer.Option(
2554
- None,
2555
- "--limit-min-col",
2556
- "--min",
2557
- help="Limit the left display of the table to this column.",
2558
- ),
2559
- limit_max_col: Optional[int] = typer.Option(
2560
- None,
2561
- "--limit-max-col",
2562
- "--max",
2563
- help="Limit the right display of the table to this column.",
2564
- ),
2565
- reuse_last: bool = Options.reuse_last,
2566
- html_output: bool = Options.html_output,
2567
- quiet: bool = Options.quiet,
2568
- verbose: bool = Options.verbose,
2569
- ):
2570
- """
2571
- Shows a table listing the weights assigned to each subnet in the root network.
2901
+ if not wallet_hotkey and not all_hotkeys and not include_hotkeys:
2902
+ if not wallet_name:
2903
+ wallet_name = Prompt.ask(
2904
+ "Enter the [blue]wallet name[/blue]",
2905
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2906
+ )
2907
+ if netuid is not None:
2908
+ hotkey_or_ss58 = Prompt.ask(
2909
+ "Enter the [blue]wallet hotkey[/blue] name or [blue]ss58 address[/blue] to stake to [dim](or Press Enter to view delegates)[/dim]",
2910
+ )
2911
+ else:
2912
+ hotkey_or_ss58 = Prompt.ask(
2913
+ "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to",
2914
+ default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey,
2915
+ )
2572
2916
 
2573
- 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.
2917
+ if hotkey_or_ss58 == "":
2918
+ wallet = self.wallet_ask(
2919
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2920
+ )
2921
+ selected_hotkey = self._run_command(
2922
+ subnets.show(
2923
+ subtensor=self.initialize_chain(network),
2924
+ netuid=netuid,
2925
+ sort=False,
2926
+ max_rows=12,
2927
+ prompt=False,
2928
+ delegate_selection=True,
2929
+ ),
2930
+ exit_early=False,
2931
+ )
2932
+ if selected_hotkey is None:
2933
+ print_error("No delegate selected. Exiting.")
2934
+ raise typer.Exit()
2935
+ include_hotkeys = selected_hotkey
2936
+ elif is_valid_ss58_address(hotkey_or_ss58):
2937
+ wallet = self.wallet_ask(
2938
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2939
+ )
2940
+ include_hotkeys = hotkey_or_ss58
2941
+ else:
2942
+ wallet_hotkey = hotkey_or_ss58
2943
+ wallet = self.wallet_ask(
2944
+ wallet_name,
2945
+ wallet_path,
2946
+ wallet_hotkey,
2947
+ ask_for=[WO.NAME, WO.HOTKEY, WO.PATH],
2948
+ validate=WV.WALLET_AND_HOTKEY,
2949
+ )
2950
+ include_hotkeys = wallet.hotkey.ss58_address
2574
2951
 
2575
- EXAMPLE
2952
+ elif all_hotkeys or include_hotkeys or exclude_hotkeys:
2953
+ wallet = self.wallet_ask(
2954
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
2955
+ )
2956
+ else:
2957
+ wallet = self.wallet_ask(
2958
+ wallet_name,
2959
+ wallet_path,
2960
+ wallet_hotkey,
2961
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
2962
+ validate=WV.WALLET_AND_HOTKEY,
2963
+ )
2576
2964
 
2577
- [green]$[/green] btcli root get_weights
2578
- """
2579
- self.verbosity_handler(quiet, verbose)
2580
- if (reuse_last or html_output) and self.config.get("use_cache") is False:
2581
- err_console.print(
2582
- "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'."
2583
- "Change it to 'False' using `btcli config set`."
2965
+ if include_hotkeys:
2966
+ include_hotkeys = parse_to_list(
2967
+ include_hotkeys,
2968
+ str,
2969
+ "Hotkeys must be a comma-separated list of ss58s, e.g., `--include-hotkeys 5Grw....,5Grw....`.",
2970
+ is_ss58=True,
2584
2971
  )
2585
- raise typer.Exit()
2586
- if not reuse_last:
2587
- subtensor = self.initialize_chain(network)
2588
2972
  else:
2589
- subtensor = None
2973
+ include_hotkeys = []
2974
+
2975
+ if exclude_hotkeys:
2976
+ exclude_hotkeys = parse_to_list(
2977
+ exclude_hotkeys,
2978
+ str,
2979
+ "Hotkeys must be a comma-separated list of ss58s, e.g., `--exclude-hotkeys 5Grw....,5Grw....`.",
2980
+ is_ss58=True,
2981
+ )
2982
+ else:
2983
+ exclude_hotkeys = []
2984
+
2985
+ # TODO: Ask amount for each subnet explicitly if more than one
2986
+ if not stake_all and not amount:
2987
+ free_balance = self._run_command(
2988
+ wallets.wallet_balance(
2989
+ wallet, self.initialize_chain(network), False, None
2990
+ ),
2991
+ exit_early=False,
2992
+ )
2993
+ if free_balance == Balance.from_tao(0):
2994
+ print_error("You dont have any balance to stake.")
2995
+ raise typer.Exit()
2996
+ if netuid is not None:
2997
+ amount = FloatPrompt.ask(
2998
+ f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake (TAO τ)"
2999
+ )
3000
+ else:
3001
+ amount = FloatPrompt.ask(
3002
+ f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake to each netuid (TAO τ)"
3003
+ )
3004
+
3005
+ if amount <= 0:
3006
+ print_error(f"You entered an incorrect stake amount: {amount}")
3007
+ raise typer.Exit()
3008
+ if Balance.from_tao(amount) > free_balance:
3009
+ print_error(
3010
+ f"You dont have enough balance to stake. Current free Balance: {free_balance}."
3011
+ )
3012
+ raise typer.Exit()
3013
+
2590
3014
  return self._run_command(
2591
- root.get_weights(
2592
- subtensor,
2593
- limit_min_col,
2594
- limit_max_col,
2595
- reuse_last,
2596
- html_output,
2597
- not self.config.get("use_cache", True),
3015
+ add_stake.stake_add(
3016
+ wallet,
3017
+ self.initialize_chain(network),
3018
+ netuid,
3019
+ stake_all,
3020
+ amount,
3021
+ prompt,
3022
+ all_hotkeys,
3023
+ include_hotkeys,
3024
+ exclude_hotkeys,
3025
+ safe_staking,
3026
+ rate_tolerance,
3027
+ allow_partial_stake,
2598
3028
  )
2599
3029
  )
2600
3030
 
2601
- def root_boost(
3031
+ def stake_remove(
2602
3032
  self,
2603
3033
  network: Optional[list[str]] = Options.network,
2604
3034
  wallet_name: str = Options.wallet_name,
2605
- wallet_path: Optional[str] = Options.wallet_path,
2606
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2607
- netuid: int = Options.netuid,
3035
+ wallet_path: str = Options.wallet_path,
3036
+ wallet_hotkey: str = Options.wallet_hotkey,
3037
+ netuid: Optional[int] = Options.netuid_not_req,
3038
+ all_netuids: bool = Options.all_netuids,
3039
+ unstake_all: bool = typer.Option(
3040
+ False,
3041
+ "--unstake-all",
3042
+ "--all",
3043
+ hidden=True,
3044
+ help="When set, this command unstakes all staked TAO + Alpha from the all hotkeys.",
3045
+ ),
3046
+ unstake_all_alpha: bool = typer.Option(
3047
+ False,
3048
+ "--unstake-all-alpha",
3049
+ "--all-alpha",
3050
+ hidden=True,
3051
+ help="When set, this command unstakes all staked Alpha from the all hotkeys.",
3052
+ ),
2608
3053
  amount: float = typer.Option(
2609
- None,
2610
- "--amount",
2611
- "--increase",
2612
- "-a",
2613
- prompt="Enter the boost amount (added to existing weight)",
2614
- help="Amount (float) to boost (added to the existing weight), (e.g. 0.01)",
3054
+ 0.0, "--amount", "-a", help="The amount of TAO to unstake."
3055
+ ),
3056
+ hotkey_ss58_address: str = typer.Option(
3057
+ "",
3058
+ help="The ss58 address of the hotkey to unstake from.",
3059
+ ),
3060
+ include_hotkeys: str = typer.Option(
3061
+ "",
3062
+ "--include-hotkeys",
3063
+ "-in",
3064
+ help="Specifies the hotkeys by name or ss58 address to unstake from. For example, `-in hk1,hk2`",
3065
+ ),
3066
+ exclude_hotkeys: str = typer.Option(
3067
+ "",
3068
+ "--exclude-hotkeys",
3069
+ "-ex",
3070
+ help="Specifies the hotkeys by name or ss58 address not to unstake from (only use with `--all-hotkeys`)"
3071
+ " i.e. `--all-hotkeys -ex hk3,hk4`",
3072
+ ),
3073
+ all_hotkeys: bool = typer.Option(
3074
+ False,
3075
+ help="When set, this command unstakes from all the hotkeys associated with the wallet. Do not use if specifying "
3076
+ "hotkeys in `--include-hotkeys`.",
2615
3077
  ),
3078
+ rate_tolerance: Optional[float] = Options.rate_tolerance,
3079
+ safe_staking: Optional[bool] = Options.safe_staking,
3080
+ allow_partial_stake: Optional[bool] = Options.allow_partial_stake,
2616
3081
  prompt: bool = Options.prompt,
3082
+ interactive: bool = typer.Option(
3083
+ False,
3084
+ "--interactive",
3085
+ "-i",
3086
+ help="Enter interactive mode for unstaking.",
3087
+ ),
2617
3088
  quiet: bool = Options.quiet,
2618
3089
  verbose: bool = Options.verbose,
2619
3090
  ):
2620
3091
  """
2621
- Increase (boost) the weights for a specific subnet in the root network. Any amount provided will be added to the subnet's existing weight.
3092
+ Unstake TAO from one or more hotkeys and transfer them back to the user's coldkey wallet.
2622
3093
 
2623
- EXAMPLE
3094
+ This command is used to withdraw TAO or Alpha stake from different hotkeys.
2624
3095
 
2625
- [green]$[/green] btcli root boost --netuid 1 --increase 0.01
3096
+ [bold]Common Examples:[/bold]
3097
+
3098
+ 1. Interactive unstaking (guided prompts):
3099
+ [green]$[/green] btcli stake remove
3100
+
3101
+ 2. Safe unstaking with 10% rate tolerance:
3102
+ [green]$[/green] btcli stake remove --amount 100 --netuid 1 --safe --tolerance 0.1
3103
+
3104
+ 3. Allow partial unstake if rates change:
3105
+ [green]$[/green] btcli stake remove --amount 300 --safe --partial
3106
+
3107
+ 4. Unstake from multiple hotkeys:
3108
+ [green]$[/green] btcli stake remove --amount 200 --include-hotkeys hk1,hk2,hk3
3109
+
3110
+ 5. Unstake all from a hotkey:
3111
+ [green]$[/green] btcli stake remove --all
3112
+
3113
+ 6. Unstake all Alpha from a hotkey and stake to Root:
3114
+ [green]$[/green] btcli stake remove --all-alpha
3115
+
3116
+ [bold]Safe Staking Parameters:[/bold]
3117
+ • [blue]--safe[/blue]: Enables rate tolerance checks during unstaking
3118
+ • [blue]--tolerance[/blue]: Max allowed rate change (0.05 = 5%)
3119
+ • [blue]--partial[/blue]: Complete partial unstake if rates exceed tolerance
2626
3120
  """
2627
3121
  self.verbosity_handler(quiet, verbose)
2628
- wallet = self.wallet_ask(
2629
- wallet_name,
2630
- wallet_path,
2631
- wallet_hotkey,
2632
- ask_for=[WO.NAME, WO.HOTKEY],
2633
- validate=WV.WALLET_AND_HOTKEY,
2634
- )
2635
- return self._run_command(
2636
- root.set_boost(
2637
- wallet, self.initialize_chain(network), netuid, amount, prompt
2638
- )
2639
- )
2640
-
2641
- def root_slash(
2642
- self,
2643
- network: Optional[list[str]] = Options.network,
2644
- wallet_name: str = Options.wallet_name,
2645
- wallet_path: Optional[str] = Options.wallet_path,
2646
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2647
- netuid: int = Options.netuid,
2648
- amount: float = typer.Option(
2649
- None,
2650
- "--amount",
2651
- "--decrease",
2652
- "-a",
2653
- prompt="Enter the slash amount (subtracted from the existing weight)",
2654
- help="Amount (float) to slash (subtract from the existing weight), (e.g. 0.01)",
2655
- ),
2656
- prompt: bool = Options.prompt,
2657
- quiet: bool = Options.quiet,
2658
- verbose: bool = Options.verbose,
2659
- ):
2660
- """
2661
- Decrease (slash) the weights for a specific subnet in the root network. Any amount provided will be subtracted from the subnet's existing weight.
2662
-
2663
- EXAMPLE
2664
-
2665
- [green]$[/green] btcli root slash --netuid 1 --decrease 0.01
2666
-
2667
- """
2668
- self.verbosity_handler(quiet, verbose)
2669
- wallet = self.wallet_ask(
2670
- wallet_name,
2671
- wallet_path,
2672
- wallet_hotkey,
2673
- ask_for=[WO.NAME, WO.HOTKEY],
2674
- validate=WV.WALLET_AND_HOTKEY,
2675
- )
2676
- return self._run_command(
2677
- root.set_slash(
2678
- wallet, self.initialize_chain(network), netuid, amount, prompt
2679
- )
2680
- )
2681
-
2682
- def root_senate_vote(
2683
- self,
2684
- network: Optional[list[str]] = Options.network,
2685
- wallet_name: Optional[str] = Options.wallet_name,
2686
- wallet_path: Optional[str] = Options.wallet_path,
2687
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2688
- proposal: str = typer.Option(
2689
- None,
2690
- "--proposal",
2691
- "--proposal-hash",
2692
- prompt="Enter the proposal hash",
2693
- help="The hash of the proposal to vote on.",
2694
- ),
2695
- prompt: bool = Options.prompt,
2696
- quiet: bool = Options.quiet,
2697
- verbose: bool = Options.verbose,
2698
- vote: bool = typer.Option(
2699
- None,
2700
- "--vote-aye/--vote-nay",
2701
- prompt="Enter y to vote Aye, or enter n to vote Nay",
2702
- help="The vote casted on the proposal",
2703
- ),
2704
- ):
2705
- """
2706
- Cast a vote on an active proposal in Bittensor's governance protocol.
2707
-
2708
- 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.
2709
-
2710
- USAGE
2711
-
2712
- 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.
2713
-
2714
- EXAMPLE
2715
-
2716
- [green]$[/green] btcli root senate_vote --proposal <proposal_hash>
2717
- """
2718
- self.verbosity_handler(quiet, verbose)
2719
- wallet = self.wallet_ask(
2720
- wallet_name,
2721
- wallet_path,
2722
- wallet_hotkey,
2723
- ask_for=[WO.NAME, WO.HOTKEY],
2724
- validate=WV.WALLET_AND_HOTKEY,
2725
- )
2726
- return self._run_command(
2727
- root.senate_vote(
2728
- wallet, self.initialize_chain(network), proposal, vote, prompt
2729
- )
2730
- )
2731
-
2732
- def root_senate(
2733
- self,
2734
- network: Optional[list[str]] = Options.network,
2735
- quiet: bool = Options.quiet,
2736
- verbose: bool = Options.verbose,
2737
- ):
2738
- """
2739
- Shows the Senate members of the Bittensor's governance protocol.
2740
-
2741
- 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.
2742
-
2743
- EXAMPLE
2744
-
2745
- [green]$[/green] btcli root senate
2746
- """
2747
- self.verbosity_handler(quiet, verbose)
2748
- return self._run_command(root.get_senate(self.initialize_chain(network)))
2749
-
2750
- def root_register(
2751
- self,
2752
- network: Optional[list[str]] = Options.network,
2753
- wallet_name: Optional[str] = Options.wallet_name,
2754
- wallet_path: Optional[str] = Options.wallet_path,
2755
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2756
- prompt: bool = Options.prompt,
2757
- quiet: bool = Options.quiet,
2758
- verbose: bool = Options.verbose,
2759
- ):
2760
- """
2761
- Register a neuron to the root subnet by recycling some TAO to cover for the registration cost.
2762
-
2763
- This command adds a new neuron as a validator on the root network. This will allow the neuron owner to set subnet weights.
2764
-
2765
- # Usage:
2766
-
2767
- 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.
2768
-
2769
- # Example usage:
2770
-
2771
- [green]$[/green] btcli subnets register --netuid 1
2772
- """
2773
- self.verbosity_handler(quiet, verbose)
2774
- wallet = self.wallet_ask(
2775
- wallet_name,
2776
- wallet_path,
2777
- wallet_hotkey,
2778
- ask_for=[WO.NAME, WO.HOTKEY],
2779
- validate=WV.WALLET_AND_HOTKEY,
2780
- )
2781
- return self._run_command(
2782
- root.register(wallet, self.initialize_chain(network), prompt)
2783
- )
2784
-
2785
- def root_proposals(
2786
- self,
2787
- network: Optional[list[str]] = Options.network,
2788
- quiet: bool = Options.quiet,
2789
- verbose: bool = Options.verbose,
2790
- ):
2791
- """
2792
- View active proposals for the senate in the Bittensor's governance protocol.
2793
-
2794
- This command displays the details of ongoing proposals, including proposal hashes, votes, thresholds, and proposal data.
2795
-
2796
- EXAMPLE
2797
-
2798
- [green]$[/green] btcli root proposals
2799
- """
2800
- self.verbosity_handler(quiet, verbose)
2801
- return self._run_command(
2802
- root.proposals(self.initialize_chain(network), verbose)
2803
- )
2804
-
2805
- def root_set_take(
2806
- self,
2807
- network: Optional[list[str]] = Options.network,
2808
- wallet_name: Optional[str] = Options.wallet_name,
2809
- wallet_path: Optional[str] = Options.wallet_path,
2810
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2811
- take: float = typer.Option(None, help="The new take value."),
2812
- quiet: bool = Options.quiet,
2813
- verbose: bool = Options.verbose,
2814
- ):
2815
- """
2816
- Allows users to change their delegate take percentage.
2817
-
2818
- 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:
2819
-
2820
- 1. The provided hotkey is already a delegate.
2821
- 2. The new take value is within 0-18% range.
2822
-
2823
- EXAMPLE
2824
-
2825
- [green]$[/green] btcli root set_take --wallet-name my_wallet --wallet-hotkey my_hotkey
2826
- """
2827
- max_value = 0.18
2828
- min_value = 0.00
2829
- self.verbosity_handler(quiet, verbose)
2830
-
2831
- if not take:
2832
- max_value_style = typer.style(f"Max: {max_value}", fg="magenta")
2833
- min_value_style = typer.style(f"Min: {min_value}", fg="magenta")
2834
- prompt_text = typer.style(
2835
- "Enter take value (0.18 for 18%)", fg="bright_cyan", bold=True
2836
- )
2837
- take = FloatPrompt.ask(f"{prompt_text} {min_value_style} {max_value_style}")
2838
-
2839
- if not (min_value <= take <= max_value):
2840
- print_error(
2841
- f"Take value must be between {min_value} and {max_value}. Provided value: {take}"
2842
- )
2843
- raise typer.Exit()
2844
-
2845
- wallet = self.wallet_ask(
2846
- wallet_name,
2847
- wallet_path,
2848
- wallet_hotkey,
2849
- ask_for=[WO.NAME, WO.HOTKEY],
2850
- validate=WV.WALLET_AND_HOTKEY,
2851
- )
2852
-
2853
- return self._run_command(
2854
- root.set_take(wallet, self.initialize_chain(network), take)
2855
- )
2856
-
2857
- def root_delegate_stake(
2858
- self,
2859
- delegate_ss58key: str = typer.Option(
2860
- None,
2861
- help="The ss58 address of the delegate hotkey to stake TAO to.",
2862
- prompt="Enter the hotkey ss58 address you want to delegate TAO to.",
2863
- ),
2864
- amount: Optional[float] = typer.Option(
2865
- None, help="The amount of TAO to stake. Do no specify if using `--all`"
2866
- ),
2867
- stake_all: Optional[bool] = typer.Option(
2868
- False,
2869
- "--all",
2870
- "-a",
2871
- help="If specified, the command stakes all available TAO. Do not specify if using"
2872
- " `--amount`",
2873
- ),
2874
- wallet_name: Optional[str] = Options.wallet_name,
2875
- wallet_path: Optional[str] = Options.wallet_path,
2876
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2877
- network: Optional[list[str]] = Options.network,
2878
- prompt: bool = Options.prompt,
2879
- quiet: bool = Options.quiet,
2880
- verbose: bool = Options.verbose,
2881
- ):
2882
- """
2883
- Stakes TAO to a specified delegate hotkey.
2884
-
2885
- This command allocates the user's TAO to the specified hotkey of a delegate, potentially earning staking rewards in return. If the
2886
- `--all` flag is used, it delegates the entire TAO balance available in the user's wallet.
2887
-
2888
- 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.
2889
-
2890
- EXAMPLE
2891
-
2892
- [green]$[/green] btcli root delegate-stake --delegate_ss58key <SS58_ADDRESS> --amount <AMOUNT>
2893
-
2894
- [green]$[/green] btcli root delegate-stake --delegate_ss58key <SS58_ADDRESS> --all
2895
-
2896
- [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.
2897
- """
2898
- self.verbosity_handler(quiet, verbose)
2899
- if amount and stake_all:
2900
- err_console.print(
2901
- "Both `--amount` and `--all` are specified. Choose one or the other."
2902
- )
2903
- if not stake_all and not amount:
2904
- while True:
2905
- amount = FloatPrompt.ask(
2906
- "Amount to [blue]stake (TAO τ)[/blue]", console=console
2907
- )
2908
- confirmation = FloatPrompt.ask(
2909
- "Confirm the amount to stake [blue](TAO τ)[/blue]",
2910
- console=console,
2911
- )
2912
- if amount == confirmation:
2913
- break
2914
- else:
2915
- err_console.print(
2916
- "[red]The amounts do not match. Please try again.[/red]"
2917
- )
2918
-
2919
- wallet = self.wallet_ask(
2920
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2921
- )
2922
- return self._run_command(
2923
- root.delegate_stake(
2924
- wallet,
2925
- self.initialize_chain(network),
2926
- amount,
2927
- delegate_ss58key,
2928
- prompt,
2929
- )
2930
- )
2931
-
2932
- def root_undelegate_stake(
2933
- self,
2934
- delegate_ss58key: str = typer.Option(
2935
- None,
2936
- help="The ss58 address of the delegate to undelegate from.",
2937
- prompt="Enter the hotkey ss58 address you want to undelegate from",
2938
- ),
2939
- amount: Optional[float] = typer.Option(
2940
- None, help="The amount of TAO to unstake. Do no specify if using `--all`"
2941
- ),
2942
- unstake_all: Optional[bool] = typer.Option(
2943
- False,
2944
- "--all",
2945
- "-a",
2946
- help="If specified, the command undelegates all staked TAO from the delegate. Do not specify if using"
2947
- " `--amount`",
2948
- ),
2949
- wallet_name: Optional[str] = Options.wallet_name,
2950
- wallet_path: Optional[str] = Options.wallet_path,
2951
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
2952
- network: Optional[list[str]] = Options.network,
2953
- prompt: bool = Options.prompt,
2954
- quiet: bool = Options.quiet,
2955
- verbose: bool = Options.verbose,
2956
- ):
2957
- """
2958
- Allows users to withdraw their staked TAO from a delegate.
2959
-
2960
- 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.
2961
-
2962
- EXAMPLE
2963
-
2964
- [green]$[/green] btcli undelegate --delegate_ss58key <SS58_ADDRESS> --amount <AMOUNT>
2965
-
2966
- [green]$[/green] btcli undelegate --delegate_ss58key <SS58_ADDRESS> --all
2967
- """
2968
- self.verbosity_handler(quiet, verbose)
2969
- if amount and unstake_all:
2970
- err_console.print(
2971
- "Both `--amount` and `--all` are specified. Choose one or the other."
2972
- )
2973
- if not unstake_all and not amount:
2974
- while True:
2975
- amount = FloatPrompt.ask(
2976
- "Amount to [blue]unstake (TAO τ)[/blue]", console=console
2977
- )
2978
- confirmation = FloatPrompt.ask(
2979
- "Confirm the amount to unstake [blue](TAO τ)[/blue]",
2980
- console=console,
2981
- )
2982
- if amount == confirmation:
2983
- break
2984
- else:
2985
- err_console.print(
2986
- "[red]The amounts do not match. Please try again.[/red]"
2987
- )
2988
-
2989
- wallet = self.wallet_ask(
2990
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2991
- )
2992
- self._run_command(
2993
- root.delegate_unstake(
2994
- wallet,
2995
- self.initialize_chain(network),
2996
- amount,
2997
- delegate_ss58key,
2998
- prompt,
2999
- )
3000
- )
3001
-
3002
- def root_my_delegates(
3003
- self,
3004
- network: Optional[list[str]] = Options.network,
3005
- wallet_name: Optional[str] = Options.wallet_name,
3006
- wallet_path: Optional[str] = Options.wallet_path,
3007
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3008
- all_wallets: bool = typer.Option(
3009
- False,
3010
- "--all-wallets",
3011
- "--all",
3012
- "-a",
3013
- help="If specified, the command aggregates information across all the wallets.",
3014
- ),
3015
- quiet: bool = Options.quiet,
3016
- verbose: bool = Options.verbose,
3017
- ):
3018
- """
3019
- Shows a table with the details on the user's delegates.
3020
-
3021
- The table output includes the following columns:
3022
-
3023
- - Wallet: The name of the user's wallet (coldkey).
3024
-
3025
- - OWNER: The name of the delegate who owns the hotkey.
3026
-
3027
- - SS58: The truncated SS58 address of the delegate's hotkey.
3028
-
3029
- - Delegation: The amount of TAO staked by the user to the delegate.
3030
-
3031
- - τ/24h: The earnings from the delegate to the user over the past 24 hours.
3032
-
3033
- - NOMS: The number of nominators for the delegate.
3034
-
3035
- - OWNER STAKE(τ): The stake amount owned by the delegate.
3036
-
3037
- - TOTAL STAKE(τ): The total stake amount held by the delegate.
3038
-
3039
- - SUBNETS: The list of subnets the delegate is a part of.
3040
-
3041
- - VPERMIT: Validator permits held by the delegate for various subnets.
3042
-
3043
- - 24h/kτ: Earnings per 1000 TAO staked over the last 24 hours.
3044
-
3045
- - Desc: A description of the delegate.
3046
-
3047
- The command also sums and prints the total amount of TAO delegated across all wallets.
3048
-
3049
- EXAMPLE
3050
-
3051
- [green]$[/green] btcli root my-delegates
3052
- [green]$[/green] btcli root my-delegates --all
3053
- [green]$[/green] btcli root my-delegates --wallet-name my_wallet
3054
-
3055
- [blue bold]Note[/blue bold]: This command is not intended to be used directly in user code.
3056
- """
3057
- self.verbosity_handler(quiet, verbose)
3058
- wallet = self.wallet_ask(
3059
- wallet_name,
3060
- wallet_path,
3061
- wallet_hotkey,
3062
- ask_for=([WO.NAME] if not all_wallets else []),
3063
- validate=WV.WALLET if not all_wallets else WV.NONE,
3064
- )
3065
- self._run_command(
3066
- root.my_delegates(wallet, self.initialize_chain(network), all_wallets)
3067
- )
3068
-
3069
- def root_list_delegates(
3070
- self,
3071
- network: Optional[list[str]] = Options.network,
3072
- quiet: bool = Options.quiet,
3073
- verbose: bool = Options.verbose,
3074
- ):
3075
- """
3076
- Displays a table of Bittensor network-wide delegates, providing a comprehensive overview of delegate statistics and information.
3077
-
3078
- This table helps users make informed decisions on which delegates to allocate their TAO stake.
3079
-
3080
- The table columns include:
3081
-
3082
- - INDEX: The delegate's index in the sorted list.
3083
-
3084
- - DELEGATE: The name of the delegate.
3085
-
3086
- - SS58: The delegate's unique ss58 address (truncated for display).
3087
-
3088
- - NOMINATORS: The count of nominators backing the delegate.
3089
-
3090
- - OWN STAKE(τ): The amount of delegate's own stake (not the TAO delegated from any nominators).
3091
-
3092
- - TOTAL STAKE(τ): The delegate's total stake, i.e., the sum of delegate's own stake and nominators' stakes.
3093
-
3094
- - CHANGE/(4h): The percentage change in the delegate's stake over the last four hours.
3095
-
3096
- - SUBNETS: The subnets in which the delegate is registered.
3097
-
3098
- - VPERMIT: Indicates the subnets in which the delegate has validator permits.
3099
-
3100
- - NOMINATOR/(24h)/kτ: The earnings per 1000 τ staked by nominators in the last 24 hours.
3101
-
3102
- - DELEGATE/(24h): The total earnings of the delegate in the last 24 hours.
3103
-
3104
- - DESCRIPTION: A brief description of the delegate's purpose and operations.
3105
-
3106
- [blue bold]NOTES:[/blue bold]
3107
-
3108
- - Sorting is done based on the `TOTAL STAKE` column in descending order.
3109
- - Changes in stake are shown as: increases in green and decreases in red.
3110
- - Entries with no previous data are marked with `NA`.
3111
- - Each delegate's name is a hyperlink to more information, if available.
3112
-
3113
- EXAMPLE
3114
-
3115
- [green]$[/green] btcli root list_delegates
3116
-
3117
- [green]$[/green] btcli root list_delegates --subtensor.network finney # can also be `test` or `local`
3118
-
3119
- [blue bold]NOTE[/blue bold]: This command is intended for use within a
3120
- console application. It prints directly to the console and does not return any value.
3121
- """
3122
- self.verbosity_handler(quiet, verbose)
3123
-
3124
- if network:
3125
- if "finney" in network:
3126
- network = ["wss://archive.chain.opentensor.ai:443"]
3127
- elif (conf_net := self.config.get("network")) == "finney":
3128
- network = ["wss://archive.chain.opentensor.ai:443"]
3129
- elif conf_net:
3130
- network = [conf_net]
3131
- else:
3132
- network = ["wss://archive.chain.opentensor.ai:443"]
3133
-
3134
- sub = self.initialize_chain(network)
3135
- return self._run_command(root.list_delegates(sub))
3136
-
3137
- # TODO: Confirm if we need a command for this - currently registering to root auto makes u delegate
3138
- def root_nominate(
3139
- self,
3140
- wallet_name: Optional[str] = Options.wallet_name,
3141
- wallet_path: Optional[str] = Options.wallet_path,
3142
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3143
- network: Optional[list[str]] = Options.network,
3144
- prompt: bool = Options.prompt,
3145
- quiet: bool = Options.quiet,
3146
- verbose: bool = Options.verbose,
3147
- ):
3148
- """
3149
- Enables a wallet's hotkey to become a delegate.
3150
-
3151
- This command handles the nomination process, including wallet unlocking and verification of the hotkey's current delegate status.
3152
-
3153
- The command performs several checks:
3154
-
3155
- - Verifies that the hotkey is not already a delegate to prevent redundant nominations.
3156
-
3157
- - Tries to nominate the wallet and reports success or failure.
3158
-
3159
- Upon success, the wallet's hotkey is registered as a delegate on the network.
3160
-
3161
- 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.
3162
-
3163
- EXAMPLE
3164
-
3165
- [green]$[/green] btcli root nominate
3166
-
3167
- [green]$[/green] btcli root nominate --wallet-name my_wallet --wallet-hotkey my_hotkey
3168
-
3169
- [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.
3170
- """
3171
- self.verbosity_handler(quiet, verbose)
3172
- wallet = self.wallet_ask(
3173
- wallet_name,
3174
- wallet_path,
3175
- wallet_hotkey,
3176
- ask_for=[WO.NAME, WO.HOTKEY],
3177
- validate=WV.WALLET_AND_HOTKEY,
3178
- )
3179
- return self._run_command(
3180
- root.nominate(wallet, self.initialize_chain(network), prompt)
3181
- )
3182
-
3183
- def stake_show(
3184
- self,
3185
- all_wallets: bool = typer.Option(
3186
- False,
3187
- "--all",
3188
- "--all-wallets",
3189
- "-a",
3190
- help="When set, the command checks all the coldkey wallets of the user instead of just the specified wallet.",
3191
- ),
3192
- network: Optional[list[str]] = Options.network,
3193
- wallet_name: Optional[str] = Options.wallet_name,
3194
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3195
- wallet_path: Optional[str] = Options.wallet_path,
3196
- reuse_last: bool = Options.reuse_last,
3197
- html_output: bool = Options.html_output,
3198
- quiet: bool = Options.quiet,
3199
- verbose: bool = Options.verbose,
3200
- ):
3201
- """
3202
- Lists all the stake accounts associated with a user's wallet.
3203
-
3204
- 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.
3205
-
3206
- 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.
3207
-
3208
- The command shows a table with the below columns:
3209
-
3210
- - Coldkey: The coldkey associated with the wallet.
3211
-
3212
- - Balance: The balance of the coldkey.
3213
-
3214
- - Hotkey: The names of the coldkey's own hotkeys and the delegate hotkeys to which this coldkey has staked.
3215
-
3216
- - Stake: The amount of TAO staked to all the hotkeys.
3217
-
3218
- - Rate: The rate of return on the stake, shown in TAO per day.
3219
-
3220
- EXAMPLE
3221
-
3222
- [green]$[/green] btcli stake show --all
3223
- """
3224
- self.verbosity_handler(quiet, verbose)
3225
- if (reuse_last or html_output) and self.config.get("use_cache") is False:
3226
- err_console.print(
3227
- "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. "
3228
- "Please change the config to 'False' using `btcli config set`"
3122
+ if not unstake_all and not unstake_all_alpha:
3123
+ safe_staking = self.ask_safe_staking(safe_staking)
3124
+ if safe_staking:
3125
+ rate_tolerance = self.ask_rate_tolerance(rate_tolerance)
3126
+ allow_partial_stake = self.ask_partial_stake(allow_partial_stake)
3127
+ console.print("\n")
3128
+
3129
+ if interactive and any(
3130
+ [hotkey_ss58_address, include_hotkeys, exclude_hotkeys, all_hotkeys]
3131
+ ):
3132
+ print_error(
3133
+ "Interactive mode cannot be used with hotkey selection options like --include-hotkeys, --exclude-hotkeys, --all-hotkeys, or --hotkey."
3229
3134
  )
3230
3135
  raise typer.Exit()
3231
- if not reuse_last:
3232
- subtensor = self.initialize_chain(network)
3233
- else:
3234
- subtensor = None
3235
-
3236
- if all_wallets:
3237
- wallet = self.wallet_ask(
3238
- wallet_name,
3239
- wallet_path,
3240
- wallet_hotkey,
3241
- ask_for=[],
3242
- validate=WV.NONE,
3243
- )
3244
- else:
3245
- wallet = self.wallet_ask(
3246
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3247
- )
3248
-
3249
- return self._run_command(
3250
- stake.show(
3251
- wallet,
3252
- subtensor,
3253
- all_wallets,
3254
- reuse_last,
3255
- html_output,
3256
- not self.config.get("use_cache", True),
3257
- )
3258
- )
3259
-
3260
- def stake_add(
3261
- self,
3262
- stake_all: bool = typer.Option(
3263
- False,
3264
- "--all-tokens",
3265
- "--all",
3266
- "-a",
3267
- help="When set, the command stakes all the available TAO from the coldkey.",
3268
- ),
3269
- amount: float = typer.Option(
3270
- 0.0, "--amount", help="The amount of TAO to stake"
3271
- ),
3272
- max_stake: float = typer.Option(
3273
- 0.0,
3274
- "--max-stake",
3275
- "-m",
3276
- 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.",
3277
- ),
3278
- hotkey_ss58_address: str = typer.Option(
3279
- "",
3280
- help="The ss58 address of the hotkey to stake to.",
3281
- ),
3282
- include_hotkeys: str = typer.Option(
3283
- "",
3284
- "--include-hotkeys",
3285
- "-in",
3286
- help="Specifies hotkeys by name or ss58 address to stake to. For example, `-in hk1,hk2`",
3287
- ),
3288
- exclude_hotkeys: str = typer.Option(
3289
- "",
3290
- "--exclude-hotkeys",
3291
- "-ex",
3292
- help="Specifies hotkeys by name or ss58 address to not to stake to (use this option only with `--all-hotkeys`)"
3293
- " i.e. `--all-hotkeys -ex hk3,hk4`",
3294
- ),
3295
- all_hotkeys: bool = typer.Option(
3296
- False,
3297
- help="When set, this command stakes to all hotkeys associated with the wallet. Do not use if specifying "
3298
- "hotkeys in `--include-hotkeys`.",
3299
- ),
3300
- wallet_name: str = Options.wallet_name,
3301
- wallet_path: str = Options.wallet_path,
3302
- wallet_hotkey: str = Options.wallet_hotkey,
3303
- network: Optional[list[str]] = Options.network,
3304
- prompt: bool = Options.prompt,
3305
- quiet: bool = Options.quiet,
3306
- verbose: bool = Options.verbose,
3307
- ):
3308
- """
3309
- Stake TAO to one or more hotkeys associated with the user's coldkey.
3310
-
3311
- 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.
3312
-
3313
- This command is used by a subnet validator to allocate stake TAO to their different hotkeys, securing their position and influence on the network.
3314
-
3315
- EXAMPLE
3316
-
3317
- [green]$[/green] btcli stake add --amount 100 --wallet-name <my_wallet> --wallet-hotkey <my_hotkey>
3318
- """
3319
- self.verbosity_handler(quiet, verbose)
3320
3136
 
3321
- if stake_all and amount:
3322
- print_error(
3323
- "Cannot specify an amount and 'stake-all'. Choose one or the other."
3324
- )
3137
+ if unstake_all and unstake_all_alpha:
3138
+ print_error("Cannot specify both unstake-all and unstake-all-alpha.")
3325
3139
  raise typer.Exit()
3326
3140
 
3327
- if not stake_all and not amount and not max_stake:
3328
- amount = FloatPrompt.ask("Amount to [blue]stake (TAO τ)[/blue]")
3141
+ if not interactive and not unstake_all and not unstake_all_alpha:
3142
+ netuid = get_optional_netuid(netuid, all_netuids)
3143
+ if all_hotkeys and include_hotkeys:
3144
+ print_error(
3145
+ "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag"
3146
+ " should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`."
3147
+ )
3148
+ raise typer.Exit()
3329
3149
 
3330
- if stake_all and not amount:
3331
- if not Confirm.ask("Stake all the available TAO tokens?", default=False):
3150
+ if include_hotkeys and exclude_hotkeys:
3151
+ print_error(
3152
+ "You have specified both including and excluding hotkeys options. Select one or the other."
3153
+ )
3332
3154
  raise typer.Exit()
3333
3155
 
3334
- if all_hotkeys and include_hotkeys:
3335
- err_console.print(
3336
- "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag"
3337
- "should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`."
3338
- )
3339
- raise typer.Exit()
3156
+ if unstake_all and amount:
3157
+ print_error(
3158
+ "Cannot specify both a specific amount and 'unstake-all'. Choose one or the other."
3159
+ )
3160
+ raise typer.Exit()
3340
3161
 
3341
- if include_hotkeys and exclude_hotkeys:
3342
- err_console.print(
3343
- "You have specified options for both including and excluding hotkeys. Select one or the other."
3344
- )
3345
- raise typer.Exit()
3162
+ if amount and amount <= 0:
3163
+ print_error(f"You entered an incorrect unstake amount: {amount}")
3164
+ raise typer.Exit()
3346
3165
 
3347
3166
  if (
3348
3167
  not wallet_hotkey
3168
+ and not hotkey_ss58_address
3349
3169
  and not all_hotkeys
3350
3170
  and not include_hotkeys
3351
- and not hotkey_ss58_address
3171
+ and not interactive
3172
+ and not unstake_all
3173
+ and not unstake_all_alpha
3352
3174
  ):
3175
+ if not wallet_name:
3176
+ wallet_name = Prompt.ask(
3177
+ "Enter the [blue]wallet name[/blue]",
3178
+ default=self.config.get("wallet_name") or defaults.wallet.name,
3179
+ )
3353
3180
  hotkey_or_ss58 = Prompt.ask(
3354
- "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to",
3181
+ "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from [dim](or Press Enter to view existing staked hotkeys)[/dim]",
3355
3182
  )
3356
- if is_valid_ss58_address(hotkey_or_ss58):
3183
+ if hotkey_or_ss58 == "":
3184
+ wallet = self.wallet_ask(
3185
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3186
+ )
3187
+ interactive = True
3188
+ elif is_valid_ss58_address(hotkey_or_ss58):
3357
3189
  hotkey_ss58_address = hotkey_or_ss58
3358
3190
  wallet = self.wallet_ask(
3359
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3191
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3360
3192
  )
3361
3193
  else:
3362
3194
  wallet_hotkey = hotkey_or_ss58
@@ -3364,214 +3196,478 @@ class CLIManager:
3364
3196
  wallet_name,
3365
3197
  wallet_path,
3366
3198
  wallet_hotkey,
3367
- ask_for=[WO.NAME, WO.HOTKEY],
3199
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3368
3200
  validate=WV.WALLET_AND_HOTKEY,
3369
3201
  )
3370
3202
 
3371
- elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address:
3203
+ elif unstake_all or unstake_all_alpha:
3204
+ if not wallet_name:
3205
+ wallet_name = Prompt.ask(
3206
+ "Enter the [blue]wallet name[/blue]",
3207
+ default=self.config.get("wallet_name") or defaults.wallet.name,
3208
+ )
3209
+ if include_hotkeys:
3210
+ if len(include_hotkeys) > 1:
3211
+ print_error("Cannot unstake_all from multiple hotkeys at once.")
3212
+ raise typer.Exit()
3213
+ elif is_valid_ss58_address(include_hotkeys[0]):
3214
+ hotkey_ss58_address = include_hotkeys[0]
3215
+ else:
3216
+ print_error("Invalid hotkey ss58 address.")
3217
+ raise typer.Exit()
3218
+ else:
3219
+ hotkey_or_ss58 = Prompt.ask(
3220
+ "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from",
3221
+ default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey,
3222
+ )
3223
+ if is_valid_ss58_address(hotkey_or_ss58):
3224
+ hotkey_ss58_address = hotkey_or_ss58
3225
+ wallet = self.wallet_ask(
3226
+ wallet_name,
3227
+ wallet_path,
3228
+ wallet_hotkey,
3229
+ ask_for=[WO.NAME, WO.PATH],
3230
+ )
3231
+ else:
3232
+ wallet_hotkey = hotkey_or_ss58
3233
+ wallet = self.wallet_ask(
3234
+ wallet_name,
3235
+ wallet_path,
3236
+ wallet_hotkey,
3237
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3238
+ validate=WV.WALLET_AND_HOTKEY,
3239
+ )
3240
+ return self._run_command(
3241
+ remove_stake.unstake_all(
3242
+ wallet=wallet,
3243
+ subtensor=self.initialize_chain(network),
3244
+ hotkey_ss58_address=hotkey_ss58_address,
3245
+ unstake_all_alpha=unstake_all_alpha,
3246
+ prompt=prompt,
3247
+ )
3248
+ )
3249
+ elif (
3250
+ all_hotkeys
3251
+ or include_hotkeys
3252
+ or exclude_hotkeys
3253
+ or hotkey_ss58_address
3254
+ or interactive
3255
+ or unstake_all
3256
+ or unstake_all_alpha
3257
+ ):
3372
3258
  wallet = self.wallet_ask(
3373
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3259
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3374
3260
  )
3375
3261
  else:
3376
3262
  wallet = self.wallet_ask(
3377
3263
  wallet_name,
3378
3264
  wallet_path,
3379
3265
  wallet_hotkey,
3380
- ask_for=[WO.NAME, WO.HOTKEY],
3266
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3381
3267
  validate=WV.WALLET_AND_HOTKEY,
3382
3268
  )
3383
3269
 
3384
3270
  if include_hotkeys:
3385
- included_hotkeys = parse_to_list(
3271
+ include_hotkeys = parse_to_list(
3386
3272
  include_hotkeys,
3387
3273
  str,
3388
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3389
- "`--include-hotkeys 5Grw....,5Grw....`.",
3274
+ "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--include-hotkeys hk1,hk2`.",
3275
+ is_ss58=False,
3390
3276
  )
3391
- else:
3392
- included_hotkeys = []
3393
3277
 
3394
3278
  if exclude_hotkeys:
3395
- excluded_hotkeys = parse_to_list(
3279
+ exclude_hotkeys = parse_to_list(
3396
3280
  exclude_hotkeys,
3397
3281
  str,
3398
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3399
- "`--exclude-hotkeys 5Grw....,5Grw....`.",
3282
+ "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--exclude-hotkeys hk3,hk4`.",
3283
+ is_ss58=False,
3400
3284
  )
3401
- else:
3402
- excluded_hotkeys = []
3403
3285
 
3404
3286
  return self._run_command(
3405
- stake.stake_add(
3406
- wallet,
3407
- self.initialize_chain(network),
3408
- amount,
3409
- stake_all,
3410
- max_stake,
3411
- included_hotkeys,
3412
- excluded_hotkeys,
3413
- all_hotkeys,
3414
- prompt,
3415
- hotkey_ss58_address,
3287
+ remove_stake.unstake(
3288
+ wallet=wallet,
3289
+ subtensor=self.initialize_chain(network),
3290
+ hotkey_ss58_address=hotkey_ss58_address,
3291
+ all_hotkeys=all_hotkeys,
3292
+ include_hotkeys=include_hotkeys,
3293
+ exclude_hotkeys=exclude_hotkeys,
3294
+ amount=amount,
3295
+ prompt=prompt,
3296
+ interactive=interactive,
3297
+ netuid=netuid,
3298
+ safe_staking=safe_staking,
3299
+ rate_tolerance=rate_tolerance,
3300
+ allow_partial_stake=allow_partial_stake,
3416
3301
  )
3417
3302
  )
3418
3303
 
3419
- def stake_remove(
3304
+ def stake_move(
3420
3305
  self,
3421
3306
  network: Optional[list[str]] = Options.network,
3422
- wallet_name: str = Options.wallet_name,
3423
- wallet_path: str = Options.wallet_path,
3424
- wallet_hotkey: str = Options.wallet_hotkey,
3425
- unstake_all: bool = typer.Option(
3426
- False,
3427
- "--unstake-all",
3428
- "--all",
3429
- help="When set, this commmand unstakes all staked TAO from the specified hotkeys.",
3430
- ),
3431
- amount: float = typer.Option(
3432
- 0.0, "--amount", "-a", help="The amount of TAO to unstake."
3307
+ wallet_name=Options.wallet_name,
3308
+ wallet_path=Options.wallet_path,
3309
+ wallet_hotkey=Options.wallet_hotkey,
3310
+ origin_netuid: Optional[int] = typer.Option(
3311
+ None, "--origin-netuid", help="Origin netuid"
3433
3312
  ),
3434
- hotkey_ss58_address: str = typer.Option(
3435
- "",
3436
- help="The ss58 address of the hotkey to unstake from.",
3437
- ),
3438
- keep_stake: float = typer.Option(
3439
- 0.0,
3440
- "--keep-stake",
3441
- "--keep",
3442
- help="Sets the maximum amount of TAO to remain staked in each hotkey.",
3313
+ destination_netuid: Optional[int] = typer.Option(
3314
+ None, "--dest-netuid", help="Destination netuid"
3443
3315
  ),
3444
- include_hotkeys: str = typer.Option(
3445
- "",
3446
- "--include-hotkeys",
3447
- "-in",
3448
- help="Specifies the hotkeys by name or ss58 address to unstake from. For example, `-in hk1,hk2`",
3316
+ destination_hotkey: Optional[str] = typer.Option(
3317
+ None, "--dest-ss58", "--dest", help="Destination hotkey", prompt=False
3449
3318
  ),
3450
- exclude_hotkeys: str = typer.Option(
3451
- "",
3452
- "--exclude-hotkeys",
3453
- "-ex",
3454
- help="Specifies the hotkeys by name or ss58 address not to unstake from (only use with `--all-hotkeys`)"
3455
- " i.e. `--all-hotkeys -ex hk3,hk4`",
3319
+ amount: float = typer.Option(
3320
+ None,
3321
+ "--amount",
3322
+ help="The amount of TAO to stake",
3323
+ prompt=False,
3456
3324
  ),
3457
- all_hotkeys: bool = typer.Option(
3458
- False,
3459
- help="When set, this command unstakes from all the hotkeys associated with the wallet. Do not use if specifying "
3460
- "hotkeys in `--include-hotkeys`.",
3325
+ stake_all: bool = typer.Option(
3326
+ False, "--stake-all", "--all", help="Stake all", prompt=False
3461
3327
  ),
3462
3328
  prompt: bool = Options.prompt,
3463
- quiet: bool = Options.quiet,
3464
- verbose: bool = Options.verbose,
3465
3329
  ):
3466
3330
  """
3467
- Unstake TAO from one or more hotkeys and transfer them back to the user's coldkey.
3331
+ Move staked TAO between hotkeys while keeping the same coldkey ownership.
3468
3332
 
3469
- This command is used to withdraw TAO previously staked to different hotkeys.
3333
+ This command allows you to:
3334
+ - Move stake from one hotkey to another hotkey
3335
+ - Move stake between different subnets
3336
+ - Keep the same coldkey ownership
3470
3337
 
3471
- EXAMPLE
3338
+ You can specify:
3339
+ - The origin subnet (--origin-netuid)
3340
+ - The destination subnet (--dest-netuid)
3341
+ - The destination hotkey (--dest-hotkey)
3342
+ - The amount to move (--amount)
3472
3343
 
3473
- [green]$[/green] btcli stake remove --amount 100 -in hk1,hk2
3344
+ If no arguments are provided, an interactive selection menu will be shown.
3474
3345
 
3475
- [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.
3476
- """
3477
- self.verbosity_handler(quiet, verbose)
3346
+ EXAMPLE
3478
3347
 
3479
- if all_hotkeys and include_hotkeys:
3480
- err_console.print(
3481
- "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag"
3482
- "should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`."
3348
+ [green]$[/green] btcli stake move
3349
+ """
3350
+ console.print(
3351
+ "[dim]This command moves stake from one hotkey to another hotkey while keeping the same coldkey.[/dim]"
3352
+ )
3353
+ if not destination_hotkey:
3354
+ dest_wallet_or_ss58 = Prompt.ask(
3355
+ "Enter the [blue]destination wallet[/blue] where destination hotkey is located or [blue]ss58 address[/blue]"
3483
3356
  )
3484
- raise typer.Exit()
3357
+ if is_valid_ss58_address(dest_wallet_or_ss58):
3358
+ destination_hotkey = dest_wallet_or_ss58
3359
+ else:
3360
+ dest_wallet = self.wallet_ask(
3361
+ dest_wallet_or_ss58,
3362
+ wallet_path,
3363
+ None,
3364
+ ask_for=[WO.NAME, WO.PATH],
3365
+ validate=WV.WALLET,
3366
+ )
3367
+ destination_hotkey = Prompt.ask(
3368
+ "Enter the [blue]destination hotkey[/blue] name",
3369
+ default=dest_wallet.hotkey_str,
3370
+ )
3371
+ destination_wallet = self.wallet_ask(
3372
+ dest_wallet_or_ss58,
3373
+ wallet_path,
3374
+ destination_hotkey,
3375
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3376
+ validate=WV.WALLET_AND_HOTKEY,
3377
+ )
3378
+ destination_hotkey = destination_wallet.hotkey.ss58_address
3379
+ else:
3380
+ if is_valid_ss58_address(destination_hotkey):
3381
+ destination_hotkey = destination_hotkey
3382
+ else:
3383
+ print_error(
3384
+ "Invalid destination hotkey ss58 address. Please enter a valid ss58 address or wallet name."
3385
+ )
3386
+ raise typer.Exit()
3485
3387
 
3486
- if include_hotkeys and exclude_hotkeys:
3487
- err_console.print(
3488
- "You have specified both including and excluding hotkeys options. Select one or the other."
3388
+ if not wallet_name:
3389
+ wallet_name = Prompt.ask(
3390
+ "Enter the [blue]origin wallet name[/blue]",
3391
+ default=self.config.get("wallet_name") or defaults.wallet.name,
3489
3392
  )
3490
- raise typer.Exit()
3393
+ wallet = self.wallet_ask(
3394
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3395
+ )
3491
3396
 
3492
- if unstake_all and amount:
3493
- err_console.print(
3494
- "Cannot specify both a specific amount and 'unstake-all'. Choose one or the other."
3397
+ interactive_selection = False
3398
+ if not wallet_hotkey:
3399
+ origin_hotkey = Prompt.ask(
3400
+ "Enter the [blue]origin hotkey[/blue] name or "
3401
+ "[blue]ss58 address[/blue] where the stake will be moved from "
3402
+ "[dim](or Press Enter to view existing stakes)[/dim]"
3495
3403
  )
3496
- raise typer.Exit()
3497
-
3498
- if not unstake_all and not amount and not keep_stake:
3499
- amount = FloatPrompt.ask("Amount to [blue]unstake (TAO τ)[/blue]")
3404
+ if origin_hotkey == "":
3405
+ interactive_selection = True
3500
3406
 
3501
- if unstake_all and not amount and prompt:
3502
- if not Confirm.ask("Unstake all staked TAO tokens?", default=False):
3503
- raise typer.Exit()
3504
-
3505
- if (
3506
- not wallet_hotkey
3507
- and not hotkey_ss58_address
3508
- and not all_hotkeys
3509
- and not include_hotkeys
3510
- ):
3511
- hotkey_or_ss58 = Prompt.ask(
3512
- "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from"
3513
- )
3514
- if is_valid_ss58_address(hotkey_or_ss58):
3515
- hotkey_ss58_address = hotkey_or_ss58
3407
+ elif is_valid_ss58_address(origin_hotkey):
3408
+ origin_hotkey = origin_hotkey
3409
+ else:
3516
3410
  wallet = self.wallet_ask(
3517
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3411
+ wallet_name,
3412
+ wallet_path,
3413
+ origin_hotkey,
3414
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3415
+ validate=WV.WALLET_AND_HOTKEY,
3518
3416
  )
3417
+ origin_hotkey = wallet.hotkey.ss58_address
3418
+ else:
3419
+ if is_valid_ss58_address(wallet_hotkey):
3420
+ origin_hotkey = wallet_hotkey
3519
3421
  else:
3520
- wallet_hotkey = hotkey_or_ss58
3521
3422
  wallet = self.wallet_ask(
3522
3423
  wallet_name,
3523
3424
  wallet_path,
3524
3425
  wallet_hotkey,
3525
- ask_for=[WO.NAME, WO.HOTKEY],
3426
+ ask_for=[],
3526
3427
  validate=WV.WALLET_AND_HOTKEY,
3527
3428
  )
3429
+ origin_hotkey = wallet.hotkey.ss58_address
3528
3430
 
3529
- elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address:
3530
- wallet = self.wallet_ask(
3531
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3431
+ if not interactive_selection:
3432
+ if origin_netuid is None:
3433
+ origin_netuid = IntPrompt.ask(
3434
+ "Enter the [blue]origin subnet[/blue] (netuid) to move stake from"
3435
+ )
3436
+
3437
+ if destination_netuid is None:
3438
+ destination_netuid = IntPrompt.ask(
3439
+ "Enter the [blue]destination subnet[/blue] (netuid) to move stake to"
3440
+ )
3441
+
3442
+ return self._run_command(
3443
+ move_stake.move_stake(
3444
+ subtensor=self.initialize_chain(network),
3445
+ wallet=wallet,
3446
+ origin_netuid=origin_netuid,
3447
+ origin_hotkey=origin_hotkey,
3448
+ destination_netuid=destination_netuid,
3449
+ destination_hotkey=destination_hotkey,
3450
+ amount=amount,
3451
+ stake_all=stake_all,
3452
+ interactive_selection=interactive_selection,
3453
+ prompt=prompt,
3454
+ )
3455
+ )
3456
+
3457
+ def stake_transfer(
3458
+ self,
3459
+ network: Optional[list[str]] = Options.network,
3460
+ wallet_name: Optional[str] = Options.wallet_name,
3461
+ wallet_path: Optional[str] = Options.wallet_path,
3462
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3463
+ origin_netuid: Optional[int] = typer.Option(
3464
+ None,
3465
+ "--origin-netuid",
3466
+ help="The netuid to transfer stake from",
3467
+ ),
3468
+ dest_netuid: Optional[int] = typer.Option(
3469
+ None,
3470
+ "--dest-netuid",
3471
+ help="The netuid to transfer stake to",
3472
+ ),
3473
+ dest_ss58: Optional[str] = typer.Option(
3474
+ None,
3475
+ "--dest-ss58",
3476
+ "--dest",
3477
+ "--dest-coldkey",
3478
+ help="The destination wallet name or SS58 address to transfer stake to",
3479
+ ),
3480
+ amount: float = typer.Option(
3481
+ None,
3482
+ "--amount",
3483
+ "-a",
3484
+ help="Amount of stake to transfer",
3485
+ ),
3486
+ prompt: bool = Options.prompt,
3487
+ quiet: bool = Options.quiet,
3488
+ verbose: bool = Options.verbose,
3489
+ ):
3490
+ """
3491
+ Transfer stake between coldkeys while keeping the same hotkey ownership.
3492
+
3493
+ This command allows you to:
3494
+ - Transfer stake from one coldkey to another coldkey
3495
+ - Keep the same hotkey ownership
3496
+ - Transfer stake between different subnets
3497
+
3498
+ You can specify:
3499
+ - The origin subnet (--origin-netuid)
3500
+ - The destination subnet (--dest-netuid)
3501
+ - The destination wallet/address (--dest)
3502
+ - The amount to transfer (--amount)
3503
+
3504
+ If no arguments are provided, an interactive selection menu will be shown.
3505
+
3506
+ EXAMPLE
3507
+
3508
+ Transfer 100 TAO from subnet 1 to subnet 2:
3509
+ [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest wallet2 --amount 100
3510
+
3511
+ Using SS58 address:
3512
+ [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngEP9H7qjkKgHLcK --amount 100
3513
+ """
3514
+ console.print(
3515
+ "[dim]This command transfers stake from one coldkey to another while keeping the same hotkey.[/dim]"
3516
+ )
3517
+ self.verbosity_handler(quiet, verbose)
3518
+
3519
+ wallet = self.wallet_ask(
3520
+ wallet_name,
3521
+ wallet_path,
3522
+ wallet_hotkey,
3523
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3524
+ validate=WV.WALLET_AND_HOTKEY,
3525
+ )
3526
+
3527
+ if not dest_ss58:
3528
+ dest_ss58 = Prompt.ask(
3529
+ "Enter the [blue]destination wallet name[/blue] or [blue]coldkey SS58 address[/blue]"
3532
3530
  )
3533
3531
 
3532
+ if is_valid_ss58_address(dest_ss58):
3533
+ dest_ss58 = dest_ss58
3534
3534
  else:
3535
- wallet = self.wallet_ask(
3536
- wallet_name,
3535
+ dest_wallet = self.wallet_ask(
3536
+ dest_ss58,
3537
3537
  wallet_path,
3538
- wallet_hotkey,
3539
- ask_for=[WO.NAME, WO.HOTKEY],
3540
- validate=WV.WALLET_AND_HOTKEY,
3538
+ None,
3539
+ ask_for=[WO.NAME, WO.PATH],
3540
+ validate=WV.WALLET,
3541
3541
  )
3542
+ dest_ss58 = dest_wallet.coldkeypub.ss58_address
3542
3543
 
3543
- if include_hotkeys:
3544
- included_hotkeys = parse_to_list(
3545
- include_hotkeys,
3546
- str,
3547
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3548
- "`--include-hotkeys 5Grw....,5Grw....`.",
3549
- )
3544
+ interactive_selection = False
3545
+ if origin_netuid is None and dest_netuid is None and not amount:
3546
+ interactive_selection = True
3550
3547
  else:
3551
- included_hotkeys = []
3548
+ if origin_netuid is None:
3549
+ origin_netuid = IntPrompt.ask(
3550
+ "Enter the [blue]origin subnet[/blue] (netuid)"
3551
+ )
3552
+ if not amount:
3553
+ amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to transfer")
3552
3554
 
3553
- if exclude_hotkeys:
3554
- excluded_hotkeys = parse_to_list(
3555
- exclude_hotkeys,
3556
- str,
3557
- "Hotkeys must be a comma-separated list of ss58s or hotkey names, e.g., "
3558
- "`--exclude-hotkeys 5Grw....,5Grw....`.",
3555
+ if dest_netuid is None:
3556
+ dest_netuid = IntPrompt.ask(
3557
+ "Enter the [blue]destination subnet[/blue] (netuid)"
3558
+ )
3559
+
3560
+ return self._run_command(
3561
+ move_stake.transfer_stake(
3562
+ wallet=wallet,
3563
+ subtensor=self.initialize_chain(network),
3564
+ origin_netuid=origin_netuid,
3565
+ dest_netuid=dest_netuid,
3566
+ dest_coldkey_ss58=dest_ss58,
3567
+ amount=amount,
3568
+ interactive_selection=interactive_selection,
3569
+ prompt=prompt,
3559
3570
  )
3571
+ )
3572
+
3573
+ def stake_swap(
3574
+ self,
3575
+ network: Optional[list[str]] = Options.network,
3576
+ wallet_name: Optional[str] = Options.wallet_name,
3577
+ wallet_path: Optional[str] = Options.wallet_path,
3578
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3579
+ origin_netuid: Optional[int] = typer.Option(
3580
+ None,
3581
+ "--origin-netuid",
3582
+ "-o",
3583
+ "--origin",
3584
+ help="The netuid to swap stake from",
3585
+ ),
3586
+ dest_netuid: Optional[int] = typer.Option(
3587
+ None,
3588
+ "--dest-netuid",
3589
+ "-d",
3590
+ "--dest",
3591
+ help="The netuid to swap stake to",
3592
+ ),
3593
+ amount: float = typer.Option(
3594
+ None,
3595
+ "--amount",
3596
+ "-a",
3597
+ help="Amount of stake to swap",
3598
+ ),
3599
+ swap_all: bool = typer.Option(
3600
+ False,
3601
+ "--swap-all",
3602
+ "--all",
3603
+ help="Swap all available stake",
3604
+ ),
3605
+ prompt: bool = Options.prompt,
3606
+ wait_for_inclusion: bool = Options.wait_for_inclusion,
3607
+ wait_for_finalization: bool = Options.wait_for_finalization,
3608
+ quiet: bool = Options.quiet,
3609
+ verbose: bool = Options.verbose,
3610
+ ):
3611
+ """
3612
+ Swap stake between different subnets while keeping the same coldkey-hotkey pair ownership.
3613
+
3614
+ This command allows you to:
3615
+ - Move stake from one subnet to another subnet
3616
+ - Keep the same coldkey ownership
3617
+ - Keep the same hotkey ownership
3618
+
3619
+ You can specify:
3620
+ - The origin subnet (--origin-netuid)
3621
+ - The destination subnet (--dest-netuid)
3622
+ - The amount to swap (--amount)
3623
+
3624
+ If no arguments are provided, an interactive selection menu will be shown.
3625
+
3626
+ EXAMPLE
3627
+
3628
+ Swap 100 TAO from subnet 1 to subnet 2:
3629
+ [green]$[/green] btcli stake swap --wallet-name default --wallet-hotkey default --origin-netuid 1 --dest-netuid 2 --amount 100
3630
+ """
3631
+ console.print(
3632
+ "[dim]This command moves stake from one subnet to another subnet while keeping the same coldkey-hotkey pair.[/dim]"
3633
+ )
3634
+ self.verbosity_handler(quiet, verbose)
3635
+
3636
+ wallet = self.wallet_ask(
3637
+ wallet_name,
3638
+ wallet_path,
3639
+ wallet_hotkey,
3640
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3641
+ validate=WV.WALLET_AND_HOTKEY,
3642
+ )
3643
+
3644
+ interactive_selection = False
3645
+ if origin_netuid is None and dest_netuid is None and not amount:
3646
+ interactive_selection = True
3560
3647
  else:
3561
- excluded_hotkeys = []
3648
+ if origin_netuid is None:
3649
+ origin_netuid = IntPrompt.ask(
3650
+ "Enter the [blue]origin subnet[/blue] (netuid)"
3651
+ )
3652
+ if dest_netuid is None:
3653
+ dest_netuid = IntPrompt.ask(
3654
+ "Enter the [blue]destination subnet[/blue] (netuid)"
3655
+ )
3656
+ if not amount and not swap_all:
3657
+ amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to swap")
3562
3658
 
3563
3659
  return self._run_command(
3564
- stake.unstake(
3565
- wallet,
3566
- self.initialize_chain(network),
3567
- hotkey_ss58_address,
3568
- all_hotkeys,
3569
- included_hotkeys,
3570
- excluded_hotkeys,
3571
- amount,
3572
- keep_stake,
3573
- unstake_all,
3574
- prompt,
3660
+ move_stake.swap_stake(
3661
+ wallet=wallet,
3662
+ subtensor=self.initialize_chain(network),
3663
+ origin_netuid=origin_netuid,
3664
+ destination_netuid=dest_netuid,
3665
+ amount=amount,
3666
+ swap_all=swap_all,
3667
+ interactive_selection=interactive_selection,
3668
+ prompt=prompt,
3669
+ wait_for_inclusion=wait_for_inclusion,
3670
+ wait_for_finalization=wait_for_finalization,
3575
3671
  )
3576
3672
  )
3577
3673
 
@@ -3611,7 +3707,7 @@ class CLIManager:
3611
3707
  wallet_name,
3612
3708
  wallet_path,
3613
3709
  wallet_hotkey,
3614
- ask_for=[WO.NAME, WO.HOTKEY],
3710
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3615
3711
  validate=WV.WALLET_AND_HOTKEY,
3616
3712
  )
3617
3713
 
@@ -3642,23 +3738,12 @@ class CLIManager:
3642
3738
  wallet_hotkey: str = Options.wallet_hotkey,
3643
3739
  wallet_path: str = Options.wallet_path,
3644
3740
  network: Optional[list[str]] = Options.network,
3645
- netuid: Optional[int] = typer.Option(
3646
- None,
3647
- help="The netuid of the subnet, (e.g. 4)",
3648
- prompt=False,
3649
- ),
3650
- all_netuids: bool = typer.Option(
3651
- False,
3652
- "--all-netuids",
3653
- "--all",
3654
- "--allnetuids",
3655
- help="When this flag is used it sets child hotkeys on all subnets.",
3656
- ),
3741
+ netuid: Optional[int] = Options.netuid_not_req,
3742
+ all_netuids: bool = Options.all_netuids,
3657
3743
  proportions: list[float] = typer.Option(
3658
3744
  [],
3659
3745
  "--proportions",
3660
3746
  "--prop",
3661
- "-p",
3662
3747
  help="Enter the stake weight proportions for the child hotkeys (sum should be less than or equal to 1)",
3663
3748
  prompt=False,
3664
3749
  ),
@@ -3678,17 +3763,10 @@ class CLIManager:
3678
3763
  EXAMPLE
3679
3764
 
3680
3765
  [green]$[/green] btcli stake child set -c 5FCL3gmjtQV4xxxxuEPEFQVhyyyyqYgNwX7drFLw7MSdBnxP -c 5Hp5dxxxxtGg7pu8dN2btyyyyVA1vELmM9dy8KQv3LxV8PA7 --hotkey default --netuid 1 -p 0.3 -p 0.7
3681
- """
3682
- self.verbosity_handler(quiet, verbose)
3683
- if all_netuids and netuid:
3684
- err_console.print("Specify either a netuid or `--all`, not both.")
3685
- raise typer.Exit()
3686
- if all_netuids:
3687
- netuid = None
3688
- elif not netuid:
3689
- netuid = IntPrompt.ask(
3690
- "Enter a netuid (leave blank for all)", default=None, show_default=True
3691
- )
3766
+ """
3767
+ self.verbosity_handler(quiet, verbose)
3768
+ netuid = get_optional_netuid(netuid, all_netuids)
3769
+
3692
3770
  children = list_prompt(
3693
3771
  children,
3694
3772
  str,
@@ -3896,7 +3974,8 @@ class CLIManager:
3896
3974
 
3897
3975
  if not param_name or not param_value:
3898
3976
  hyperparams = self._run_command(
3899
- sudo.get_hyperparameters(self.initialize_chain(network), netuid)
3977
+ sudo.get_hyperparameters(self.initialize_chain(network), netuid),
3978
+ exit_early=False,
3900
3979
  )
3901
3980
  if not hyperparams:
3902
3981
  raise typer.Exit()
@@ -3926,11 +4005,11 @@ class CLIManager:
3926
4005
 
3927
4006
  if not param_value:
3928
4007
  param_value = Prompt.ask(
3929
- f"Enter the new value for [dark_orange]{param_name}[/dark_orange] in the VALUE column format"
4008
+ f"Enter the new value for [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{param_name}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] in the VALUE column format"
3930
4009
  )
3931
4010
 
3932
4011
  wallet = self.wallet_ask(
3933
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
4012
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
3934
4013
  )
3935
4014
  return self._run_command(
3936
4015
  sudo.sudo_set_hyperparameter(
@@ -3952,8 +4031,6 @@ class CLIManager:
3952
4031
  """
3953
4032
  Shows a list of the hyperparameters for the specified subnet.
3954
4033
 
3955
- The output of this command is the same as that of `btcli subnets hyperparameters`.
3956
-
3957
4034
  EXAMPLE
3958
4035
 
3959
4036
  [green]$[/green] btcli sudo get --netuid 1
@@ -3963,98 +4040,448 @@ class CLIManager:
3963
4040
  sudo.get_hyperparameters(self.initialize_chain(network), netuid)
3964
4041
  )
3965
4042
 
3966
- def subnets_list(
4043
+ def sudo_senate(
3967
4044
  self,
3968
4045
  network: Optional[list[str]] = Options.network,
3969
- reuse_last: bool = Options.reuse_last,
3970
- html_output: bool = Options.html_output,
3971
4046
  quiet: bool = Options.quiet,
3972
4047
  verbose: bool = Options.verbose,
3973
4048
  ):
3974
4049
  """
3975
- List all subnets and their detailed information.
4050
+ Shows the Senate members of the Bittensor's governance protocol.
4051
+
4052
+ 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.
4053
+
4054
+ EXAMPLE
4055
+ [green]$[/green] btcli sudo senate
4056
+ """
4057
+ self.verbosity_handler(quiet, verbose)
4058
+ return self._run_command(sudo.get_senate(self.initialize_chain(network)))
3976
4059
 
3977
- This command displays a table with the below columns:
4060
+ def sudo_proposals(
4061
+ self,
4062
+ network: Optional[list[str]] = Options.network,
4063
+ quiet: bool = Options.quiet,
4064
+ verbose: bool = Options.verbose,
4065
+ ):
4066
+ """
4067
+ View active proposals for the senate in the Bittensor's governance protocol.
3978
4068
 
3979
- - NETUID: The subnet's netuid.
3980
- - N: The number of neurons (subnet validators and subnet miners) in the subnet.
3981
- - MAX_N: The maximum allowed number of neurons in the subnet.
3982
- - EMISSION: The percentage of emissions to the subnet as of the last tempo.
3983
- - TEMPO: The subnet's tempo, expressed in number of blocks.
3984
- - RECYCLE: The recycle register cost for this subnet.
3985
- - POW: The proof of work (PoW) difficulty.
3986
- - SUDO: The subnet owner's name or the owner's ss58 address.
4069
+ This command displays the details of ongoing proposals, including proposal hashes, votes, thresholds, and proposal data.
3987
4070
 
3988
4071
  EXAMPLE
4072
+ [green]$[/green] btcli sudo proposals
4073
+ """
4074
+ self.verbosity_handler(quiet, verbose)
4075
+ return self._run_command(
4076
+ sudo.proposals(self.initialize_chain(network), verbose)
4077
+ )
3989
4078
 
3990
- [green]$[/green] btcli subnets list
4079
+ def sudo_senate_vote(
4080
+ self,
4081
+ network: Optional[list[str]] = Options.network,
4082
+ wallet_name: Optional[str] = Options.wallet_name,
4083
+ wallet_path: Optional[str] = Options.wallet_path,
4084
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
4085
+ proposal: str = typer.Option(
4086
+ None,
4087
+ "--proposal",
4088
+ "--proposal-hash",
4089
+ prompt="Enter the proposal hash",
4090
+ help="The hash of the proposal to vote on.",
4091
+ ),
4092
+ prompt: bool = Options.prompt,
4093
+ quiet: bool = Options.quiet,
4094
+ verbose: bool = Options.verbose,
4095
+ vote: bool = typer.Option(
4096
+ None,
4097
+ "--vote-aye/--vote-nay",
4098
+ prompt="Enter y to vote Aye, or enter n to vote Nay",
4099
+ help="The vote casted on the proposal",
4100
+ ),
4101
+ ):
4102
+ """
4103
+ Cast a vote on an active proposal in Bittensor's governance protocol.
4104
+
4105
+ 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.
4106
+
4107
+ USAGE
4108
+ 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.
4109
+
4110
+ EXAMPLE
4111
+ [green]$[/green] btcli sudo senate_vote --proposal <proposal_hash>
3991
4112
  """
3992
4113
  self.verbosity_handler(quiet, verbose)
3993
- if (reuse_last or html_output) and self.config.get("use_cache") is False:
3994
- err_console.print(
3995
- "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. "
3996
- "Change the config to 'False' using `btcli config set`."
4114
+ wallet = self.wallet_ask(
4115
+ wallet_name,
4116
+ wallet_path,
4117
+ wallet_hotkey,
4118
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4119
+ validate=WV.WALLET_AND_HOTKEY,
4120
+ )
4121
+ return self._run_command(
4122
+ sudo.senate_vote(
4123
+ wallet, self.initialize_chain(network), proposal, vote, prompt
4124
+ )
4125
+ )
4126
+
4127
+ def sudo_set_take(
4128
+ self,
4129
+ network: Optional[list[str]] = Options.network,
4130
+ wallet_name: Optional[str] = Options.wallet_name,
4131
+ wallet_path: Optional[str] = Options.wallet_path,
4132
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
4133
+ take: float = typer.Option(None, help="The new take value."),
4134
+ quiet: bool = Options.quiet,
4135
+ verbose: bool = Options.verbose,
4136
+ ):
4137
+ """
4138
+ Allows users to change their delegate take percentage.
4139
+
4140
+ 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.
4141
+ The command makes sure the new take value is within 0-18% range.
4142
+
4143
+ EXAMPLE
4144
+ [green]$[/green] btcli sudo set-take --wallet-name my_wallet --wallet-hotkey my_hotkey
4145
+ """
4146
+ max_value = 0.18
4147
+ min_value = 0.00
4148
+ self.verbosity_handler(quiet, verbose)
4149
+
4150
+ wallet = self.wallet_ask(
4151
+ wallet_name,
4152
+ wallet_path,
4153
+ wallet_hotkey,
4154
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4155
+ validate=WV.WALLET_AND_HOTKEY,
4156
+ )
4157
+
4158
+ self._run_command(
4159
+ sudo.display_current_take(self.initialize_chain(network), wallet),
4160
+ exit_early=False,
4161
+ )
4162
+
4163
+ if not take:
4164
+ take = FloatPrompt.ask(
4165
+ f"Enter [blue]take value[/blue] (0.18 for 18%) [blue]Min: {min_value} Max: {max_value}"
4166
+ )
4167
+ if not (min_value <= take <= max_value):
4168
+ print_error(
4169
+ f"Take value must be between {min_value} and {max_value}. Provided value: {take}"
3997
4170
  )
3998
4171
  raise typer.Exit()
3999
- if reuse_last:
4000
- subtensor = None
4001
- else:
4002
- subtensor = self.initialize_chain(network)
4172
+
4173
+ return self._run_command(
4174
+ sudo.set_take(wallet, self.initialize_chain(network), take)
4175
+ )
4176
+
4177
+ def sudo_get_take(
4178
+ self,
4179
+ network: Optional[list[str]] = Options.network,
4180
+ wallet_name: Optional[str] = Options.wallet_name,
4181
+ wallet_path: Optional[str] = Options.wallet_path,
4182
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey,
4183
+ quiet: bool = Options.quiet,
4184
+ verbose: bool = Options.verbose,
4185
+ ):
4186
+ """
4187
+ Allows users to check their delegate take percentage.
4188
+
4189
+ This command can be used to fetch the delegate take of your hotkey.
4190
+
4191
+ EXAMPLE
4192
+ [green]$[/green] btcli sudo get-take --wallet-name my_wallet --wallet-hotkey my_hotkey
4193
+ """
4194
+ self.verbosity_handler(quiet, verbose)
4195
+
4196
+ wallet = self.wallet_ask(
4197
+ wallet_name,
4198
+ wallet_path,
4199
+ wallet_hotkey,
4200
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4201
+ validate=WV.WALLET_AND_HOTKEY,
4202
+ )
4203
+
4204
+ self._run_command(
4205
+ sudo.display_current_take(self.initialize_chain(network), wallet)
4206
+ )
4207
+
4208
+ def subnets_list(
4209
+ self,
4210
+ network: Optional[list[str]] = Options.network,
4211
+ quiet: bool = Options.quiet,
4212
+ verbose: bool = Options.verbose,
4213
+ live_mode: bool = Options.live,
4214
+ ):
4215
+ """
4216
+ List all subnets and their detailed information.
4217
+
4218
+ [bold]Common Examples:[/bold]
4219
+
4220
+ 1. List all subnets:
4221
+ [green]$[/green] btcli subnets list
4222
+
4223
+ 2. List all subnets in live mode:
4224
+ [green]$[/green] btcli subnets list --live
4225
+
4226
+ [bold]Output Columns:[/bold]
4227
+ • [white]Netuid[/white] - Subnet identifier number
4228
+ • [white]Name[/white] - Subnet name with currency symbol (τ/α/β etc)
4229
+ • [white]Price (τ_in/α_in)[/white] - Exchange rate (TAO per alpha token)
4230
+ • [white]Market Cap (α * Price)[/white] - Total value in TAO (alpha tokens × price)
4231
+ • [white]Emission (τ)[/white] - TAO rewards emitted per block to subnet
4232
+ • [white]P (τ_in, α_in)[/white] - Pool reserves (Tao reserves, alpha reserves) in liquidity pool
4233
+ • [white]Stake (α_out)[/white] - Total staked alpha tokens across all hotkeys (alpha outstanding)
4234
+ • [white]Supply (α)[/white] - Circulating alpha token supply
4235
+ • [white]Tempo (k/n)[/white] - Block interval for subnet updates
4236
+
4237
+ EXAMPLE
4238
+
4239
+ [green]$[/green] btcli subnets list
4240
+ """
4241
+ self.verbosity_handler(quiet, verbose)
4242
+ subtensor = self.initialize_chain(network)
4003
4243
  return self._run_command(
4004
4244
  subnets.subnets_list(
4005
4245
  subtensor,
4006
- reuse_last,
4007
- html_output,
4246
+ False, # reuse-last
4247
+ False, # html-output
4008
4248
  not self.config.get("use_cache", True),
4249
+ verbose,
4250
+ live_mode,
4251
+ )
4252
+ )
4253
+
4254
+ def subnets_price(
4255
+ self,
4256
+ network: Optional[list[str]] = Options.network,
4257
+ netuids: str = typer.Option(
4258
+ None,
4259
+ "--netuids",
4260
+ "--netuid",
4261
+ "-n",
4262
+ help="Netuid(s) to show the price for.",
4263
+ ),
4264
+ interval_hours: int = typer.Option(
4265
+ 24,
4266
+ "--interval-hours",
4267
+ "--interval",
4268
+ help="The number of hours to show the historical price for.",
4269
+ ),
4270
+ all_netuids: bool = typer.Option(
4271
+ False,
4272
+ "--all-netuids",
4273
+ "--all",
4274
+ help="Show the price for all subnets.",
4275
+ ),
4276
+ log_scale: bool = typer.Option(
4277
+ False,
4278
+ "--log-scale",
4279
+ "--log",
4280
+ help="Show the price in log scale.",
4281
+ ),
4282
+ html_output: bool = Options.html_output,
4283
+ ):
4284
+ """
4285
+ Shows the historical price of a subnet for the past 24 hours.
4286
+
4287
+ This command displays the historical price of a subnet for the past 24 hours.
4288
+ If the `--all` flag is used, the command will display the price for all subnets in html format.
4289
+ If the `--html` flag is used, the command will display the price in an HTML chart.
4290
+ If the `--log-scale` flag is used, the command will display the price in log scale.
4291
+ If no html flag is used, the command will display the price in the cli.
4292
+
4293
+ EXAMPLE
4294
+
4295
+ [green]$[/green] btcli subnets price --netuid 1
4296
+ [green]$[/green] btcli subnets price --netuid 1 --html --log
4297
+ [green]$[/green] btcli subnets price --all --html
4298
+ [green]$[/green] btcli subnets price --netuids 1,2,3,4 --html
4299
+ """
4300
+ if netuids:
4301
+ netuids = parse_to_list(
4302
+ netuids,
4303
+ int,
4304
+ "Netuids must be a comma-separated list of ints, e.g., `--netuids 1,2,3,4`.",
4305
+ )
4306
+ if all_netuids and netuids:
4307
+ print_error("Cannot specify both --netuid and --all-netuids")
4308
+ raise typer.Exit()
4309
+
4310
+ if not netuids and not all_netuids:
4311
+ netuids = Prompt.ask(
4312
+ "Enter the [blue]netuid(s)[/blue] to view the price of in comma-separated format [dim](or Press Enter to view all subnets)[/dim]",
4313
+ )
4314
+ if not netuids:
4315
+ all_netuids = True
4316
+ html_output = True
4317
+ else:
4318
+ netuids = parse_to_list(
4319
+ netuids,
4320
+ int,
4321
+ "Netuids must be a comma-separated list of ints, e.g., `--netuids 1,2,3,4`.",
4322
+ )
4323
+
4324
+ if all_netuids:
4325
+ html_output = True
4326
+
4327
+ if html_output and is_linux():
4328
+ print_linux_dependency_message()
4329
+
4330
+ return self._run_command(
4331
+ price.price(
4332
+ self.initialize_chain(network),
4333
+ netuids,
4334
+ all_netuids,
4335
+ interval_hours,
4336
+ html_output,
4337
+ log_scale,
4338
+ )
4339
+ )
4340
+
4341
+ def subnets_show(
4342
+ self,
4343
+ network: Optional[list[str]] = Options.network,
4344
+ netuid: int = Options.netuid,
4345
+ sort: bool = typer.Option(
4346
+ False,
4347
+ "--sort",
4348
+ help="Sort the subnets by uid.",
4349
+ ),
4350
+ quiet: bool = Options.quiet,
4351
+ verbose: bool = Options.verbose,
4352
+ prompt: bool = Options.prompt,
4353
+ ):
4354
+ """
4355
+ Displays detailed information about a subnet including participants and their state.
4356
+
4357
+ EXAMPLE
4358
+
4359
+ [green]$[/green] btcli subnets list
4360
+ """
4361
+ self.verbosity_handler(quiet, verbose)
4362
+ subtensor = self.initialize_chain(network)
4363
+ return self._run_command(
4364
+ subnets.show(
4365
+ subtensor=subtensor,
4366
+ netuid=netuid,
4367
+ sort=sort,
4368
+ max_rows=None,
4369
+ delegate_selection=False,
4370
+ verbose=verbose,
4371
+ prompt=prompt,
4009
4372
  )
4010
4373
  )
4011
4374
 
4012
- def subnets_lock_cost(
4375
+ def subnets_burn_cost(
4013
4376
  self,
4014
4377
  network: Optional[list[str]] = Options.network,
4015
4378
  quiet: bool = Options.quiet,
4016
4379
  verbose: bool = Options.verbose,
4017
4380
  ):
4018
4381
  """
4019
- Shows the required amount of TAO to be locked for creating a new subnet, i.e., cost of registering a new subnet.
4382
+ Shows the required amount of TAO to be recycled for creating a new subnet, i.e., cost of registering a new subnet.
4020
4383
 
4021
4384
  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.
4022
4385
 
4023
4386
  EXAMPLE
4024
4387
 
4025
- [green]$[/green] btcli subnets lock_cost
4388
+ [green]$[/green] btcli subnets burn_cost
4026
4389
  """
4027
4390
  self.verbosity_handler(quiet, verbose)
4028
- return self._run_command(subnets.lock_cost(self.initialize_chain(network)))
4391
+ return self._run_command(subnets.burn_cost(self.initialize_chain(network)))
4029
4392
 
4030
4393
  def subnets_create(
4031
4394
  self,
4032
4395
  wallet_name: str = Options.wallet_name,
4033
4396
  wallet_path: str = Options.wallet_path,
4397
+ wallet_hotkey: str = Options.wallet_hotkey,
4034
4398
  network: Optional[list[str]] = Options.network,
4399
+ subnet_name: Optional[str] = typer.Option(
4400
+ None, "--subnet-name", "--name", help="Name of the subnet"
4401
+ ),
4402
+ github_repo: Optional[str] = typer.Option(
4403
+ None, "--github-repo", "--repo", help="GitHub repository URL"
4404
+ ),
4405
+ subnet_contact: Optional[str] = typer.Option(
4406
+ None,
4407
+ "--subnet-contact",
4408
+ "--contact",
4409
+ "--email",
4410
+ help="Contact email for subnet",
4411
+ ),
4412
+ subnet_url: Optional[str] = typer.Option(
4413
+ None, "--subnet-url", "--url", help="Subnet URL"
4414
+ ),
4415
+ discord: Optional[str] = typer.Option(
4416
+ None, "--discord-handle", "--discord", help="Discord handle"
4417
+ ),
4418
+ description: Optional[str] = typer.Option(
4419
+ None, "--description", help="Description"
4420
+ ),
4421
+ additional_info: Optional[str] = typer.Option(
4422
+ None, "--additional-info", help="Additional information"
4423
+ ),
4035
4424
  prompt: bool = Options.prompt,
4036
4425
  quiet: bool = Options.quiet,
4037
4426
  verbose: bool = Options.verbose,
4038
4427
  ):
4039
4428
  """
4040
- Registers a new subnet.
4429
+ Registers a new subnet on the network.
4041
4430
 
4042
- EXAMPLE
4431
+ This command allows you to create a new subnet and set the subnet's identity.
4432
+ You also have the option to set your own identity after the registration is complete.
4433
+
4434
+ [bold]Common Examples:[/bold]
4043
4435
 
4436
+ 1. Interactive subnet creation:
4044
4437
  [green]$[/green] btcli subnets create
4438
+
4439
+ 2. Create with GitHub repo and contact email:
4440
+ [green]$[/green] btcli subnets create --subnet-name MySubnet --github-repo https://github.com/myorg/mysubnet --subnet-contact team@mysubnet.net
4045
4441
  """
4046
4442
  self.verbosity_handler(quiet, verbose)
4047
4443
  wallet = self.wallet_ask(
4048
4444
  wallet_name,
4049
4445
  wallet_path,
4050
- None,
4051
- ask_for=[WO.NAME],
4052
- validate=WV.WALLET,
4446
+ wallet_hotkey,
4447
+ ask_for=[
4448
+ WO.NAME,
4449
+ WO.HOTKEY,
4450
+ WO.PATH,
4451
+ ],
4452
+ validate=WV.WALLET_AND_HOTKEY,
4053
4453
  )
4054
- return self._run_command(
4055
- subnets.create(wallet, self.initialize_chain(network), prompt)
4454
+ identity = prompt_for_subnet_identity(
4455
+ subnet_name=subnet_name,
4456
+ github_repo=github_repo,
4457
+ subnet_contact=subnet_contact,
4458
+ subnet_url=subnet_url,
4459
+ discord=discord,
4460
+ description=description,
4461
+ additional=additional_info,
4462
+ )
4463
+ success = self._run_command(
4464
+ subnets.create(wallet, self.initialize_chain(network), identity, prompt),
4465
+ exit_early=False,
4056
4466
  )
4057
4467
 
4468
+ if success and prompt:
4469
+ set_id = Confirm.ask(
4470
+ "[dark_sea_green3]Do you want to set/update your identity?",
4471
+ default=False,
4472
+ show_default=True,
4473
+ )
4474
+ if set_id:
4475
+ self.wallet_set_id(
4476
+ wallet_name=wallet.name,
4477
+ wallet_hotkey=wallet.hotkey,
4478
+ wallet_path=wallet.path,
4479
+ network=network,
4480
+ prompt=prompt,
4481
+ quiet=quiet,
4482
+ verbose=verbose,
4483
+ )
4484
+
4058
4485
  def subnets_pow_register(
4059
4486
  self,
4060
4487
  wallet_name: Optional[str] = Options.wallet_name,
@@ -4128,7 +4555,7 @@ class CLIManager:
4128
4555
  wallet_name,
4129
4556
  wallet_path,
4130
4557
  wallet_hotkey,
4131
- ask_for=[WO.NAME, WO.HOTKEY],
4558
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4132
4559
  validate=WV.WALLET_AND_HOTKEY,
4133
4560
  ),
4134
4561
  self.initialize_chain(network),
@@ -4257,6 +4684,12 @@ class CLIManager:
4257
4684
  )
4258
4685
  raise typer.Exit()
4259
4686
 
4687
+ # For Rao games
4688
+ effective_network = get_effective_network(self.config, network)
4689
+ if is_rao_network(effective_network):
4690
+ print_error("This command is disabled on the 'rao' network.")
4691
+ raise typer.Exit()
4692
+
4260
4693
  if reuse_last:
4261
4694
  if netuid is not None:
4262
4695
  console.print("Cannot specify netuid when using `--reuse-last`")
@@ -4359,7 +4792,7 @@ class CLIManager:
4359
4792
  wallet_name,
4360
4793
  wallet_path,
4361
4794
  wallet_hotkey,
4362
- ask_for=[WO.NAME, WO.HOTKEY],
4795
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4363
4796
  validate=WV.WALLET_AND_HOTKEY,
4364
4797
  )
4365
4798
 
@@ -4457,7 +4890,7 @@ class CLIManager:
4457
4890
  wallet_name,
4458
4891
  wallet_path,
4459
4892
  wallet_hotkey,
4460
- ask_for=[WO.NAME, WO.HOTKEY],
4893
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
4461
4894
  validate=WV.WALLET_AND_HOTKEY,
4462
4895
  )
4463
4896
  return self._run_command(