bittensor-cli 9.0.3__py3-none-any.whl → 9.1.1__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
@@ -32,7 +32,7 @@ from bittensor_cli.version import __version__, __version_as_int__
32
32
  from bittensor_cli.src.bittensor import utils
33
33
  from bittensor_cli.src.bittensor.balances import Balance
34
34
  from async_substrate_interface.errors import SubstrateRequestException
35
- from bittensor_cli.src.commands import sudo, wallets
35
+ from bittensor_cli.src.commands import sudo, wallets, view
36
36
  from bittensor_cli.src.commands import weights as weights_cmds
37
37
  from bittensor_cli.src.commands.subnets import price, subnets
38
38
  from bittensor_cli.src.commands.stake import (
@@ -109,6 +109,18 @@ class Options:
109
109
  "--wallet.hotkey",
110
110
  help="Hotkey of the wallet",
111
111
  )
112
+ wallet_hotkey_ss58 = typer.Option(
113
+ None,
114
+ "--hotkey",
115
+ "--hotkey-ss58",
116
+ "-H",
117
+ "--wallet_hotkey",
118
+ "--wallet_hotkey_ss58",
119
+ "--wallet-hotkey",
120
+ "--wallet-hotkey-ss58",
121
+ "--wallet.hotkey",
122
+ help="Hotkey name or SS58 address of the hotkey",
123
+ )
112
124
  mnemonic = typer.Option(
113
125
  None,
114
126
  help='Mnemonic used to regenerate your key. For example: "horse cart dog ..."',
@@ -193,8 +205,7 @@ class Options:
193
205
  )
194
206
  wait_for_finalization = typer.Option(
195
207
  True,
196
- help="If `True`, waits until the transaction is finalized "
197
- "on the blockchain.",
208
+ help="If `True`, waits until the transaction is finalized on the blockchain.",
198
209
  )
199
210
  prompt = typer.Option(
200
211
  True,
@@ -247,6 +258,15 @@ class Options:
247
258
  "--allow-partial/--not-partial",
248
259
  help="Enable or disable partial stake mode (default: disabled).",
249
260
  )
261
+ dashboard_path = typer.Option(
262
+ None,
263
+ "--dashboard-path",
264
+ "--dashboard_path",
265
+ "--dash_path",
266
+ "--dash.path",
267
+ "--dashboard.path",
268
+ help="Path to save the dashboard HTML file. For example: `~/.bittensor/dashboard`.",
269
+ )
250
270
 
251
271
 
252
272
  def list_prompt(init_var: list, list_type: type, help_text: str) -> list:
@@ -513,6 +533,7 @@ class CLIManager:
513
533
  subnets_app: typer.Typer
514
534
  weights_app: typer.Typer
515
535
  utils_app = typer.Typer(epilog=_epilog)
536
+ view_app: typer.Typer
516
537
  asyncio_runner = asyncio
517
538
 
518
539
  def __init__(self):
@@ -525,6 +546,7 @@ class CLIManager:
525
546
  "rate_tolerance": None,
526
547
  "safe_staking": True,
527
548
  "allow_partial_stake": False,
549
+ "dashboard_path": None,
528
550
  # Commenting this out as this needs to get updated
529
551
  # "metagraph_cols": {
530
552
  # "UID": True,
@@ -562,6 +584,7 @@ class CLIManager:
562
584
  self.sudo_app = typer.Typer(epilog=_epilog)
563
585
  self.subnets_app = typer.Typer(epilog=_epilog)
564
586
  self.weights_app = typer.Typer(epilog=_epilog)
587
+ self.view_app = typer.Typer(epilog=_epilog)
565
588
 
566
589
  # config alias
567
590
  self.app.add_typer(
@@ -639,6 +662,14 @@ class CLIManager:
639
662
  self.utils_app, name="utils", no_args_is_help=True, hidden=True
640
663
  )
641
664
 
665
+ # view app
666
+ self.app.add_typer(
667
+ self.view_app,
668
+ name="view",
669
+ short_help="HTML view commands",
670
+ no_args_is_help=True,
671
+ )
672
+
642
673
  # config commands
643
674
  self.config_app.command("set")(self.set_config)
644
675
  self.config_app.command("get")(self.get_config)
