bittensor-cli 9.1.0__py3-none-any.whl → 9.1.2__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
@@ -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 ..."',
@@ -246,6 +258,15 @@ class Options:
246
258
  "--allow-partial/--not-partial",
247
259
  help="Enable or disable partial stake mode (default: disabled).",
248
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
+ )
249
270
 
250
271
 
251
272
  def list_prompt(init_var: list, list_type: type, help_text: str) -> list:
@@ -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,
@@ -1119,6 +1141,7 @@ class CLIManager:
1119
1141
  "--partial/--no-partial",
1120
1142
  "--allow/--not-allow",
1121
1143
  ),
1144
+ dashboard_path: Optional[str] = Options.dashboard_path,
1122
1145
  ):
1123
1146
  """
1124
1147
  Sets or updates configuration values in the BTCLI config file.
@@ -1148,6 +1171,7 @@ class CLIManager:
1148
1171
  "rate_tolerance": rate_tolerance,
1149
1172
  "safe_staking": safe_staking,
1150
1173
  "allow_partial_stake": allow_partial_stake,
1174
+ "dashboard_path": dashboard_path,
1151
1175
  }
1152
1176
  bools = ["use_cache", "safe_staking", "allow_partial_stake"]
1153
1177
  if all(v is None for v in args.values()):
@@ -1257,6 +1281,7 @@ class CLIManager:
1257
1281
  "--allow/--not-allow",
1258
1282
  ),
1259
1283
  all_items: bool = typer.Option(False, "--all"),
1284
+ dashboard_path: Optional[str] = Options.dashboard_path,
1260
1285
  ):
1261
1286
  """
1262
1287
  Clears the fields in the config file and sets them to 'None'.
@@ -1288,6 +1313,7 @@ class CLIManager:
1288
1313
  "rate_tolerance": rate_tolerance,
1289
1314
  "safe_staking": safe_staking,
1290
1315
  "allow_partial_stake": allow_partial_stake,
1316
+ "dashboard_path": dashboard_path,
1291
1317
  }
1292
1318
 
1293
1319
  # If no specific argument is provided, iterate over all
@@ -2656,23 +2682,29 @@ class CLIManager:
2656
2682
  [bold]Note[/bold]: This command is primarily used for informational purposes and has no side effects on the blockchain network state.
2657
2683
  """
2658
2684
  wallet = None
2659
- if coldkey_ss58:
2660
- if not is_valid_ss58_address(coldkey_ss58):
2661
- print_error("You entered an invalid ss58 address")
2662
- raise typer.Exit()
2663
- else:
2664
- coldkey_or_ss58 = Prompt.ask(
2665
- "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]",
2666
- default=self.config.get("wallet_name") or defaults.wallet.name,
2667
- )
2668
- if is_valid_ss58_address(coldkey_or_ss58):
2669
- coldkey_ss58 = coldkey_or_ss58
2685
+ if not wallet_name:
2686
+ if coldkey_ss58:
2687
+ if not is_valid_ss58_address(coldkey_ss58):
2688
+ print_error("You entered an invalid ss58 address")
2689
+ raise typer.Exit()
2670
2690
  else:
2671
- wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name
2672
- wallet = self.wallet_ask(
2673
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2691
+ coldkey_or_ss58 = Prompt.ask(
2692
+ "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]",
2693
+ default=self.config.get("wallet_name") or defaults.wallet.name,
2674
2694
  )
2675
- coldkey_ss58 = wallet.coldkeypub.ss58_address
2695
+ if is_valid_ss58_address(coldkey_or_ss58):
2696
+ coldkey_ss58 = coldkey_or_ss58
2697
+ else:
2698
+ wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name
2699
+ wallet = self.wallet_ask(
2700
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2701
+ )
2702
+ coldkey_ss58 = wallet.coldkeypub.ss58_address
2703
+ else:
2704
+ wallet = self.wallet_ask(
2705
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
2706
+ )
2707
+ coldkey_ss58 = wallet.coldkeypub.ss58_address
2676
2708
 
2677
2709
  self.verbosity_handler(quiet, verbose)
2678
2710
  return self._run_command(
@@ -2937,7 +2969,7 @@ class CLIManager:
2937
2969
  ),
2938
2970
  exit_early=False,
2939
2971
  )
2940
- if selected_hotkey is None:
2972
+ if not selected_hotkey:
2941
2973
  print_error("No delegate selected. Exiting.")
2942
2974
  raise typer.Exit()
2943
2975
  include_hotkeys = selected_hotkey
@@ -3048,14 +3080,12 @@ class CLIManager:
3048
3080
  False,
3049
3081
  "--unstake-all",
3050
3082
  "--all",
3051
- hidden=True,
3052
3083
  help="When set, this command unstakes all staked TAO + Alpha from the all hotkeys.",
3053
3084
  ),
3054
3085
  unstake_all_alpha: bool = typer.Option(
3055
3086
  False,
3056
3087
  "--unstake-all-alpha",
3057
3088
  "--all-alpha",
3058
- hidden=True,
3059
3089
  help="When set, this command unstakes all staked Alpha from the all hotkeys.",
3060
3090
  ),
3061
3091
  amount: float = typer.Option(
@@ -3337,7 +3367,7 @@ class CLIManager:
3337
3367
  network: Optional[list[str]] = Options.network,
3338
3368
  wallet_name: Optional[str] = Options.wallet_name,
3339
3369
  wallet_path: Optional[str] = Options.wallet_path,
3340
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3370
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey_ss58,
3341
3371
  origin_netuid: Optional[int] = typer.Option(
3342
3372
  None, "--origin-netuid", help="Origin netuid"
3343
3373
  ),
@@ -3357,6 +3387,8 @@ class CLIManager:
3357
3387
  False, "--stake-all", "--all", help="Stake all", prompt=False
3358
3388
  ),
3359
3389
  prompt: bool = Options.prompt,
3390
+ quiet: bool = Options.quiet,
3391
+ verbose: bool = Options.verbose,
3360
3392
  ):
3361
3393
  """
