bittensor-cli 9.0.0rc2__py3-none-any.whl → 9.0.0rc4__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.
bittensor_cli/cli.py CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  import asyncio
3
3
  import curses
4
+ import importlib
4
5
  import os.path
5
6
  import re
6
7
  import ssl
@@ -32,7 +33,13 @@ from async_substrate_interface.errors import SubstrateRequestException
32
33
  from bittensor_cli.src.commands import sudo, wallets
33
34
  from bittensor_cli.src.commands import weights as weights_cmds
34
35
  from bittensor_cli.src.commands.subnets import price, subnets
35
- from bittensor_cli.src.commands.stake import children_hotkeys, stake, move
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
+ )
36
43
  from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
37
44
  from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
38
45
  from bittensor_cli.src.bittensor.utils import (
@@ -50,9 +57,9 @@ from bittensor_cli.src.bittensor.utils import (
50
57
  prompt_for_subnet_identity,
51
58
  print_linux_dependency_message,
52
59
  is_linux,
60
+ validate_rate_tolerance,
53
61
  )
54
62
  from typing_extensions import Annotated
55
- from textwrap import dedent
56
63
  from websockets import ConnectionClosed, InvalidHandshake
57
64
  from yaml import safe_dump, safe_load
58
65
 
@@ -65,7 +72,7 @@ except ImportError:
65
72
  pass
66
73
 
67
74
 
68
- __version__ = "9.0.0rc2"
75
+ __version__ = "9.0.0rc4"
69
76
 
70
77
 
71
78
  _core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0)
@@ -169,13 +176,13 @@ class Options:
169
176
  )
170
177
  netuid = typer.Option(
171
178
  None,
172
- help="The netuid of the subnet in the root network, (e.g. 1).",
179
+ help="The netuid of the subnet in the network, (e.g. 1).",
173
180
  prompt=True,
174
181
  callback=validate_netuid,
175
182
  )
176
183
  netuid_not_req = typer.Option(
177
184
  None,
178
- help="The netuid of the subnet in the root network, (e.g. 1).",
185
+ help="The netuid of the subnet in the network, (e.g. 1).",
179
186
  prompt=False,
180
187
  )
181
188
  all_netuids = typer.Option(
@@ -238,6 +245,28 @@ class Options:
238
245
  help="Create wallet from uri (e.g. 'Alice', 'Bob', 'Charlie', 'Dave', 'Eve')",
239
246
  callback=validate_uri,
240
247
  )
248
+ rate_tolerance = typer.Option(
249
+ None,
250
+ "--slippage",
251
+ "--slippage-tolerance",
252
+ "--tolerance",
253
+ help="Set the rate tolerance percentage for transactions (default: 0.05%).",
254
+ callback=validate_rate_tolerance,
255
+ )
256
+ safe_staking = typer.Option(
257
+ None,
258
+ "--safe-staking/--no-safe-staking",
259
+ "--safe/--unsafe",
260
+ help="Enable or disable safe staking mode (default: enabled).",
261
+ )
262
+ allow_partial_stake = typer.Option(
263
+ None,
264
+ "--allow-partial-stake/--no-allow-partial-stake",
265
+ "--partial/--no-partial",
266
+ "--allow/--not-allow",
267
+ "--allow-partial/--not-partial",
268
+ help="Enable or disable partial stake mode (default: disabled).",
269
+ )
241
270
 
242
271
 
243
272
  def list_prompt(init_var: list, list_type: type, help_text: str) -> list:
@@ -504,6 +533,7 @@ class CLIManager:
504
533
  subnets_app: typer.Typer
505
534
  weights_app: typer.Typer
506
535
  utils_app = typer.Typer(epilog=_epilog)
536
+ asyncio_runner = asyncio
507
537
 
508
538
  def __init__(self):
509
539
  self.config = {
@@ -512,25 +542,29 @@ class CLIManager:
512
542
  "wallet_hotkey": None,
513
543
  "network": None,
514
544
  "use_cache": True,
515
- "metagraph_cols": {
516
- "UID": True,
517
- "GLOBAL_STAKE": True,
518
- "LOCAL_STAKE": True,
519
- "STAKE_WEIGHT": True,
520
- "RANK": True,
521
- "TRUST": True,
522
- "CONSENSUS": True,
523
- "INCENTIVE": True,
524
- "DIVIDENDS": True,
525
- "EMISSION": True,
526
- "VTRUST": True,
527
- "VAL": True,
528
- "UPDATED": True,
529
- "ACTIVE": True,
530
- "AXON": True,
531
- "HOTKEY": True,
532
- "COLDKEY": True,
533
- },
545
+ "rate_tolerance": None,
546
+ "safe_staking": True,
547
+ "allow_partial_stake": False,
548
+ # Commenting this out as this needs to get updated
549
+ # "metagraph_cols": {
550
+ # "UID": True,
551
+ # "GLOBAL_STAKE": True,
552
+ # "LOCAL_STAKE": True,
553
+ # "STAKE_WEIGHT": True,
554
+ # "RANK": True,
555
+ # "TRUST": True,
556
+ # "CONSENSUS": True,
557
+ # "INCENTIVE": True,
558
+ # "DIVIDENDS": True,
559
+ # "EMISSION": True,
560
+ # "VTRUST": True,
561
+ # "VAL": True,
562
+ # "UPDATED": True,
563
+ # "ACTIVE": True,
564
+ # "AXON": True,
565
+ # "HOTKEY": True,
566
+ # "COLDKEY": True,
567
+ # },
534
568
  }