@@ -806,6 +837,11 @@ class CLIManager:
806
837
  "commit", rich_help_panel=HELP_PANELS["WEIGHTS"]["COMMIT_REVEAL"]
807
838
  )(self.weights_commit)
808
839
 
840
+ # view commands
841
+ self.view_app.command(
842
+ "dashboard", rich_help_panel=HELP_PANELS["VIEW"]["DASHBOARD"]
843
+ )(self.view_dashboard)
844
+
809
845
  # Sub command aliases
810
846
  # Weights
811
847
  self.wallet_app.command(
@@ -1105,6 +1141,7 @@ class CLIManager:
1105
1141
  "--partial/--no-partial",
1106
1142
  "--allow/--not-allow",
1107
1143
  ),
1144
+ dashboard_path: Optional[str] = Options.dashboard_path,
1108
1145
  ):
1109
1146
  """
1110
1147
  Sets or updates configuration values in the BTCLI config file.
@@ -1134,6 +1171,7 @@ class CLIManager:
1134
1171
  "rate_tolerance": rate_tolerance,
1135
1172
  "safe_staking": safe_staking,
1136
1173
  "allow_partial_stake": allow_partial_stake,
1174
+ "dashboard_path": dashboard_path,
1137
1175
  }
1138
1176
  bools = ["use_cache", "safe_staking", "allow_partial_stake"]
1139
1177
  if all(v is None for v in args.values()):
@@ -1243,6 +1281,7 @@ class CLIManager:
1243
1281
  "--allow/--not-allow",
1244
1282
  ),
1245
1283
  all_items: bool = typer.Option(False, "--all"),
1284
+ dashboard_path: Optional[str] = Options.dashboard_path,
1246
1285
  ):
1247
1286
  """
1248
1287
  Clears the fields in the config file and sets them to 'None'.
@@ -1274,6 +1313,7 @@ class CLIManager:
1274
1313
  "rate_tolerance": rate_tolerance,
1275
1314
  "safe_staking": safe_staking,
1276
1315
  "allow_partial_stake": allow_partial_stake,
1316
+ "dashboard_path": dashboard_path,
1277
1317
  }
1278
1318
 
1279
1319
  # If no specific argument is provided, iterate over all
@@ -1336,7 +1376,7 @@ class CLIManager:
1336
1376
  if value in Constants.networks:
1337
1377
  value = value + f" ({Constants.network_map[value]})"
1338
1378
  if key == "rate_tolerance":
1339
- value = f"{value} ({value*100}%)" if value is not None else "None"
1379
+ value = f"{value} ({value * 100}%)" if value is not None else "None"
1340
1380
 
1341
1381
  elif key in deprecated_configs:
1342
1382
  continue
@@ -1365,19 +1405,19 @@ class CLIManager:
1365
1405
  """
1366
1406
  if rate_tolerance is not None:
1367
1407
  console.print(
1368
- f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{rate_tolerance} ({rate_tolerance*100}%)[/bold cyan]."
1408
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{rate_tolerance} ({rate_tolerance * 100}%)[/bold cyan]."
1369
1409
  )
1370
1410
  return rate_tolerance
1371
1411
  elif self.config.get("rate_tolerance") is not None:
1372
1412
  config_slippage = self.config["rate_tolerance"]
1373
1413
  console.print(
1374
- f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{config_slippage} ({config_slippage*100}%)[/bold cyan] (from config)."
1414
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{config_slippage} ({config_slippage * 100}%)[/bold cyan] (from config)."
1375
1415
  )
1376
1416
  return config_slippage
1377
1417
  else:
1378
1418
  console.print(
1379
1419
  "[dim][blue]Rate tolerance[/blue]: "
1380
- + f"[bold cyan]{defaults.rate_tolerance} ({defaults.rate_tolerance*100}%)[/bold cyan] "
1420
+ + f"[bold cyan]{defaults.rate_tolerance} ({defaults.rate_tolerance * 100}%)[/bold cyan] "
1381
1421
  + "by default. Set this using "
1382
1422
  + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1383
1423
  + "or "
@@ -2923,7 +2963,7 @@ class CLIManager:
2923
2963
  ),
2924
2964
  exit_early=False,
2925
2965
  )
2926
- if selected_hotkey is None:
2966
+ if not selected_hotkey:
2927
2967
  print_error("No delegate selected. Exiting.")
2928
2968
  raise typer.Exit()
2929
2969
  include_hotkeys = selected_hotkey
@@ -3034,14 +3074,12 @@ class CLIManager:
3034
3074
  False,
3035
3075
  "--unstake-all",
3036
3076
  "--all",
3037
- hidden=True,
3038
3077
  help="When set, this command unstakes all staked TAO + Alpha from the all hotkeys.",
3039
3078
  ),
3040
3079
  unstake_all_alpha: bool = typer.Option(
3041
3080
  False,
3042
3081
  "--unstake-all-alpha",
3043
3082
  "--all-alpha",
3044
- hidden=True,
3045
3083
  help="When set, this command unstakes all staked Alpha from the all hotkeys.",
3046
3084
  ),
3047
3085
  amount: float = typer.Option(
@@ -3323,7 +3361,7 @@ class CLIManager:
3323
3361
  network: Optional[list[str]] = Options.network,
3324
3362
  wallet_name: Optional[str] = Options.wallet_name,
3325
3363
  wallet_path: Optional[str] = Options.wallet_path,
3326
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3364
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey_ss58,
3327
3365
  origin_netuid: Optional[int] = typer.Option(
3328
3366
  None, "--origin-netuid", help="Origin netuid"
3329
3367
  ),
@@ -3343,6 +3381,8 @@ class CLIManager:
3343
3381
  False, "--stake-all", "--all", help="Stake all", prompt=False
3344
3382
  ),
3345
3383
  prompt: bool = Options.prompt,
3384
+ quiet: bool = Options.quiet,
3385
+ verbose: bool = Options.verbose,
3346
3386
  ):
3347
3387
  """