3362
3394
  Move staked TAO between hotkeys while keeping the same coldkey ownership.
@@ -3378,6 +3410,7 @@ class CLIManager:
3378
3410
 
3379
3411
  [green]$[/green] btcli stake move
3380
3412
  """
3413
+ self.verbosity_handler(quiet, verbose)
3381
3414
  console.print(
3382
3415
  "[dim]This command moves stake from one hotkey to another hotkey while keeping the same coldkey.[/dim]"
3383
3416
  )
@@ -3490,7 +3523,7 @@ class CLIManager:
3490
3523
  network: Optional[list[str]] = Options.network,
3491
3524
  wallet_name: Optional[str] = Options.wallet_name,
3492
3525
  wallet_path: Optional[str] = Options.wallet_path,
3493
- wallet_hotkey: Optional[str] = Options.wallet_hotkey,
3526
+ wallet_hotkey: Optional[str] = Options.wallet_hotkey_ss58,
3494
3527
  origin_netuid: Optional[int] = typer.Option(
3495
3528
  None,
3496
3529
  "--origin-netuid",
@@ -3514,6 +3547,9 @@ class CLIManager:
3514
3547
  "-a",
3515
3548
  help="Amount of stake to transfer",
3516
3549
  ),
3550
+ stake_all: bool = typer.Option(
3551
+ False, "--stake-all", "--all", help="Stake all", prompt=False
3552
+ ),
3517
3553
  prompt: bool = Options.prompt,
3518
3554
  quiet: bool = Options.quiet,
3519
3555
  verbose: bool = Options.verbose,
@@ -3531,6 +3567,8 @@ class CLIManager:
3531
3567
  - The destination subnet (--dest-netuid)
3532
3568
  - The destination wallet/address (--dest)
3533
3569
  - The amount to transfer (--amount)
3570
+ - The origin wallet (--wallet-name)
3571
+ - The origin hotkey wallet/address (--wallet-hotkey)
3534
3572
 
3535
3573
  If no arguments are provided, an interactive selection menu will be shown.
3536
3574
 
@@ -3539,14 +3577,37 @@ class CLIManager:
3539
3577
  Transfer 100 TAO from subnet 1 to subnet 2:
3540
3578
  [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest wallet2 --amount 100
3541
3579
 
3542
- Using SS58 address:
3580
+ Using Destination SS58 address:
3543
3581
  [green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --dest 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngEP9H7qjkKgHLcK --amount 100
3582
+
3583
+ Using Origin hotkey SS58 address (useful when transferring stake from a delegate):
3584
+ [green]$[/green] btcli stake transfer --wallet-hotkey 5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngEP9H7qjkKgHLcK --wallet-name sample_wallet
3585
+
3586
+ Transfer all available stake from origin hotkey:
3587
+ [green]$[/green] btcli stake transfer --all --origin-netuid 1 --dest-netuid 2
3544
3588
  """