535
569
  self.subtensor = None
536
570
  self.config_base_path = os.path.expanduser(defaults.config.base_path)
@@ -629,7 +663,7 @@ class CLIManager:
629
663
  self.config_app.command("set")(self.set_config)
630
664
  self.config_app.command("get")(self.get_config)
631
665
  self.config_app.command("clear")(self.del_config)
632
- self.config_app.command("metagraph")(self.metagraph_config)
666
+ self.config_app.command("metagraph", hidden=True)(self.metagraph_config)
633
667
 
634
668
  # wallet commands
635
669
  self.wallet_app.command(
@@ -923,9 +957,12 @@ class CLIManager:
923
957
  ConnectionClosed,
924
958
  SubstrateRequestException,
925
959
  KeyboardInterrupt,
960
+ RuntimeError,
926
961
  ) as e:
927
962
  if isinstance(e, SubstrateRequestException):
928
963
  err_console.print(str(e))
964
+ elif isinstance(e, RuntimeError):
965
+ pass # Temporarily to handle loop bound issues
929
966
  verbose_console.print(traceback.format_exc())
930
967
  except Exception as e:
931
968
  err_console.print(f"An unknown error has occurred: {e}")
@@ -937,15 +974,12 @@ class CLIManager:
937
974
  try:
938
975
  raise typer.Exit()
939
976
  except Exception as e: # ensures we always exit cleanly
940
- if not isinstance(e, (typer.Exit, RuntimeError)): # temporarily to handle multiple run commands in one session
977
+ if not isinstance(
978
+ e, (typer.Exit, RuntimeError)
979
+ ): # temporarily to handle multiple run commands in one session
941
980
  err_console.print(f"An unknown error has occurred: {e}")
942
981
 
943
- if sys.version_info < (3, 10):
944
- # For Python 3.9 or lower
945
- return asyncio.get_event_loop().run_until_complete(_run())
946
- else:
947
- # For Python 3.10 or higher
948
- return asyncio.run(_run())
982
+ return self.asyncio_runner(_run())
949
983
 
950
984
  def main_callback(
951
985
  self,
@@ -996,6 +1030,20 @@ class CLIManager:
996
1030
  if k in self.config.keys():
997
1031
  self.config[k] = v
998
1032
 
1033
+ if sys.version_info < (3, 10):
1034
+ # For Python 3.9 or lower
1035
+ self.asyncio_runner = asyncio.get_event_loop().run_until_complete
1036
+ else:
1037
+ try:
1038
+ uvloop = importlib.import_module("uvloop")
1039
+ if sys.version_info >= (3, 11):
1040
+ self.asyncio_runner = uvloop.run
1041
+ else:
1042
+ uvloop.install()
1043
+ self.asyncio_runner = asyncio.run
1044
+ except ModuleNotFoundError:
1045
+ self.asyncio_runner = asyncio.run
1046
+
999
1047
  def verbosity_handler(self, quiet: bool, verbose: bool):
1000
1048
  if quiet and verbose:
1001
1049
  err_console.print("Cannot specify both `--quiet` and `--verbose`")
@@ -1052,9 +1100,44 @@ class CLIManager:
1052
1100
  help="Disable caching of some commands. This will disable the `--reuse-last` and `--html` flags on "
1053
1101
  "commands such as `subnets metagraph`, `stake show` and `subnets list`.",
1054
1102
  ),
1103
+ rate_tolerance: Optional[float] = typer.Option(
1104
+ None,
1105
+ "--slippage",
1106
+ "--slippage-tolerance",
1107
+ "--tolerance",
1108
+ help="Set the rate tolerance percentage for transactions (e.g. 0.1 for 0.1%).",
1109
+ ),
1110
+ safe_staking: Optional[bool] = typer.Option(
1111
+ None,
1112
+ "--safe-staking/--no-safe-staking",
1113
+ "--safe/--unsafe",
1114
+ help="Enable or disable safe staking mode.",
1115
+ ),
1116
+ allow_partial_stake: Optional[bool] = typer.Option(
1117
+ None,
1118
+ "--allow-partial-stake/--no-allow-partial-stake",
1119
+ "--partial/--no-partial",
1120
+ "--allow/--not-allow",
1121
+ ),
1055
1122
  ):
1056
1123
  """
1057
- Sets the values in the config file. To set the metagraph configuration, use the command `btcli config metagraph`
1124
+ Sets or updates configuration values in the BTCLI config file.
1125
+
1126
+ This command allows you to set default values that will be used across all BTCLI commands.
1127
+
1128
+ USAGE
1129
+ Interactive mode:
1130
+ [green]$[/green] btcli config set
1131
+
1132
+ Set specific values:
1133
+ [green]$[/green] btcli config set --wallet-name default --network finney
1134
+ [green]$[/green] btcli config set --safe-staking --rate-tolerance 0.1
1135
+
1136
+ [bold]NOTE[/bold]:
1137
+ - Network values can be network names (e.g., 'finney', 'test') or websocket URLs
1138
+ - Rate tolerance is specified as a decimal (e.g., 0.05 for 0.05%)
1139
+ - Changes are saved to ~/.bittensor/btcli.yaml
1140
+ - Use '[green]$[/green] btcli config get' to view current settings
1058
1141
  """