3348
3388
  Move staked TAO between hotkeys while keeping the same coldkey ownership.
@@ -3364,6 +3404,7 @@ class CLIManager:
3364
3404
 
3365
3405
  [green]$[/green] btcli stake move
3366
3406
  """
3407
+ self.verbosity_handler(quiet, verbose)
3367
3408
  console.print(
3368
3409
  "[dim]This command moves stake from one hotkey to another hotkey while keeping the same coldkey.[/dim]"
3369
3410
  )
@@ -3476,7 +3517,7 @@ class CLIManager:
3476
3517
  network: Optional[list[str]] = Options.network,
3477
3518
  wallet_name: Optional[str] = Options.wallet_name,
3478
3519
  wallet_path: Optional[str] = Options.wallet_path,
3479
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3520
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey_ss58,
3480
3521
  origin_netuid: Optional[int] = typer.Option(
3481
3522
  None,
3482
3523
  "--origin-netuid",
@@ -3500,6 +3541,9 @@ class CLIManager:
3500
3541
  "-a",
3501
3542
  help="Amount of stake to transfer",
3502
3543
  ),
3544
+ stake_all: bool = typer.Option(
3545
+ False, "--stake-all", "--all", help="Stake all", prompt=False
3546
+ ),
3503
3547
  prompt: bool = Options.prompt,
3504
3548
  quiet: bool = Options.quiet,
3505
3549
  verbose: bool = Options.verbose,
@@ -3517,6 +3561,8 @@ class CLIManager:
3517
3561
  - The destination subnet (--dest-netuid)
3518
3562
  - The destination wallet/address (--dest)
3519
3563
  - The amount to transfer (--amount)
3564
+ - The origin wallet (--wallet-name)
3565
+ - The origin hotkey wallet/address (--wallet-hotkey)
3520
3566
 
3521
3567
  If no arguments are provided, an interactive selection menu will be shown.
3522
3568
 
@@ -3525,14 +3571,37 @@ class CLIManager:
3525
3571
  Transfer 100 TAO from subnet 1 to subnet 2:
3526
3572
  [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest wallet2 --amount 100
3527
3573
 
3528
- Using SS58 address:
3574
+ Using Destination SS58 address:
3529
3575
  [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngEP9H7qjkKgHLcK --amount 100
3576
+
3577
+ Using Origin hotkey SS58 address (useful when transferring stake from a delegate):
3578
+ [green]$[/green] btcli stake transfer --wallet-hotkey 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngEP9H7qjkKgHLcK --wallet-name sample_wallet
3579
+
3580
+ Transfer all available stake from origin hotkey:
3581
+ [green]$[/green] btcli stake transfer --all --origin-netuid 1 --dest-netuid 2
3530
3582
  """