3545
3589
  console.print(
3546
3590
  "[dim]This command transfers stake from one coldkey to another while keeping the same hotkey.[/dim]"
3547
3591
  )
3548
3592
  self.verbosity_handler(quiet, verbose)
3549
3593
 
3594
+ if not dest_ss58:
3595
+ dest_ss58 = Prompt.ask(
3596
+ "Enter the [blue]destination wallet name[/blue] or [blue]coldkey SS58 address[/blue]"
3597
+ )
3598
+
3599
+ if is_valid_ss58_address(dest_ss58):
3600
+ dest_ss58 = dest_ss58
3601
+ else:
3602
+ dest_wallet = self.wallet_ask(
3603
+ dest_ss58,
3604
+ wallet_path,
3605
+ None,
3606
+ ask_for=[WO.NAME, WO.PATH],
3607
+ validate=WV.WALLET,
3608
+ )
3609
+ dest_ss58 = dest_wallet.coldkeypub.ss58_address
3610
+
3550
3611
  if not wallet_name:
3551
3612
  wallet_name = Prompt.ask(
3552
3613
  "Enter the [blue]origin wallet name[/blue]",
@@ -3556,13 +3617,16 @@ class CLIManager:
3556
3617
  wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME]
3557
3618
  )
3558
3619
 
3620
+ interactive_selection = False
3559
3621
  if not wallet_hotkey:
3560
3622
  origin_hotkey = Prompt.ask(
3561
- "Enter the [blue]origin hotkey[/blue] name or "
3562
- "[blue]ss58 address[/blue] where the stake will be moved from",
3563
- default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey,
3623
+ "Enter the [blue]origin hotkey[/blue] name or ss58 address [bold](stake will be transferred FROM here)[/bold] "
3624
+ "[dim](or press Enter to select from existing stakes)[/dim]"
3564
3625
  )
3565
- if is_valid_ss58_address(origin_hotkey):
3626
+ if origin_hotkey == "":
3627
+ interactive_selection = True
3628
+
3629
+ elif is_valid_ss58_address(origin_hotkey):
3566
3630
  origin_hotkey = origin_hotkey
3567
3631
  else:
3568
3632
  wallet = self.wallet_ask(
@@ -3586,33 +3650,11 @@ class CLIManager:
3586
3650
  )
3587
3651
  origin_hotkey = wallet.hotkey.ss58_address
3588
3652
 
3589
- if not dest_ss58:
3590
- dest_ss58 = Prompt.ask(
3591
- "Enter the [blue]destination wallet name[/blue] or [blue]coldkey SS58 address[/blue]"
3592
- )
3593
-
3594
- if is_valid_ss58_address(dest_ss58):
3595
- dest_ss58 = dest_ss58
3596
- else:
3597
- dest_wallet = self.wallet_ask(
3598
- dest_ss58,
3599
- wallet_path,
3600
- None,
3601
- ask_for=[WO.NAME, WO.PATH],
3602
- validate=WV.WALLET,
3603
- )
3604
- dest_ss58 = dest_wallet.coldkeypub.ss58_address
3605
-
3606
- interactive_selection = False
3607
- if origin_netuid is None and dest_netuid is None and not amount:
3608
- interactive_selection = True
3609
- else:
3653
+ if not interactive_selection:
3610
3654
  if origin_netuid is None:
3611
3655
  origin_netuid = IntPrompt.ask(
3612
3656
  "Enter the [blue]origin subnet[/blue] (netuid)"
3613
3657
  )
3614
- if not amount:
3615
- amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to transfer")
3616
3658
 
3617
3659
  if dest_netuid is None:
3618
3660
  dest_netuid = IntPrompt.ask(
@@ -3629,6 +3671,7 @@ class CLIManager:
3629
3671
  dest_coldkey_ss58=dest_ss58,
3630
3672
  amount=amount,
3631
3673
  interactive_selection=interactive_selection,
3674
+ stake_all=stake_all,
3632
3675
  prompt=prompt,
3633
3676
  )
3634
3677
  )
@@ -5091,6 +5134,19 @@ class CLIManager:
5091
5134
  wallet_name: str = Options.wallet_name,
5092
5135
  wallet_path: str = Options.wallet_path,
5093
5136
  wallet_hotkey: str = Options.wallet_hotkey,