1059
1142
  args = {
1060
1143
  "wallet_name": wallet_name,
@@ -1062,8 +1145,11 @@ class CLIManager:
1062
1145
  "wallet_hotkey": wallet_hotkey,
1063
1146
  "network": network,
1064
1147
  "use_cache": use_cache,
1148
+ "rate_tolerance": rate_tolerance,
1149
+ "safe_staking": safe_staking,
1150
+ "allow_partial_stake": allow_partial_stake,
1065
1151
  }
1066
- bools = ["use_cache"]
1152
+ bools = ["use_cache", "safe_staking", "allow_partial_stake"]
1067
1153
  if all(v is None for v in args.values()):
1068
1154
  # Print existing configs
1069
1155
  self.get_config()
@@ -1087,6 +1173,20 @@ class CLIManager:
1087
1173
  default=True,
1088
1174
  )
1089
1175
  self.config[arg] = nc
1176
+
1177
+ elif arg == "rate_tolerance":
1178
+ while True:
1179
+ val = FloatPrompt.ask(
1180
+ f"What percentage would you like to set for [red]{arg}[/red]?\nValues are percentages (e.g. 0.05 for 5%)",
1181
+ default=0.05,
1182
+ )
1183
+ try:
1184
+ validated_val = validate_rate_tolerance(val)
1185
+ self.config[arg] = validated_val
1186
+ break
1187
+ except typer.BadParameter as e:
1188
+ print_error(str(e))
1189
+ continue
1090
1190
  else:
1091
1191
  val = Prompt.ask(
1092
1192
  f"What value would you like to assign to [red]{arg}[/red]?"
@@ -1144,6 +1244,18 @@ class CLIManager:
1144
1244
  wallet_hotkey: bool = typer.Option(False, *Options.wallet_hotkey.param_decls),
1145
1245
  network: bool = typer.Option(False, *Options.network.param_decls),
1146
1246
  use_cache: bool = typer.Option(False, "--cache"),
1247
+ rate_tolerance: bool = typer.Option(
1248
+ False, "--slippage", "--slippage-tolerance", "--tolerance"
1249
+ ),
1250
+ safe_staking: bool = typer.Option(
1251
+ False, "--safe-staking/--no-safe-staking", "--safe/--unsafe"
1252
+ ),
1253
+ allow_partial_stake: bool = typer.Option(
1254
+ False,
1255
+ "--allow-partial-stake/--no-allow-partial-stake",
1256
+ "--partial/--no-partial",
1257
+ "--allow/--not-allow",
1258
+ ),
1147
1259
  all_items: bool = typer.Option(False, "--all"),
1148
1260
  ):