3531
3583
  console.print(
3532
3584
  "[dim]This command transfers stake from one coldkey to another while keeping the same hotkey.[/dim]"
3533
3585
  )
3534
3586
  self.verbosity_handler(quiet, verbose)
3535
3587
 
3588
+ if not dest_ss58:
3589
+ dest_ss58 = Prompt.ask(
3590
+ "Enter the [blue]destination wallet name[/blue] or [blue]coldkey SS58 address[/blue]"
3591
+ )
3592
+
3593
+ if is_valid_ss58_address(dest_ss58):
3594
+ dest_ss58 = dest_ss58
3595
+ else:
3596
+ dest_wallet = self.wallet_ask(
3597
+ dest_ss58,
3598
+ wallet_path,
3599
+ None,
3600
+ ask_for=[WO.NAME, WO.PATH],
3601
+ validate=WV.WALLET,
3602
+ )
3603
+ dest_ss58 = dest_wallet.coldkeypub.ss58_address
3604
+
3536
3605
  if not wallet_name:
3537
3606
  wallet_name = Prompt.ask(
3538
3607
  "Enter the [blue]origin wallet name[/blue]",
@@ -3542,13 +3611,16 @@ class CLIManager:
3542
3611
  wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3543
3612
  )
3544
3613
 
3614
+ interactive_selection = False
3545
3615
  if not wallet_hotkey:
3546
3616
  origin_hotkey = Prompt.ask(
3547
- "Enter the [blue]origin hotkey[/blue] name or "
3548
- "[blue]ss58 address[/blue] where the stake will be moved from",
3549
- default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey,
3617
+ "Enter the [blue]origin hotkey[/blue] name or ss58 address [bold](stake will be transferred FROM here)[/bold] "
3618
+ "[dim](or press Enter to select from existing stakes)[/dim]"
3550
3619
  )
3551
- if is_valid_ss58_address(origin_hotkey):
3620
+ if origin_hotkey == "":
3621
+ interactive_selection = True
3622
+
3623
+ elif is_valid_ss58_address(origin_hotkey):
3552
3624
  origin_hotkey = origin_hotkey
3553
3625
  else:
3554
3626
  wallet = self.wallet_ask(
@@ -3572,33 +3644,11 @@ class CLIManager:
3572
3644
  )
3573
3645
  origin_hotkey = wallet.hotkey.ss58_address
3574
3646
 
3575
- if not dest_ss58:
3576
- dest_ss58 = Prompt.ask(
3577
- "Enter the [blue]destination wallet name[/blue] or [blue]coldkey SS58 address[/blue]"
3578
- )
3579
-
3580
- if is_valid_ss58_address(dest_ss58):
3581
- dest_ss58 = dest_ss58
3582
- else:
3583
- dest_wallet = self.wallet_ask(
3584
- dest_ss58,
3585
- wallet_path,
3586
- None,
3587
- ask_for=[WO.NAME, WO.PATH],
3588
- validate=WV.WALLET,
3589
- )
3590
- dest_ss58 = dest_wallet.coldkeypub.ss58_address
3591
-
3592
- interactive_selection = False
3593
- if origin_netuid is None and dest_netuid is None and not amount:
3594
- interactive_selection = True
3595
- else:
3647
+ if not interactive_selection:
3596
3648
  if origin_netuid is None:
3597
3649
  origin_netuid = IntPrompt.ask(
3598
3650
  "Enter the [blue]origin subnet[/blue] (netuid)"
3599
3651
  )
3600
- if not amount:
3601
- amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to transfer")
3602
3652
 
3603
3653
  if dest_netuid is None:
3604
3654
  dest_netuid = IntPrompt.ask(
@@ -3615,6 +3665,7 @@ class CLIManager:
3615
3665
  dest_coldkey_ss58=dest_ss58,
3616
3666
  amount=amount,
3617
3667
  interactive_selection=interactive_selection,
3668
+ stake_all=stake_all,
3618
3669
  prompt=prompt,
3619
3670
  )
3620
3671
  )
@@ -5071,6 +5122,68 @@ class CLIManager:
5071
5122
  )
5072
5123
  )
5073
5124
 