5137
+ coldkey_ss58: Optional[str] = typer.Option(
5138
+ None,
5139
+ "--coldkey-ss58",
5140
+ "--ss58",
5141
+ help="Coldkey SS58 address to view dashboard for",
5142
+ ),
5143
+ use_wry: bool = typer.Option(
5144
+ False, "--use-wry", help="Use PyWry instead of browser window"
5145
+ ),
5146
+ save_file: bool = typer.Option(
5147
+ False, "--save-file", "--save", help="Save the dashboard HTML file"
5148
+ ),
5149
+ dashboard_path: Optional[str] = Options.dashboard_path,
5094
5150
  quiet: bool = Options.quiet,
5095
5151
  verbose: bool = Options.verbose,
5096
5152
  ):
@@ -5098,13 +5154,40 @@ class CLIManager:
5098
5154
  Display html dashboard with subnets list, stake, and neuron information.
5099
5155
  """
5100
5156
  self.verbosity_handler(quiet, verbose)
5101
- if is_linux():
5157
+ if use_wry and is_linux():
5102
5158
  print_linux_dependency_message()
5103
- wallet = self.wallet_ask(
5104
- wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
5105
- )
5159
+
5160
+ if use_wry and save_file:
5161
+ print_error("Cannot save file when using PyWry.")
5162
+ raise typer.Exit()
5163
+
5164
+ if save_file:
5165
+ if not dashboard_path:
5166
+ dashboard_path = Prompt.ask(
5167
+ "Enter the [blue]path[/blue] where the dashboard HTML file will be saved",
5168
+ default=self.config.get("dashboard_path")
5169
+ or defaults.dashboard.path,
5170
+ )
5171
+
5172
+ if coldkey_ss58:
5173
+ if not is_valid_ss58_address(coldkey_ss58):
5174
+ print_error(f"Invalid SS58 address: {coldkey_ss58}")
5175
+ raise typer.Exit()
5176
+ wallet = None
5177
+ else:
5178
+ wallet = self.wallet_ask(
5179
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
5180
+ )
5181
+
5106
5182
  return self._run_command(
5107
- view.display_network_dashboard(wallet, self.initialize_chain(network))
5183
+ view.display_network_dashboard(
5184
+ wallet=wallet,
5185
+ subtensor=self.initialize_chain(network),
5186
+ use_wry=use_wry,
5187
+ save_file=save_file,
5188
+ dashboard_path=dashboard_path,
5189
+ coldkey_ss58=coldkey_ss58,
5190
+ )
5108
5191
  )
5109
5192
 
5110
5193
  @staticmethod
@@ -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
 
@@ -1251,7 +1251,7 @@ class SubtensorInterface:
1251
1251
  if _result is None:
1252
1252
  return Balance(0).set_unit(netuid)
1253
1253
  else:
1254
- return Balance.from_rao(_result).set_unit(int(netuid))
1254
+ return Balance.from_rao(fixed_to_float(_result)).set_unit(int(netuid))
1255
1255
 
1256
1256
  async def get_metagraph_info(
1257
1257
  self, netuid: int, block_hash: Optional[str] = None
@@ -45,17 +45,33 @@ class _Hotkey:
45
45
  self.ss58_address = hotkey_ss58
46
46
 
47
47
 
48
+ class _Coldkeypub:
49
+ def __init__(self, coldkey_ss58=None):
50
+ self.ss58_address = coldkey_ss58
51
+
52
+
48
53
  class WalletLike:
49
- def __init__(self, name=None, hotkey_ss58=None, hotkey_str=None):
54
+ def __init__(
55
+ self,
56
+ name=None,
57
+ hotkey_ss58=None,
58
+ hotkey_str=None,
59
+ coldkeypub_ss58=None,
60
+ ):
50
61
  self.name = name
51
62
  self.hotkey_ss58 = hotkey_ss58
52
63
  self.hotkey_str = hotkey_str
53
64
  self._hotkey = _Hotkey(hotkey_ss58)
65
+ self._coldkeypub = _Coldkeypub(coldkeypub_ss58)
54
66
 
55
67
  @property
56
68
  def hotkey(self):
57
69
  return self._hotkey
58
70
 
71
+ @property
72
+ def coldkeypub(self):
73
+ return self._coldkeypub
74
+
59
75
 
60
76
  def print_console(message: str, colour: str, title: str, console: Console):
61
77
  console.print(
@@ -1081,38 +1097,30 @@ def prompt_for_identity(
1081
1097
  identity_fields = {}
1082
1098
 
1083
1099
  fields = [
1084
- ("name", "[blue]Display name[/blue]", name),
1085
- ("url", "[blue]Web URL[/blue]", web_url),
1086
- ("image", "[blue]Image URL[/blue]", image_url),
1087
- ("discord", "[blue]Discord handle[/blue]", discord),
1088
- ("description", "[blue]Description[/blue]", description),
1089
- ("additional", "[blue]Additional information[/blue]", additional),
1090
- ("github_repo", "[blue]GitHub repository URL[/blue]", github_repo),
1100
+ ("name", "[blue]Display name[/blue]", name, 256),
1101
+ ("url", "[blue]Web URL[/blue]", web_url, 256),
1102
+ ("image", "[blue]Image URL[/blue]", image_url, 1024),
1103
+ ("discord", "[blue]Discord handle[/blue]", discord, 256),
1104
+ ("description", "[blue]Description[/blue]", description, 1024),
1105
+ ("additional", "[blue]Additional information[/blue]", additional, 1024),
1106
+ ("github_repo", "[blue]GitHub repository URL[/blue]", github_repo, 256),
1091
1107
  ]
1092
1108
 
1093
- text_rejection = partial(
1094
- retry_prompt,
1095
- rejection=lambda x: sys.getsizeof(x) > 113,
1096
- rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.",
1097
- )
1098
-
1099
1109
  if not any(
1100
- [
1101
- name,
1102
- web_url,
1103
- image_url,
1104
- discord,
1105
- description,
1106
- additional,
1107
- github_repo,
1108
- ]
1110
+ [name, web_url, image_url, discord, description, additional, github_repo]
1109
1111
  ):
1110
1112
  console.print(
1111
1113
  "\n[yellow]All fields are optional. Press Enter to skip and keep the default/existing value.[/yellow]\n"
1112
1114
  "[dark_sea_green3]Tip: Entering a space and pressing Enter will clear existing default value.\n"
1113
1115
  )
1114
1116
 
1115
- for key, prompt, value in fields:
1117
+ for key, prompt, value, byte_limit in fields:
1118
+ text_rejection = partial(
1119
+ retry_prompt,
1120
+ rejection=lambda x: len(x.encode("utf-8")) > byte_limit,
1121
+ rejection_text=f"[red]Error:[/red] {key} field must be <= {byte_limit} bytes.",
1122
+ )
1123
+
1116
1124
  if value:
1117
1125
  identity_fields[key] = value
1118
1126
  else:
@@ -1154,50 +1162,51 @@ def prompt_for_subnet_identity(
1154
1162
  "subnet_name",
1155
1163
  "[blue]Subnet name [dim](optional)[/blue]",
1156
1164
  subnet_name,
1157
- lambda x: x and sys.getsizeof(x) > 113,
1158
- "[red]Error:[/red] Subnet name must be <= 64 raw bytes.",
1165
+ lambda x: x and len(x.encode("utf-8")) > 256,
1166
+ "[red]Error:[/red] Subnet name must be <= 256 bytes.",
1159
1167
  ),
1160
1168
  (
1161
1169
  "github_repo",
1162
1170
  "[blue]GitHub repository URL [dim](optional)[/blue]",
1163
1171
  github_repo,
1164
- lambda x: x and not is_valid_github_url(x),
1172
+ lambda x: x
1173
+ and (not is_valid_github_url(x) or len(x.encode("utf-8")) > 1024),
1165
1174
  "[red]Error:[/red] Please enter a valid GitHub repository URL (e.g., https://github.com/username/repo).",
1166
1175
  ),
1167
1176
  (
1168
1177
  "subnet_contact",
1169
1178
  "[blue]Contact email [dim](optional)[/blue]",
1170
1179
  subnet_contact,
1171
- lambda x: x and not is_valid_contact(x),
1180
+ lambda x: x and (not is_valid_contact(x) or len(x.encode("utf-8")) > 1024),
1172
1181
  "[red]Error:[/red] Please enter a valid email address.",
1173
1182
  ),
1174
1183
  (
1175
1184
  "subnet_url",
1176
1185
  "[blue]Subnet URL [dim](optional)[/blue]",
1177
1186
  subnet_url,
1178
- lambda x: x and sys.getsizeof(x) > 113,
1179
- "[red]Error:[/red] Please enter a valid URL.",
1187
+ lambda x: x and len(x.encode("utf-8")) > 1024,
1188
+ "[red]Error:[/red] Please enter a valid URL <= 1024 bytes.",
1180
1189
  ),
1181
1190
  (
1182
1191
  "discord",
1183
1192
  "[blue]Discord handle [dim](optional)[/blue]",
1184
1193
  discord,
1185
- lambda x: x and sys.getsizeof(x) > 113,
1186
- "[red]Error:[/red] Please enter a valid Discord handle.",
1194
+ lambda x: x and len(x.encode("utf-8")) > 256,
1195
+ "[red]Error:[/red] Please enter a valid Discord handle <= 256 bytes.",
1187
1196
  ),
1188
1197
  (
1189
1198
  "description",
1190
1199
  "[blue]Description [dim](optional)[/blue]",
1191
1200
  description,
1192
- lambda x: x and sys.getsizeof(x) > 113,
1193
- "[red]Error:[/red] Description must be <= 64 raw bytes.",
1201
+ lambda x: x and len(x.encode("utf-8")) > 1024,
1202
+ "[red]Error:[/red] Description must be <= 1024 bytes.",
1194
1203
  ),
1195
1204
  (
1196
1205
  "additional",
1197
1206
  "[blue]Additional information [dim](optional)[/blue]",
1198
1207
  additional,
1199
- lambda x: x and sys.getsizeof(x) > 113,
1200
- "[red]Error:[/red] Additional information must be <= 64 raw bytes.",
1208
+ lambda x: x and len(x.encode("utf-8")) > 1024,
1209
+ "[red]Error:[/red] Additional information must be <= 1024 bytes.",
1201
1210
  ),
1202
1211
  ]
1203
1212
 
@@ -1257,16 +1266,18 @@ def is_valid_contact(contact: str) -> bool:
1257
1266
  return bool(re.match(email_pattern, contact))
1258
1267
 
1259
1268
 
1260
- def get_subnet_name(subnet_info) -> str:
1269
+ def get_subnet_name(subnet_info, max_length: int = 20) -> str:
1261
1270
  """Get the subnet name, prioritizing subnet_identity.subnet_name over subnet.subnet_name.