1149
1261
  """
@@ -1173,6 +1285,9 @@ class CLIManager:
1173
1285
  "wallet_hotkey": wallet_hotkey,
1174
1286
  "network": network,
1175
1287
  "use_cache": use_cache,
1288
+ "rate_tolerance": rate_tolerance,
1289
+ "safe_staking": safe_staking,
1290
+ "allow_partial_stake": allow_partial_stake,
1176
1291
  }
1177
1292
 
1178
1293
  # If no specific argument is provided, iterate over all
@@ -1234,6 +1349,8 @@ class CLIManager:
1234
1349
  else:
1235
1350
  if value in Constants.networks:
1236
1351
  value = value + f" ({Constants.network_map[value]})"
1352
+ if key == "rate_tolerance":
1353
+ value = f"{value} ({value*100}%)" if value is not None else "None"
1237
1354
 
1238
1355
  elif key in deprecated_configs:
1239
1356
  continue
@@ -1246,13 +1363,112 @@ class CLIManager:
1246
1363
  table.add_row(str(key), str(value), "")
1247
1364
 
1248
1365
  console.print(table)
1249
- console.print(
1250
- dedent(
1251
- """
1252
- [red]Deprecation notice[/red]: The chain endpoint config is now deprecated. You can use the network config to pass chain endpoints.
1253
- """
1366
+
1367
+ def ask_rate_tolerance(
1368
+ self,
1369
+ rate_tolerance: Optional[float],
1370
+ ) -> float:
1371
+ """
1372
+ Gets rate tolerance from args, config, or default.
1373
+
1374
+ Args:
1375
+ rate_tolerance (Optional[float]): Explicitly provided slippage value
1376
+
1377
+ Returns:
1378
+ float: rate tolerance value
1379
+ """
1380
+ if rate_tolerance is not None:
1381
+ console.print(
1382
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{rate_tolerance} ({rate_tolerance*100}%)[/bold cyan]."
1254
1383
  )
1255
- )
1384
+ return rate_tolerance
1385
+ elif self.config.get("rate_tolerance") is not None:
1386
+ config_slippage = self.config["rate_tolerance"]
1387
+ console.print(
1388
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{config_slippage} ({config_slippage*100}%)[/bold cyan] (from config)."
1389
+ )
1390
+ return config_slippage
1391
+ else:
1392
+ console.print(
1393
+ "[dim][blue]Rate tolerance[/blue]: "
1394
+ + f"[bold cyan]{defaults.rate_tolerance} ({defaults.rate_tolerance*100}%)[/bold cyan] "
1395
+ + "by default. Set this using "
1396
+ + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1397
+ + "or "
1398
+ + "[dark_sea_green3 italic]`--tolerance`[/dark_sea_green3 italic] flag[/dim]"
1399
+ )
1400
+ return defaults.rate_tolerance
1401
+
1402
+ def ask_safe_staking(
1403
+ self,
1404
+ safe_staking: Optional[bool],
1405
+ ) -> bool:
1406
+ """
1407
+ Gets safe staking setting from args, config, or default.
1408
+
1409
+ Args:
1410
+ safe_staking (Optional[bool]): Explicitly provided safe staking value
1411
+
1412
+ Returns:
1413
+ bool: Safe staking setting
1414
+ """
1415
+ if safe_staking is not None:
1416
+ console.print(
1417
+ f"[dim][blue]Safe staking[/blue]: [bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan]."
1418
+ )
1419
+ return safe_staking
1420
+ elif self.config.get("safe_staking") is not None:
1421
+ safe_staking = self.config["safe_staking"]
1422
+ console.print(
1423
+ f"[dim][blue]Safe staking[/blue]: [bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan] (from config)."
1424
+ )
1425
+ return safe_staking
1426
+ else:
1427
+ safe_staking = True
1428
+ console.print(
1429
+ "[dim][blue]Safe staking[/blue]: "
1430
+ + f"[bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan] "
1431
+ + "by default. Set this using "
1432
+ + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1433
+ + "or "
1434
+ + "[dark_sea_green3 italic]`--safe/--unsafe`[/dark_sea_green3 italic] flag[/dim]"
1435
+ )
1436
+ return safe_staking
1437
+
1438
+ def ask_partial_stake(
1439
+ self,
1440
+ allow_partial_stake: Optional[bool],
1441
+ ) -> bool:
1442
+ """
1443
+ Gets partial stake setting from args, config, or default.
1444
+
1445
+ Args:
1446
+ allow_partial_stake (Optional[bool]): Explicitly provided partial stake value
1447
+
1448
+ Returns:
1449
+ bool: Partial stake setting
1450
+ """
1451
+ if allow_partial_stake is not None:
1452
+ console.print(
1453
+ f"[dim][blue]Partial staking[/blue]: [bold cyan]{'enabled' if allow_partial_stake else 'disabled'}[/bold cyan]."
1454
+ )
1455
+ return allow_partial_stake
1456
+ elif self.config.get("allow_partial_stake") is not None:
1457
+ config_partial = self.config["allow_partial_stake"]
1458
+ console.print(
1459
+ f"[dim][blue]Partial staking[/blue]: [bold cyan]{'enabled' if config_partial else 'disabled'}[/bold cyan] (from config)."
1460
+ )
1461
+ return config_partial
1462
+ else:
1463
+ console.print(
1464
+ "[dim][blue]Partial staking[/blue]: "
1465
+ + f"[bold cyan]{'enabled' if allow_partial_stake else 'disabled'}[/bold cyan] "
1466
+ + "by default. Set this using "
1467
+ + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1468
+ + "or "
1469
+ + "[dark_sea_green3 italic]`--partial/--no-partial`[/dark_sea_green3 italic] flag[/dim]"
1470
+ )
1471
+ return False
1256
1472
 
1257
1473
  def wallet_ask(
1258
1474
  self,
@@ -1410,50 +1626,9 @@ class CLIManager:
1410
1626
 
1411
1627
  USAGE
1412
1628
 
1413
- The command offers various options to customize the output. Users can filter the displayed data by specific
1414
- netuid, sort by different criteria, and choose to include all the wallets in the user's wallet path location.
1415
- The output is presented in a tabular format with the following columns:
1416
-
1417
- - COLDKEY: The SS58 address of the coldkey.
1418
-
1419
- - HOTKEY: The SS58 address of the hotkey.
1420
-
1421
- - UID: Unique identifier of the neuron.
1422
-
1423
- - ACTIVE: Indicates if the neuron is active.
1424
-
1425
- - STAKE(τ): Amount of stake in the neuron, in TAO.
1426
-
1427
- - RANK: The rank of the neuron within the network.
1428
-
1429
- - TRUST: Trust score of the neuron.
1430
-
1431
- - CONSENSUS: Consensus score of the neuron.
1432
-
1433
- - INCENTIVE: Incentive score of the neuron.
1434
-
1435
- - DIVIDENDS: Dividends earned by the neuron.
1436
-
1437
- - EMISSION(p): Emission received by the neuron, expressed in rho.
1438
-
1439
- - VTRUST: Validator trust score of the neuron.
1440
-
1441
- - VPERMIT: Indicates if the neuron has a validator permit.
1442
-
1443
- - UPDATED: Time since last update.
1444
-
1445
- - AXON: IP address and port of the neuron.
1446
-
1447
- - HOTKEY_SS58: Human-readable representation of the hotkey.
1448
-
1449
-
1450
- # EXAMPLE:
1451
-
1452
1629
  [green]$[/green] btcli wallet overview
1453
1630
 
1454
- [green]$[/green] btcli wallet overview --all --sort-by stake --sort-order descending
1455
-
1456
- [green]$[/green] btcli wallet overview -in hk1,hk2 --sort-by stake
1631
+ [green]$[/green] btcli wallet overview --all
1457
1632
 
1458
1633
  [bold]NOTE[/bold]: This command is read-only and does not modify the blockchain state or account configuration.
1459
1634
  It provides a quick and comprehensive view of the user's network presence, making it useful for monitoring account status,
@@ -2561,7 +2736,25 @@ class CLIManager:
2561
2736
  no_prompt: bool = Options.prompt,
2562
2737
  # TODO add: all-wallets, reuse_last, html_output
2563
2738
  ):
2564
- """List all stake accounts for wallet."""
2739
+ """
2740
+ Display detailed stake information for a wallet across all subnets.
2741
+
2742
+ Shows stake allocations, exchange rates, and emissions for each hotkey.
2743
+
2744
+ [bold]Common Examples:[/bold]
2745
+
2746
+ 1. Basic stake overview:
2747
+ [green]$[/green] btcli stake list --wallet.name my_wallet
2748
+
2749
+ 2. Live updating view with refresh:
2750
+ [green]$[/green] btcli stake list --wallet.name my_wallet --live
2751
+
2752
+ 3. View specific coldkey by address:
2753
+ [green]$[/green] btcli stake list --ss58 5Dk...X3q
2754
+
2755
+ 4. Verbose output with full values:
2756
+ [green]$[/green] btcli stake list --wallet.name my_wallet --verbose
2757
+ """
2565
2758
  self.verbosity_handler(quiet, verbose)
2566
2759
 
2567
2760
  wallet = None
@@ -2586,7 +2779,7 @@ class CLIManager:
2586
2779
  )
2587
2780
 
2588
2781
  return self._run_command(
2589
- stake.stake_list(
2782
+ list_stake.stake_list(
2590
2783
  wallet,
2591
2784
  coldkey_ss58,
2592
2785
  self.initialize_chain(network),
@@ -2608,12 +2801,6 @@ class CLIManager:
2608
2801
  amount: float = typer.Option(
2609
2802
  0.0, "--amount", help="The amount of TAO to stake"
2610
2803
  ),
2611
- max_stake: float = typer.Option(
2612
- 0.0,
2613
- "--max-stake",
2614
- "-m",
2615
- help="Stake is sent to a hotkey only until the hotkey's total stake is less than or equal to this maximum staked TAO. If a hotkey already has stake greater than this amount, then stake is not added to this hotkey.",
2616
- ),
2617
2804
  include_hotkeys: str = typer.Option(
2618
2805
  "",
2619
2806
  "--include-hotkeys",
@@ -2639,22 +2826,50 @@ class CLIManager:
2639
2826
  wallet_path: str = Options.wallet_path,
2640
2827
  wallet_hotkey: str = Options.wallet_hotkey,
2641
2828
  network: Optional[list[str]] = Options.network,
2829
+ rate_tolerance: Optional[float] = Options.rate_tolerance,
2830
+ safe_staking: Optional[bool] = Options.safe_staking,
2831
+ allow_partial_stake: Optional[bool] = Options.allow_partial_stake,
2642
2832
  prompt: bool = Options.prompt,
2643
2833
  quiet: bool = Options.quiet,
2644
2834
  verbose: bool = Options.verbose,
2645
2835
  ):
2646
2836
  """