5125
+ def view_dashboard(
5126
+ self,
5127
+ network: Optional[list[str]] = Options.network,
5128
+ wallet_name: str = Options.wallet_name,
5129
+ wallet_path: str = Options.wallet_path,
5130
+ wallet_hotkey: str = Options.wallet_hotkey,
5131
+ coldkey_ss58: Optional[str] = typer.Option(
5132
+ None,
5133
+ "--coldkey-ss58",
5134
+ "--ss58",
5135
+ help="Coldkey SS58 address to view dashboard for",
5136
+ ),
5137
+ use_wry: bool = typer.Option(
5138
+ False, "--use-wry", help="Use PyWry instead of browser window"
5139
+ ),
5140
+ save_file: bool = typer.Option(
5141
+ False, "--save-file", "--save", help="Save the dashboard HTML file"
5142
+ ),
5143
+ dashboard_path: Optional[str] = Options.dashboard_path,
5144
+ quiet: bool = Options.quiet,
5145
+ verbose: bool = Options.verbose,
5146
+ ):
5147
+ """
5148
+ Display html dashboard with subnets list, stake, and neuron information.
5149
+ """
5150
+ self.verbosity_handler(quiet, verbose)
5151
+ if use_wry and is_linux():
5152
+ print_linux_dependency_message()
5153
+
5154
+ if use_wry and save_file:
5155
+ print_error("Cannot save file when using PyWry.")
5156
+ raise typer.Exit()
5157
+
5158
+ if save_file:
5159
+ if not dashboard_path:
5160
+ dashboard_path = Prompt.ask(
5161
+ "Enter the [blue]path[/blue] where the dashboard HTML file will be saved",
5162
+ default=self.config.get("dashboard_path")
5163
+ or defaults.dashboard.path,
5164
+ )
5165
+
5166
+ if coldkey_ss58:
5167
+ if not is_valid_ss58_address(coldkey_ss58):
5168
+ print_error(f"Invalid SS58 address: {coldkey_ss58}")
5169
+ raise typer.Exit()
5170
+ wallet = None
5171
+ else:
5172
+ wallet = self.wallet_ask(
5173
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
5174
+ )
5175
+
5176
+ return self._run_command(
5177
+ view.display_network_dashboard(
5178
+ wallet=wallet,
5179
+ subtensor=self.initialize_chain(network),
5180
+ use_wry=use_wry,
5181
+ save_file=save_file,
5182
+ dashboard_path=dashboard_path,
5183
+ coldkey_ss58=coldkey_ss58,
5184
+ )
5185
+ )
5186
+
5074
5187
  @staticmethod
5075
5188
  @utils_app.command("convert")
5076
5189
  def convert(
@@ -138,6 +138,9 @@ class Defaults:
138
138
  record_log = False
139
139
  logging_dir = "~/.bittensor/miners"
140
140
 
141
+ class dashboard:
142
+ path = "~/.bittensor/dashboard/"
143
+
141
144
 
142
145
  defaults = Defaults
143
146
 
@@ -713,6 +716,9 @@ HELP_PANELS = {
713
716
  "IDENTITY": "Subnet Identity Management",
714
717
  },
715
718
  "WEIGHTS": {"COMMIT_REVEAL": "Commit / Reveal"},
719
+ "VIEW": {
720
+ "DASHBOARD": "Network Dashboard",
721
+ },
716
722
  }
717
723
 
718
724
  COLOR_PALETTE = {
@@ -297,17 +297,16 @@ class Balance:
297
297
  return self
298
298
 
299
299
 
300
- def fixed_to_float(fixed: dict) -> float:
301
- # Currently this is stored as a U64F64
300
+ def fixed_to_float(fixed, frac_bits: int = 64, total_bits: int = 128) -> float:
301
+ # By default, this is a U64F64
302
302
  # which is 64 bits of integer and 64 bits of fractional
303
- # uint_bits = 64
304
- frac_bits = 64
305
303
 
306
304
  data: int = fixed["bits"]
307
305
 
308
- # Shift bits to extract integer part (assuming 64 bits for integer part)
309
- integer_part = data >> frac_bits
306
+ # Logical and to get the fractional part; remaining is the integer part
310
307
  fractional_part = data & (2**frac_bits - 1)
308
+ # Shift to get the integer part from the remaining bits
309
+ integer_part = data >> (total_bits - frac_bits)
311
310
 
312
311
  frac_float = fractional_part / (2**frac_bits)
313
312