1271
+ Truncates the name if it exceeds max_length.
1262
1272
 
1263
1273
  Args:
1264
- subnet: The subnet dynamic info
1274
+ subnet_info: The subnet dynamic info
1275
+ max_length: Maximum length of the returned name. Names longer than this will be truncated with '...'
1265
1276
 
1266
1277
  Returns:
1267
- str: The subnet name or empty string if no name is found
1278
+ str: The subnet name (truncated if necessary) or empty string if no name is found
1268
1279
  """
1269
- return (
1280
+ name = (
1270
1281
  subnet_info.subnet_identity.subnet_name
1271
1282
  if hasattr(subnet_info, "subnet_identity")
1272
1283
  and subnet_info.subnet_identity is not None
@@ -1274,6 +1285,10 @@ def get_subnet_name(subnet_info) -> str:
1274
1285
  else (subnet_info.subnet_name if subnet_info.subnet_name is not None else "")
1275
1286
  )
1276
1287
 
1288
+ if len(name) > max_length:
1289
+ return name[: max_length - 3] + "..."
1290
+ return name
1291
+
1277
1292
 
1278
1293
  def print_linux_dependency_message():
1279
1294
  """Prints the WebKit dependency message for Linux systems."""
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  from functools import partial
3
3
 
4
- import typer
5
4
  from typing import TYPE_CHECKING, Optional
6
5
  from rich.table import Table
7
6
  from rich.prompt import Confirm, Prompt
@@ -20,7 +19,6 @@ from bittensor_cli.src.bittensor.utils import (
20
19
  unlock_key,
21
20
  )
22
21
  from bittensor_wallet import Wallet
23
- from bittensor_wallet.errors import KeyFileError
24
22
 
25
23
  if TYPE_CHECKING:
26
24
  from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
@@ -338,7 +336,7 @@ async def stake_add(
338
336
 
339
337
  if prompt:
340
338
  if not Confirm.ask("Would you like to continue?"):
341
- raise typer.Exit()
339
+ return False
342
340
  if not unlock_key(wallet).success:
343
341
  return False
344
342
 
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
 
3
3
  from typing import TYPE_CHECKING, Optional
4
- import typer
5
4
 
6
5
  from bittensor_wallet import Wallet
7
6
  from rich.prompt import Prompt
@@ -428,7 +427,7 @@ async def stake_list(
428
427
 
429
428
  if not hotkeys_to_substakes:
430
429
  print_error(f"No stakes found for coldkey ss58: ({coldkey_address})")
431
- raise typer.Exit()
430
+ return
432
431
 
433
432
  if live:
434
433
  # Select one hotkey for live monitoring