2647
- Stake TAO to one or more hotkeys associated with the user's coldkey.
2837
+ Stake TAO to one or more hotkeys on specific netuids with your coldkey.
2648
2838
 
2649
- This command is used by a subnet validator to stake to their own hotkey. Compare this command with "btcli root delegate" that is typically run by a TAO holder to delegate their TAO to a delegate's hotkey.
2839
+ Stake is always added through your coldkey's free balance. For stake movement, please see `[green]$[/green] btcli stake move` command.
2650
2840
 
2651
- This command is used by a subnet validator to allocate stake TAO to their different hotkeys, securing their position and influence on the network.
2841
+ [bold]Common Examples:[/bold]
2652
2842
 
2653
- EXAMPLE
2843
+ 1. Interactive staking (guided prompts):
2844
+ [green]$[/green] btcli stake add
2845
+
2846
+ 2. Safe staking with rate tolerance of 10% with partial transaction disabled:
2847
+ [green]$[/green] btcli stake add --amount 100 --netuid 1 --safe --tolerance 0.1 --no-partial
2848
+
2849
+ 3. Allow partial stake if rates change with tolerance of 10%:
2850
+ [green]$[/green] btcli stake add --amount 300 --safe --partial --tolerance 0.1
2851
+
2852
+ 4. Unsafe staking with no rate protection:
2853
+ [green]$[/green] btcli stake add --amount 300 --netuid 1 --unsafe
2854
+
2855
+ 5. Stake to multiple hotkeys:
2856
+ [green]$[/green] btcli stake add --amount 200 --include-hotkeys hk_ss58_1,hk_ss58_2,hk_ss58_3
2857
+
2858
+ 6. Stake all balance to a subnet:
2859
+ [green]$[/green] btcli stake add --all --netuid 3
2860
+
2861
+ [bold]Safe Staking Parameters:[/bold]
2862
+ • [blue]--safe[/blue]: Enables rate tolerance checks
2863
+ • [blue]--tolerance[/blue]: Maximum % rate change allowed (0.05 = 5%)
2864
+ • [blue]--partial[/blue]: Complete partial stake if rates exceed tolerance
2654
2865
 
2655
- [green]$[/green] btcli stake add --amount 100 --wallet-name <my_wallet> --wallet-hotkey <my_hotkey>
2656
2866
  """
2657
2867
  self.verbosity_handler(quiet, verbose)
2868
+ safe_staking = self.ask_safe_staking(safe_staking)
2869
+ if safe_staking:
2870
+ rate_tolerance = self.ask_rate_tolerance(rate_tolerance)
2871
+ allow_partial_stake = self.ask_partial_stake(allow_partial_stake)
2872
+ console.print("\n")
2658
2873
  netuid = get_optional_netuid(netuid, all_netuids)
2659
2874
 
2660
2875
  if stake_all and amount:
@@ -2704,6 +2919,7 @@ class CLIManager:
2704
2919
  subnets.show(
2705
2920
  subtensor=self.initialize_chain(network),
2706
2921
  netuid=netuid,
2922
+ sort=False,
2707
2923
  max_rows=12,
2708
2924
  prompt=False,
2709
2925
  delegate_selection=True,
@@ -2764,8 +2980,8 @@ class CLIManager:
2764
2980
  excluded_hotkeys = []
2765
2981
 
2766
2982
  # TODO: Ask amount for each subnet explicitly if more than one
2767
- if not stake_all and not amount and not max_stake:
2768
- free_balance, staked_balance = self._run_command(
2983
+ if not stake_all and not amount:
2984
+ free_balance = self._run_command(
2769
2985
  wallets.wallet_balance(
2770
2986
  wallet, self.initialize_chain(network), False, None
2771
2987
  ),
@@ -2793,18 +3009,19 @@ class CLIManager:
2793
3009
  raise typer.Exit()
2794
3010
 
2795
3011
  return self._run_command(
2796
- stake.stake_add(
3012
+ add_stake.stake_add(
2797
3013
  wallet,
2798
3014
  self.initialize_chain(network),
2799
3015
  netuid,
2800
3016
  stake_all,
2801
3017
  amount,
2802
- False,
2803
3018
  prompt,
2804
- max_stake,
2805
3019
  all_hotkeys,
2806
3020
  included_hotkeys,
2807
3021
  excluded_hotkeys,
3022
+ safe_staking,
3023
+ rate_tolerance,
3024
+ allow_partial_stake,
2808
3025
  )
2809
3026
  )
2810
3027
 
@@ -2837,12 +3054,6 @@ class CLIManager:
2837
3054
  "",
2838
3055
  help="The ss58 address of the hotkey to unstake from.",
2839
3056
  ),
2840
- keep_stake: float = typer.Option(
2841
- 0.0,
2842
- "--keep-stake",
2843
- "--keep",
2844
- help="Sets the maximum amount of TAO to remain staked in each hotkey.",
2845
- ),
2846
3057
  include_hotkeys: str = typer.Option(
2847
3058
  "",
2848
3059
  "--include-hotkeys",
@@ -2861,6 +3072,9 @@ class CLIManager:
2861
3072
  help="When set, this command unstakes from all the hotkeys associated with the wallet. Do not use if specifying "
2862
3073
  "hotkeys in `--include-hotkeys`.",
2863
3074
  ),
3075
+ rate_tolerance: Optional[float] = Options.rate_tolerance,
3076
+ safe_staking: Optional[bool] = Options.safe_staking,
3077
+ allow_partial_stake: Optional[bool] = Options.allow_partial_stake,
2864
3078
  prompt: bool = Options.prompt,
2865
3079
  interactive: bool = typer.Option(
2866
3080
  False,
@@ -2872,20 +3086,42 @@ class CLIManager:
2872
3086
  verbose: bool = Options.verbose,
2873
3087
  ):
2874
3088
  """
2875
- Unstake TAO from one or more hotkeys and transfer them back to the user's coldkey.
3089
+ Unstake TAO from one or more hotkeys and transfer them back to the user's coldkey wallet.
2876
3090
 
2877
- This command is used to withdraw TAO previously staked to different hotkeys.
3091
+ This command is used to withdraw TAO or Alpha stake from different hotkeys.
2878
3092
 
2879
- EXAMPLE
3093
+ [bold]Common Examples:[/bold]
3094
+
3095
+ 1. Interactive unstaking (guided prompts):
3096
+ [green]$[/green] btcli stake remove
3097
+
3098
+ 2. Safe unstaking with 10% rate tolerance:
3099
+ [green]$[/green] btcli stake remove --amount 100 --netuid 1 --safe --tolerance 0.1
3100
+
3101
+ 3. Allow partial unstake if rates change:
3102
+ [green]$[/green] btcli stake remove --amount 300 --safe --partial
3103
+
3104
+ 4. Unstake from multiple hotkeys:
3105
+ [green]$[/green] btcli stake remove --amount 200 --include-hotkeys hk1,hk2,hk3
2880
3106
 
2881
- [green]$[/green] btcli stake remove --amount 100 -in hk1,hk2
3107
+ 5. Unstake all from a hotkey:
3108
+ [green]$[/green] btcli stake remove --all
2882
3109
 
2883
- [blue bold]Note[/blue bold]: This command is for users who wish to reallocate their stake or withdraw them from the network. It allows for flexible management of TAO stake across different neurons (hotkeys) on the network.
3110
+ 6. Unstake all Alpha from a hotkey and stake to Root:
3111
+ [green]$[/green] btcli stake remove --all-alpha
3112
+
3113
+ [bold]Safe Staking Parameters:[/bold]
3114
+ • [blue]--safe[/blue]: Enables rate tolerance checks during unstaking
3115
+ • [blue]--tolerance[/blue]: Max allowed rate change (0.05 = 5%)
3116
+ • [blue]--partial[/blue]: Complete partial unstake if rates exceed tolerance
2884
3117
  """
2885
3118
  self.verbosity_handler(quiet, verbose)
2886
- # TODO: Coldkey related unstakes need to be updated. Patching for now.
2887
- unstake_all_alpha = False
2888
- unstake_all = False
3119
+ if not unstake_all and not unstake_all_alpha:
3120
+ safe_staking = self.ask_safe_staking(safe_staking)
3121
+ if safe_staking:
3122
+ rate_tolerance = self.ask_rate_tolerance(rate_tolerance)
3123
+ allow_partial_stake = self.ask_partial_stake(allow_partial_stake)
3124
+ console.print("\n")
2889
3125
 
2890
3126
  if interactive and any(
2891
3127
  [hotkey_ss58_address, include_hotkeys, exclude_hotkeys, all_hotkeys]
@@ -2961,6 +3197,52 @@ class CLIManager:
2961
3197
  validate=WV.WALLET_AND_HOTKEY,
2962
3198
  )
2963
3199
 
3200
+ elif unstake_all or unstake_all_alpha:
3201
+ if not wallet_name:
3202
+ wallet_name = Prompt.ask(
3203
+ "Enter the [blue]wallet name[/blue]",
3204
+ default=self.config.get("wallet_name") or defaults.wallet.name,
3205
+ )
3206
+ if include_hotkeys:
3207
+ if len(include_hotkeys) > 1:
3208
+ print_error("Cannot unstake_all from multiple hotkeys at once.")
3209
+ raise typer.Exit()
3210
+ elif is_valid_ss58_address(include_hotkeys[0]):
3211
+ hotkey_ss58_address = include_hotkeys[0]
3212
+ else:
3213
+ print_error("Invalid hotkey ss58 address.")
3214
+ raise typer.Exit()
3215
+ else:
3216
+ hotkey_or_ss58 = Prompt.ask(
3217
+ "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from",
3218
+ default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey,
3219
+ )
3220
+ if is_valid_ss58_address(hotkey_or_ss58):
3221
+ hotkey_ss58_address = hotkey_or_ss58
3222
+ wallet = self.wallet_ask(
3223
+ wallet_name,
3224
+ wallet_path,
3225
+ wallet_hotkey,
3226
+ ask_for=[WO.NAME, WO.PATH],
3227
+ )
3228
+ else:
3229
+ wallet_hotkey = hotkey_or_ss58
3230
+ wallet = self.wallet_ask(
3231
+ wallet_name,
3232
+ wallet_path,
3233
+ wallet_hotkey,
3234
+ ask_for=[WO.NAME, WO.PATH, WO.HOTKEY],
3235
+ validate=WV.WALLET_AND_HOTKEY,
3236
+ )
3237
+ return self._run_command(
3238
+ remove_stake.unstake_all(
3239
+ wallet=wallet,
3240
+ subtensor=self.initialize_chain(network),
3241
+ hotkey_ss58_address=hotkey_ss58_address,
3242
+ unstake_all_alpha=unstake_all_alpha,
3243
+ prompt=prompt,
3244
+ )
3245
+ )
2964
3246
  elif (
2965
3247
  all_hotkeys
2966
3248
  or include_hotkeys
@@ -3003,20 +3285,20 @@ class CLIManager:
3003
3285
  excluded_hotkeys = []
3004
3286
 
3005
3287
  return self._run_command(
3006
- stake.unstake(
3007
- wallet,
3008
- self.initialize_chain(network),
3009
- hotkey_ss58_address,
3010
- all_hotkeys,
3011
- included_hotkeys,
3012
- excluded_hotkeys,
3013
- amount,
3014
- keep_stake,
3015
- unstake_all,
3016
- prompt,
3017
- interactive,
3288
+ remove_stake.unstake(
3289
+ wallet=wallet,
3290
+ subtensor=self.initialize_chain(network),
3291
+ hotkey_ss58_address=hotkey_ss58_address,
3292
+ all_hotkeys=all_hotkeys,
3293
+ include_hotkeys=included_hotkeys,
3294
+ exclude_hotkeys=excluded_hotkeys,
3295
+ amount=amount,
3296
+ prompt=prompt,
3297
+ interactive=interactive,
3018
3298
  netuid=netuid,
3019
- unstake_all_alpha=unstake_all_alpha,
3299
+ safe_staking=safe_staking,
3300
+ rate_tolerance=rate_tolerance,
3301
+ allow_partial_stake=allow_partial_stake,
3020
3302
  )
3021
3303
  )
3022
3304
 
@@ -3159,7 +3441,7 @@ class CLIManager:
3159
3441
  )
3160
3442
 
3161
3443
  return self._run_command(
3162
- move.move_stake(
3444
+ move_stake.move_stake(
3163
3445
  subtensor=self.initialize_chain(network),
3164
3446
  wallet=wallet,
3165
3447
  origin_netuid=origin_netuid,
@@ -3277,7 +3559,7 @@ class CLIManager:
3277
3559
  )
3278
3560
 
3279
3561
  return self._run_command(
3280
- move.transfer_stake(
3562
+ move_stake.transfer_stake(
3281
3563
  wallet=wallet,
3282
3564
  subtensor=self.initialize_chain(network),
3283
3565
  origin_netuid=origin_netuid,
@@ -3376,7 +3658,7 @@ class CLIManager:
3376
3658
  amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to swap")
3377
3659
 
3378
3660
  return self._run_command(
3379
- move.swap_stake(
3661
+ move_stake.swap_stake(
3380
3662
  wallet=wallet,
3381
3663
  subtensor=self.initialize_chain(network),
3382
3664
  origin_netuid=origin_netuid,
@@ -3737,8 +4019,6 @@ class CLIManager:
3737
4019
  """
3738
4020
  Shows a list of the hyperparameters for the specified subnet.
3739
4021
 
3740
- The output of this command is the same as that of `btcli subnets hyperparameters`.
3741
-
3742
4022
  EXAMPLE
3743
4023
 
3744
4024
  [green]$[/green] btcli sudo get --netuid 1
@@ -3914,40 +4194,37 @@ class CLIManager:
3914
4194
  def subnets_list(
3915
4195
  self,
3916
4196
  network: Optional[list[str]] = Options.network,
3917
- # reuse_last: bool = Options.reuse_last,
3918
- # html_output: bool = Options.html_output,
3919
4197
  quiet: bool = Options.quiet,
3920
4198
  verbose: bool = Options.verbose,
3921
4199
  live_mode: bool = Options.live,
3922
4200
  ):
3923
4201
  """
3924
- List all subnets and their detailed information.
4202
+ List all subnets and their detailed information.
3925
4203
 
3926
- This command displays a table with the below columns:
4204
+ [bold]Common Examples:[/bold]
3927
4205
 
3928
- - NETUID: The subnet's netuid.
3929
- - N: The number of neurons (subnet validators and subnet miners) in the subnet.
3930
- - MAX_N: The maximum allowed number of neurons in the subnet.
3931
- - EMISSION: The percentage of emissions to the subnet as of the last tempo.
3932
- - TEMPO: The subnet's tempo, expressed in number of blocks.
3933
- - RECYCLE: The recycle register cost for this subnet.
3934
- - POW: The proof of work (PoW) difficulty.
3935
- - SUDO: The subnet owner's name or the owner's ss58 address.
4206
+ 1. List all subnets:
4207
+ [green]$[/green] btcli subnets list
3936
4208
 
3937
- EXAMPLE
4209
+ 2. List all subnets in live mode:
4210
+ [green]$[/green] btcli subnets list --live
3938
4211
 
3939
- [green]$[/green] btcli subnets list
4212
+ [bold]Output Columns:[/bold]
4213
+ • [white]Netuid[/white] - Subnet identifier number
4214
+ • [white]Name[/white] - Subnet name with currency symbol (τ/α/β etc)
4215
+ • [white]Price (τ_in/α_in)[/white] - Exchange rate (TAO per alpha token)
4216
+ • [white]Market Cap (α * Price)[/white] - Total value in TAO (alpha tokens × price)
4217
+ • [white]Emission (τ)[/white] - TAO rewards emitted per block to subnet
4218
+ • [white]P (τ_in, α_in)[/white] - Pool reserves (Tao reserves, alpha reserves) in liquidity pool
4219
+ • [white]Stake (α_out)[/white] - Total staked alpha tokens across all hotkeys (alpha outstanding)
4220
+ • [white]Supply (α)[/white] - Circulating alpha token supply
4221
+ • [white]Tempo (k/n)[/white] - Block interval for subnet updates
4222
+
4223
+ EXAMPLE
4224
+
4225
+ [green]$[/green] btcli subnets list
3940
4226
  """
3941
4227
  self.verbosity_handler(quiet, verbose)
3942
- # if (reuse_last or html_output) and self.config.get("use_cache") is False:
3943
- # err_console.print(
3944
- # "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. "
3945
- # "Change the config to 'False' using `btcli config set`."
3946
- # )
3947
- # raise typer.Exit()
3948
- # if reuse_last:
3949
- # subtensor = None
3950
- # else:
3951
4228
  subtensor = self.initialize_chain(network)
3952
4229
  return self._run_command(
3953
4230
  subnets.subnets_list(
@@ -4051,6 +4328,11 @@ class CLIManager:
4051
4328
  self,
4052
4329
  network: Optional[list[str]] = Options.network,
4053
4330
  netuid: int = Options.netuid,
4331
+ sort: bool = typer.Option(
4332
+ False,
4333
+ "--sort",
4334
+ help="Sort the subnets by uid.",
4335
+ ),
4054
4336
  quiet: bool = Options.quiet,
4055
4337
  verbose: bool = Options.verbose,
4056
4338
  prompt: bool = Options.prompt,
@@ -4066,8 +4348,11 @@ class CLIManager:
4066
4348
  subtensor = self.initialize_chain(network)
4067
4349
  return self._run_command(
4068
4350
  subnets.show(
4069
- subtensor,
4070
- netuid,
4351
+ subtensor=subtensor,
4352
+ netuid=netuid,
4353
+ sort=sort,
4354
+ max_rows=None,
4355
+ delegate_selection=False,
4071
4356
  verbose=verbose,
4072
4357
  prompt=prompt,
4073
4358
  )
@@ -4127,11 +4412,18 @@ class CLIManager:
4127
4412
  verbose: bool = Options.verbose,
4128
4413
  ):
4129
4414
  """
4130
- Registers a new subnet.
4415
+ Registers a new subnet on the network.
4131
4416
 
4132
- EXAMPLE
4417
+ This command allows you to create a new subnet and set the subnet's identity.
4418
+ You also have the option to set your own identity after the registration is complete.
4133
4419
 
4420
+ [bold]Common Examples:[/bold]
4421
+
4422
+ 1. Interactive subnet creation:
4134
4423
  [green]$[/green] btcli subnets create
4424
+
4425
+ 2. Create with GitHub repo and contact email:
4426
+ [green]$[/green] btcli subnets create --subnet-name MySubnet --github-repo https://github.com/myorg/mysubnet --subnet-contact team@mysubnet.net
4135
4427
  """
4136
4428
  self.verbosity_handler(quiet, verbose)
4137
4429
  wallet = self.wallet_ask(