bittensor-cli 9.19.0rc2__tar.gz → 9.20.0__tar.gz

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 (67) hide show
  1. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/PKG-INFO +3 -2
  2. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/cli.py +96 -25
  3. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/__init__.py +7 -0
  4. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/chain_data.py +18 -6
  5. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/subtensor_interface.py +92 -54
  6. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/utils.py +3 -2
  7. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/proxy.py +46 -19
  8. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/add.py +156 -49
  9. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/list.py +10 -6
  10. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/remove.py +242 -45
  11. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/wallets.py +150 -4
  12. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/pyproject.toml +3 -2
  13. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/README.md +0 -0
  14. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/__init__.py +0 -0
  15. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/doc_generation_helper.py +0 -0
  16. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/__init__.py +0 -0
  17. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/balances.py +0 -0
  18. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/__init__.py +0 -0
  19. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/mev_shield.py +0 -0
  20. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/registration.py +0 -0
  21. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/root.py +0 -0
  22. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/serving.py +0 -0
  23. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/transfer.py +0 -0
  24. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/minigraph.py +0 -0
  25. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/networking.py +0 -0
  26. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/main-filters.j2 +0 -0
  27. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/main-header.j2 +0 -0
  28. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/neuron-details.j2 +0 -0
  29. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/price-multi.j2 +0 -0
  30. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/price-single.j2 +0 -0
  31. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnet-details-header.j2 +0 -0
  32. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnet-details.j2 +0 -0
  33. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnet-metrics.j2 +0 -0
  34. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnets-table.j2 +0 -0
  35. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/table.j2 +0 -0
  36. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/view.css +0 -0
  37. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/view.j2 +0 -0
  38. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/view.js +0 -0
  39. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/__init__.py +0 -0
  40. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/axon/__init__.py +0 -0
  41. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/axon/axon.py +0 -0
  42. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/__init__.py +0 -0
  43. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/contribute.py +0 -0
  44. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/contributors.py +0 -0
  45. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/create.py +0 -0
  46. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/dissolve.py +0 -0
  47. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/refund.py +0 -0
  48. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/update.py +0 -0
  49. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/utils.py +0 -0
  50. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/view.py +0 -0
  51. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/liquidity/__init__.py +0 -0
  52. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/liquidity/liquidity.py +0 -0
  53. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/liquidity/utils.py +0 -0
  54. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/__init__.py +0 -0
  55. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/auto_staking.py +0 -0
  56. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/children_hotkeys.py +0 -0
  57. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/claim.py +0 -0
  58. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/move.py +0 -0
  59. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/wizard.py +0 -0
  60. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/__init__.py +0 -0
  61. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/mechanisms.py +0 -0
  62. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/price.py +0 -0
  63. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/subnets.py +0 -0
  64. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/sudo.py +0 -0
  65. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/view.py +0 -0
  66. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/weights.py +0 -0
  67. {bittensor_cli-9.19.0rc2 → bittensor_cli-9.20.0}/bittensor_cli/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bittensor-cli
3
- Version: 9.19.0rc2
3
+ Version: 9.20.0
4
4
  Summary: Bittensor CLI
5
5
  Author: bittensor.com
6
6
  Requires-Python: >=3.10
@@ -32,7 +32,8 @@ Requires-Dist: PyYAML~=6.0
32
32
  Requires-Dist: rich>=13.7,<15.0
33
33
  Requires-Dist: scalecodec==1.2.12
34
34
  Requires-Dist: typer>=0.16
35
- Requires-Dist: bittensor-wallet>=4.0.0
35
+ Requires-Dist: typing_extensions>4.0.0; python_version<'3.11'
36
+ Requires-Dist: bittensor-wallet==4.0.1
36
37
  Requires-Dist: packaging
37
38
  Requires-Dist: plotille>=5.0.0
38
39
  Requires-Dist: plotly>=6.0.0
@@ -641,8 +641,8 @@ def get_creation_data(
641
641
  return mnemonic, seed, json_path, json_password
642
642
 
643
643
 
644
- def config_selector(conf: dict, title: str):
645
- def curses_selector(stdscr):
644
+ def config_selector(conf: dict[str, bool], title: str) -> dict[str, bool]:
645
+ def curses_selector(stdscr) -> dict[str, bool]:
646
646
  """
647
647
  Enhanced Curses TUI to make selections.
648
648
  """
@@ -698,7 +698,7 @@ def config_selector(conf: dict, title: str):
698
698
  return curses.wrapper(curses_selector)
699
699
 
700
700
 
701
- def version_callback(value: bool):
701
+ def version_callback(value: bool) -> None:
702
702
  """
703
703
  Prints the current version/branch-name
704
704
  """
@@ -716,7 +716,7 @@ def version_callback(value: bool):
716
716
  raise typer.Exit()
717
717
 
718
718
 
719
- def commands_callback(value: bool):
719
+ def commands_callback(value: bool) -> None:
720
720
  """
721
721
  Prints a tree of commands for the app
722
722
  """
@@ -726,7 +726,7 @@ def commands_callback(value: bool):
726
726
  raise typer.Exit()
727
727
 
728
728
 
729
- def debug_callback(value: bool):
729
+ def debug_callback(value: bool) -> None:
730
730
  if value:
731
731
  debug_file_loc = Path(
732
732
  os.getenv("BTCLI_DEBUG_FILE")
@@ -1389,7 +1389,7 @@ class CLIManager:
1389
1389
  Generates a rich.Tree of the commands, subcommands, and groups of this app
1390
1390
  """
1391
1391
 
1392
- def build_rich_tree(data: dict, parent: Tree):
1392
+ def build_rich_tree(data: dict, parent: Tree) -> None:
1393
1393
  for group, content in data.get("groups", {}).items():
1394
1394
  group_node = parent.add(
1395
1395
  f"[bold cyan]{group}[/]"
@@ -1943,7 +1943,7 @@ class CLIManager:
1943
1943
  with open(self.config_path, "w") as f:
1944
1944
  safe_dump(self.config, f)
1945
1945
 
1946
- def get_config(self):
1946
+ def get_config(self) -> None:
1947
1947
  """
1948
1948
  Prints the current config file in a table.
1949
1949
  """
@@ -2079,7 +2079,7 @@ class CLIManager:
2079
2079
  print_error(f"Proxy {name} not found in address book.")
2080
2080
  self.config_get_proxies()
2081
2081
 
2082
- def config_get_proxies(self):
2082
+ def config_get_proxies(self) -> None:
2083
2083
  """
2084
2084
  Displays the current proxies address book
2085
2085
 
@@ -2228,7 +2228,7 @@ class CLIManager:
2228
2228
  console.print("Proxy updated")
2229
2229
  self.config_get_proxies()
2230
2230
 
2231
- def config_clear_proxy_book(self):
2231
+ def config_clear_proxy_book(self) -> None:
2232
2232
  """
2233
2233
  Clears the proxy address book. Use with caution.
2234
2234
  Really only useful if you have corrupted your proxy address book.
@@ -4144,7 +4144,11 @@ class CLIManager:
4144
4144
  self,
4145
4145
  action: str = typer.Argument(
4146
4146
  None,
4147
- help="Action to perform: 'announce' to announce intent, 'execute' to complete swap after delay, 'dispute' to freeze the swap.",
4147
+ help=(
4148
+ "Action to perform: 'announce' to announce intent, "
4149
+ "'execute' to complete swap after delay, 'dispute' to freeze the swap, "
4150
+ "'clear' to withdraw announcement, 'check' to view status."
4151
+ ),
4148
4152
  ),
4149
4153
  wallet_name: Optional[str] = Options.wallet_name,
4150
4154
  wallet_path: Optional[str] = Options.wallet_path,
@@ -4176,6 +4180,9 @@ class CLIManager:
4176
4180
  If you suspect compromise, you can [bold]Dispute[/bold] an active announcement to freeze
4177
4181
  all activity for the coldkey until the triumvirate can intervene.
4178
4182
 
4183
+ If you want to withdraw your announcement, you can [bold]Clear[/bold] (withdraw) an announcement once the
4184
+ reannouncement delay has elapsed.
4185
+
4179
4186
  EXAMPLES
4180
4187
 
4181
4188
  Step 1 - Announce your intent to swap:
@@ -4190,9 +4197,13 @@ class CLIManager:
4190
4197
 
4191
4198
  [green]$[/green] btcli wallet swap-coldkey dispute
4192
4199
 
4193
- Check status of pending swaps:
4200
+ Clear (withdraw) an announcement:
4201
+
4202
+ [green]$[/green] btcli wallet swap-coldkey clear
4194
4203
 
4195
- [green]$[/green] btcli wallet swap-check
4204
+ Check status of your swap:
4205
+
4206
+ [green]$[/green] btcli wallet swap-coldkey check
4196
4207
  """
4197
4208
  self.verbosity_handler(quiet, verbose, prompt=False, json_output=False)
4198
4209
 
@@ -4201,18 +4212,19 @@ class CLIManager:
4201
4212
  "\n[bold][blue]Coldkey Swap Actions:[/blue][/bold]\n"
4202
4213
  " [dark_sea_green3]announce[/dark_sea_green3] - Start the swap process (pays fee, starts delay timer)\n"
4203
4214
  " [dark_sea_green3]execute[/dark_sea_green3] - Complete the swap (after delay period)\n"
4204
- " [dark_sea_green3]dispute[/dark_sea_green3] - Freeze the swap process if you suspect compromise\n\n"
4205
- " [dim]You can check the current status of your swap with 'btcli wallet swap-check'.[/dim]\n"
4215
+ " [dark_sea_green3]dispute[/dark_sea_green3] - Freeze the swap process if you suspect compromise\n"
4216
+ " [dark_sea_green3]clear[/dark_sea_green3] - Withdraw your swap announcement\n"
4217
+ " [dark_sea_green3]check[/dark_sea_green3] - Check the status of your swap\n\n"
4206
4218
  )
4207
4219
  action = Prompt.ask(
4208
4220
  "Select action",
4209
- choices=["announce", "execute", "dispute"],
4221
+ choices=["announce", "execute", "dispute", "clear", "check"],
4210
4222
  default="announce",
4211
4223
  )
4212
4224
 
4213
- if action.lower() not in ("announce", "execute", "dispute"):
4225
+ if action.lower() not in ("announce", "execute", "dispute", "clear", "check"):
4214
4226
  print_error(
4215
- f"Invalid action: {action}. Must be 'announce', 'execute', or 'dispute'."
4227
+ f"Invalid action: {action}. Must be 'announce', 'execute', 'dispute', 'clear', or 'check'."
4216
4228
  )
4217
4229
  raise typer.Exit(1)
4218
4230
 
@@ -4233,7 +4245,7 @@ class CLIManager:
4233
4245
  )
4234
4246
 
4235
4247
  new_wallet_coldkey_ss58 = None
4236
- if action != "dispute":
4248
+ if action not in ("dispute", "clear", "check"):
4237
4249
  if not new_wallet_or_ss58:
4238
4250
  new_wallet_or_ss58 = Prompt.ask(
4239
4251
  "Enter the [blue]new wallet name[/blue] or [blue]SS58 address[/blue] of the new coldkey",
@@ -4285,6 +4297,24 @@ class CLIManager:
4285
4297
  mev_protection=mev_protection,
4286
4298
  )
4287
4299
  )
4300
+ elif action == "clear":
4301
+ return self._run_command(
4302
+ wallets.clear_coldkey_swap_announcement(
4303
+ wallet=wallet,
4304
+ subtensor=self.initialize_chain(network),
4305
+ decline=decline,
4306
+ quiet=quiet,
4307
+ prompt=prompt,
4308
+ mev_protection=mev_protection,
4309
+ )
4310
+ )
4311
+ elif action == "check":
4312
+ return self._run_command(
4313
+ wallets.check_swap_status(
4314
+ subtensor=self.initialize_chain(network),
4315
+ origin_ss58=wallet.coldkeypub.ss58_address,
4316
+ )
4317
+ )
4288
4318
  else:
4289
4319
  return self._run_command(
4290
4320
  wallets.execute_coldkey_swap(
@@ -9807,13 +9837,18 @@ class CLIManager:
9807
9837
  def proxy_remove(
9808
9838
  self,
9809
9839
  delegate: Annotated[
9810
- str,
9840
+ Optional[str],
9811
9841
  typer.Option(
9812
9842
  callback=is_valid_ss58_address_param,
9813
- prompt="Enter the SS58 address of the delegate to remove, e.g. 5dxds...",
9814
- help="The SS58 address of the delegate to remove",
9843
+ prompt=False,
9844
+ help="The SS58 address of the delegate to remove (required if --all is not used)",
9815
9845
  ),
9816
- ] = "",
9846
+ ] = None,
9847
+ all_: bool = typer.Option(
9848
+ False,
9849
+ "--all",
9850
+ help="Remove all proxies associated with this account",
9851
+ ),
9817
9852
  network: Optional[list[str]] = Options.network,
9818
9853
  proxy_type: ProxyType = Options.proxy_type,
9819
9854
  delay: int = typer.Option(0, help="Delay, in number of blocks"),
@@ -9840,10 +9875,10 @@ class CLIManager:
9840
9875
  [green]$[/green] btcli proxy remove --delegate 5GDel... --proxy-type Transfer
9841
9876
 
9842
9877
  """
9843
- # TODO should add a --all flag to call Proxy.remove_proxies ?
9844
9878
  logger.debug(
9845
9879
  "args:\n"
9846
9880
  f"delegate: {delegate}\n"
9881
+ f"all: {all_}\n"
9847
9882
  f"network: {network}\n"
9848
9883
  f"proxy_type: {proxy_type}\n"
9849
9884
  f"delay: {delay}\n"
@@ -9851,6 +9886,41 @@ class CLIManager:
9851
9886
  f"wait_for_inclusion: {wait_for_inclusion}\n"
9852
9887
  f"era: {period}\n"
9853
9888
  )
9889
+ # Validate that --delegate and --all are not used together
9890
+ if all_ and delegate:
9891
+ if not json_output:
9892
+ print_error("--delegate cannot be used together with --all flag.")
9893
+ else:
9894
+ json_console.print_json(
9895
+ data={
9896
+ "success": False,
9897
+ "message": "--delegate cannot be used together with --all flag.",
9898
+ "extrinsic_identifier": None,
9899
+ }
9900
+ )
9901
+ return
9902
+
9903
+ # If --all is not used and delegate is not provided, prompt or error
9904
+ if not all_ and not delegate:
9905
+ if not prompt:
9906
+ if not json_output:
9907
+ print_error(
9908
+ "Either --delegate must be provided or --all flag must be used."
9909
+ )
9910
+ else:
9911
+ json_console.print_json(
9912
+ data={
9913
+ "success": False,
9914
+ "message": "Either --delegate must be provided or --all flag must be used.",
9915
+ "extrinsic_identifier": None,
9916
+ }
9917
+ )
9918
+ return
9919
+ delegate = Prompt.ask(
9920
+ "Enter the SS58 address of the delegate to remove, e.g. 5dxds..."
9921
+ )
9922
+ delegate = is_valid_ss58_address_param(delegate)
9923
+
9854
9924
  self.verbosity_handler(quiet, verbose, json_output, prompt)
9855
9925
  wallet = self.wallet_ask(
9856
9926
  wallet_name=wallet_name,
@@ -9873,6 +9943,7 @@ class CLIManager:
9873
9943
  wait_for_finalization=wait_for_finalization,
9874
9944
  period=period,
9875
9945
  json_output=json_output,
9946
+ remove_all=all_,
9876
9947
  )
9877
9948
  )
9878
9949
 
@@ -10275,11 +10346,11 @@ class CLIManager:
10275
10346
  )
10276
10347
  return True
10277
10348
 
10278
- def run(self):
10349
+ def run(self) -> None:
10279
10350
  self.app()
10280
10351
 
10281
10352
 
10282
- def main():
10353
+ def main() -> None:
10283
10354
  manager = CLIManager()
10284
10355
  manager.run()
10285
10356
 
@@ -692,6 +692,7 @@ HYPERPARAMS = {
692
692
  "alpha_low": ("", RootSudoOnly.FALSE), # Derived from alpha_values
693
693
  "subnet_is_active": ("", RootSudoOnly.FALSE), # Set via btcli subnets start
694
694
  "yuma_version": ("", RootSudoOnly.FALSE), # Related to yuma3_enabled
695
+ "max_allowed_uids": ("sudo_set_max_allowed_uids", RootSudoOnly.FALSE),
695
696
  }
696
697
 
697
698
  HYPERPARAMS_MODULE = {
@@ -941,6 +942,12 @@ HYPERPARAMS_METADATA = {
941
942
  "owner_settable": True,
942
943
  "docs_link": "docs.learnbittensor.org/subnets/subnet-hyperparameters#yuma3",
943
944
  },
945
+ "max_allowed_uids": {
946
+ "description": "Maximum number of UIDs (neurons) on the subnet, essentially 'untrimming'.",
947
+ "side_effects": "See description for min_allowed_uids",
948
+ "owner_settable": True,
949
+ "docs_link": "docs.learnbittensor.org/subnets/subnet-hyperparameters#maxalloweduids",
950
+ },
944
951
  }
945
952
 
946
953
  # Help Panels for cli help
@@ -1,7 +1,8 @@
1
1
  from abc import abstractmethod
2
2
  from dataclasses import dataclass
3
+ from collections.abc import Sequence
3
4
  from enum import Enum
4
- from typing import Optional, Any, Union
5
+ from typing import Optional, Any, Union, Callable, Hashable
5
6
 
6
7
  import netaddr
7
8
  from scalecodec.utils.ss58 import ss58_encode
@@ -16,6 +17,11 @@ from bittensor_cli.src.bittensor.utils import (
16
17
  get_netuid_and_subuid_by_storage_index,
17
18
  )
18
19
 
20
+ try:
21
+ from typing import Self
22
+ except ImportError:
23
+ from typing_extensions import Self
24
+
19
25
 
20
26
  class ChainDataType(Enum):
21
27
  NeuronInfo = 1
@@ -69,9 +75,12 @@ def _chr_str(codes: tuple[int]) -> str:
69
75
  return "".join(map(chr, codes))
70
76
 
71
77
 
72
- def process_nested(data: Union[tuple, dict], chr_transform):
78
+ def process_nested(
79
+ data: Sequence[dict[Hashable, tuple[int]]] | dict | Any,
80
+ chr_transform: Callable[[tuple[int]], str],
81
+ ) -> list[dict[Hashable, str]] | dict[Hashable, str] | Any:
73
82
  """Processes nested data structures by applying a transformation function to their elements."""
74
- if isinstance(data, (list, tuple)):
83
+ if isinstance(data, Sequence):
75
84
  if len(data) > 0 and isinstance(data[0], dict):
76
85
  return [
77
86
  {k: chr_transform(v) for k, v in item.items()}
@@ -79,9 +88,12 @@ def process_nested(data: Union[tuple, dict], chr_transform):
79
88
  else None
80
89
  for item in data
81
90
  ]
91
+ # TODO @abe why do we kind of silently fail here?
82
92
  return {}
83
93
  elif isinstance(data, dict):
84
94
  return {k: chr_transform(v) for k, v in data.items()}
95
+ else:
96
+ return data
85
97
 
86
98
 
87
99
  @dataclass
@@ -127,17 +139,17 @@ class InfoBase:
127
139
  """Base dataclass for info objects."""
128
140
 
129
141
  @abstractmethod
130
- def _fix_decoded(self, decoded: Any) -> "InfoBase":
142
+ def _fix_decoded(self, decoded: Any) -> Self:
131
143
  raise NotImplementedError(
132
144
  "This is an abstract method and must be implemented in a subclass."
133
145
  )
134
146
 
135
147
  @classmethod
136
- def from_any(cls, data: Any) -> "InfoBase":
148
+ def from_any(cls, data: Any) -> Self:
137
149
  return cls._fix_decoded(data)
138
150
 
139
151
  @classmethod
140
- def list_from_any(cls, data_list: list[Any]) -> list["InfoBase"]:
152
+ def list_from_any(cls, data_list: list[Any]) -> list[Self]:
141
153
  return [cls.from_any(data) for data in data_list]
142
154
 
143
155
  def __getitem__(self, item):
@@ -297,7 +297,7 @@ class SubtensorInterface:
297
297
  self,
298
298
  hotkey_ss58: str,
299
299
  coldkey_ss58: str,
300
- netuid: Optional[int] = None,
300
+ netuid: int,
301
301
  block_hash: Optional[str] = None,
302
302
  ) -> Balance:
303
303
  """
@@ -305,42 +305,18 @@ class SubtensorInterface:
305
305
 
306
306
  :param hotkey_ss58: The SS58 address of the hotkey.
307
307
  :param coldkey_ss58: The SS58 address of the coldkey.
308
- :param netuid: The subnet ID to filter by. If provided, only returns stake for this specific
309
- subnet.
308
+ :param netuid: The subnet ID for the stake query.
310
309
  :param block_hash: The block hash at which to query the stake information.
311
310
 
312
311
  :return: Balance: The stake under the coldkey - hotkey pairing.
313
312
  """
314
- alpha_shares, hotkey_alpha, hotkey_shares = await asyncio.gather(
315
- self.query(
316
- module="SubtensorModule",
317
- storage_function="Alpha",
318
- params=[hotkey_ss58, coldkey_ss58, netuid],
319
- block_hash=block_hash,
320
- ),
321
- self.query(
322
- module="SubtensorModule",
323
- storage_function="TotalHotkeyAlpha",
324
- params=[hotkey_ss58, netuid],
325
- block_hash=block_hash,
326
- ),
327
- self.query(
328
- module="SubtensorModule",
329
- storage_function="TotalHotkeyShares",
330
- params=[hotkey_ss58, netuid],
331
- block_hash=block_hash,
332
- ),
313
+ result = await self.query_runtime_api(
314
+ runtime_api="StakeInfoRuntimeApi",
315
+ method="get_stake_info_for_hotkey_coldkey_netuid",
316
+ params=[hotkey_ss58, coldkey_ss58, netuid],
317
+ block_hash=block_hash,
333
318
  )
334
-
335
- alpha_shares_as_float = fixed_to_float(alpha_shares or 0)
336
- hotkey_shares_as_float = fixed_to_float(hotkey_shares or 0)
337
-
338
- if hotkey_shares_as_float == 0:
339
- return Balance.from_rao(0).set_unit(netuid=netuid)
340
-
341
- stake = alpha_shares_as_float / hotkey_shares_as_float * (hotkey_alpha or 0)
342
-
343
- return Balance.from_rao(int(stake)).set_unit(netuid=netuid)
319
+ return StakeInfo.from_any(result).stake
344
320
 
345
321
  # Alias
346
322
  get_stake = get_stake_for_coldkey_and_hotkey
@@ -1249,6 +1225,9 @@ class SubtensorInterface:
1249
1225
  )
1250
1226
  inner_hash = ""
1251
1227
  if mev_protection:
1228
+ max_mev_era = 8
1229
+ if era is None or era["period"] > max_mev_era:
1230
+ era = {"period": max_mev_era}
1252
1231
  next_nonce = await self.substrate.get_account_next_index(
1253
1232
  keypair.ss58_address
1254
1233
  )
@@ -1292,8 +1271,8 @@ class SubtensorInterface:
1292
1271
  err_msg = format_error_message(e)
1293
1272
  if mev_protection and "'result': 'invalid'" in str(e).lower():
1294
1273
  err_msg = (
1295
- f"MEV Shield extrinsic rejected as invalid. "
1296
- f"This usually means the MEV Shield NextKey changed between fetching and submission."
1274
+ "MEV Shield extrinsic rejected as invalid. "
1275
+ "This usually means the MEV Shield NextKey changed between fetching and submission."
1297
1276
  )
1298
1277
  if proxy and "Invalid Transaction" in err_msg:
1299
1278
  extrinsic_fee, signer_balance = await asyncio.gather(
@@ -1310,6 +1289,84 @@ class SubtensorInterface:
1310
1289
  )
1311
1290
  return False, err_msg, None
1312
1291
 
1292
+ async def sign_and_send_batch_extrinsic(
1293
+ self,
1294
+ calls: list[GenericCall],
1295
+ wallet: Wallet,
1296
+ wait_for_inclusion: bool = True,
1297
+ wait_for_finalization: bool = False,
1298
+ era: Optional[dict[str, int]] = None,
1299
+ proxy: Optional[str] = None,
1300
+ nonce: Optional[int] = None,
1301
+ sign_with: Literal["coldkey", "hotkey", "coldkeypub"] = "coldkey",
1302
+ announce_only: bool = False,
1303
+ mev_protection: bool = False,
1304
+ block_hash: Optional[str] = None,
1305
+ ) -> tuple[bool, str, Optional[AsyncExtrinsicReceipt]]:
1306
+ """
1307
+ Wraps multiple extrinsic calls into a single Utility.batch_all transaction
1308
+ and submits it. This reduces fees by combining N separate transactions into one.
1309
+
1310
+ batch_all is atomic: if any call in the batch fails, the entire batch reverts.
1311
+
1312
+ For a single call, this delegates directly to sign_and_send_extrinsic without
1313
+ wrapping, so there's no overhead for non-batch use cases.
1314
+
1315
+ :param calls: list of prepared GenericCall objects to batch together.
1316
+ :param wallet: the wallet whose key will sign the extrinsic.
1317
+ :param wait_for_inclusion: wait until the extrinsic is included on chain.
1318
+ :param wait_for_finalization: wait until the extrinsic is finalized on chain.
1319
+ :param era: validity period in blocks for the transaction.
1320
+ :param proxy: the real account if using a proxy. None otherwise.
1321
+ :param nonce: explicit nonce for submission. Fetched automatically if None.
1322
+ :param sign_with: which wallet keypair signs the extrinsic.
1323
+ :param announce_only: make the call as a proxy announcement.
1324
+ :param mev_protection: encrypt the extrinsic via MEV Shield.
1325
+ :param block_hash: cached block hash for compose_call. Fetched if None.
1326
+
1327
+ :return: (success, error message or inner hash, extrinsic receipt | None)
1328
+ """
1329
+ if not calls:
1330
+ return False, "No calls to batch", None
1331
+
1332
+ # No need to wrap a single call in a batch
1333
+ if len(calls) == 1:
1334
+ return await self.sign_and_send_extrinsic(
1335
+ call=calls[0],
1336
+ wallet=wallet,
1337
+ wait_for_inclusion=wait_for_inclusion,
1338
+ wait_for_finalization=wait_for_finalization,
1339
+ era=era,
1340
+ proxy=proxy,
1341
+ nonce=nonce,
1342
+ sign_with=sign_with,
1343
+ announce_only=announce_only,
1344
+ mev_protection=mev_protection,
1345
+ )
1346
+
1347
+ if block_hash is None:
1348
+ block_hash = await self.substrate.get_chain_head()
1349
+
1350
+ batch_call = await self.substrate.compose_call(
1351
+ call_module="Utility",
1352
+ call_function="batch_all",
1353
+ call_params={"calls": calls},
1354
+ block_hash=block_hash,
1355
+ )
1356
+
1357
+ return await self.sign_and_send_extrinsic(
1358
+ call=batch_call,
1359
+ wallet=wallet,
1360
+ wait_for_inclusion=wait_for_inclusion,
1361
+ wait_for_finalization=wait_for_finalization,
1362
+ era=era,
1363
+ proxy=proxy,
1364
+ nonce=nonce,
1365
+ sign_with=sign_with,
1366
+ announce_only=announce_only,
1367
+ mev_protection=mev_protection,
1368
+ )
1369
+
1313
1370
  async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
1314
1371
  """
1315
1372
  This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys
@@ -1488,25 +1545,6 @@ class SubtensorInterface:
1488
1545
 
1489
1546
  return all_delegates_details
1490
1547
 
1491
- async def get_stake_for_coldkey_and_hotkey_on_netuid(
1492
- self,
1493
- hotkey_ss58: str,
1494
- coldkey_ss58: str,
1495
- netuid: int,
1496
- block_hash: Optional[str] = None,
1497
- ) -> "Balance":
1498
- """Returns the stake under a coldkey - hotkey - netuid pairing"""
1499
- _result = await self.query(
1500
- "SubtensorModule",
1501
- "Alpha",
1502
- [hotkey_ss58, coldkey_ss58, netuid],
1503
- block_hash,
1504
- )
1505
- if _result is None:
1506
- return Balance(0).set_unit(netuid)
1507
- else:
1508
- return Balance.from_rao(fixed_to_float(_result)).set_unit(int(netuid))
1509
-
1510
1548
  async def get_mechagraph_info(
1511
1549
  self, netuid: int, mech_id: int, block_hash: Optional[str] = None
1512
1550
  ) -> Optional[MetagraphInfo]:
@@ -2387,7 +2425,7 @@ class SubtensorInterface:
2387
2425
  After manual claim, claimable (available) stake will be added to subnet stake.
2388
2426
  """
2389
2427
  root_stake, root_claimable_rate, root_claimed = await asyncio.gather(
2390
- self.get_stake_for_coldkey_and_hotkey_on_netuid(
2428
+ self.get_stake(
2391
2429
  coldkey_ss58=coldkey_ss58,
2392
2430
  hotkey_ss58=hotkey_ss58,
2393
2431
  netuid=0,
@@ -1757,8 +1757,9 @@ def prompt_for_subnet_identity(
1757
1757
  "github_repo",
1758
1758
  "[blue]GitHub repository URL [dim](optional)[/blue]",
1759
1759
  github_repo,
1760
- lambda x: x
1761
- and (not is_valid_github_url(x) or len(x.encode("utf-8")) > 1024),
1760
+ lambda x: (
1761
+ x and (not is_valid_github_url(x) or len(x.encode("utf-8")) > 1024)
1762
+ ),
1762
1763
  "[red]Error:[/red] Please enter a valid GitHub repository URL (e.g., https://github.com/username/repo).",
1763
1764
  ),
1764
1765
  (
@@ -255,8 +255,8 @@ async def create_proxy(
255
255
  async def remove_proxy(
256
256
  subtensor: "SubtensorInterface",
257
257
  wallet: "Wallet",
258
- proxy_type: ProxyType,
259
- delegate: str,
258
+ proxy_type: Optional[ProxyType],
259
+ delegate: Optional[str],
260
260
  delay: int,
261
261
  prompt: bool,
262
262
  decline: bool,
@@ -265,18 +265,35 @@ async def remove_proxy(
265
265
  wait_for_finalization: bool,
266
266
  period: int,
267
267
  json_output: bool,
268
+ remove_all: bool = False,
268
269
  ) -> None:
269
270
  """
270
- Executes the remove proxy call on the chain
271
+ Executes the remove proxy call on the chain.
272
+
273
+ If remove_all is True, removes all proxies for the account.
274
+ Otherwise, removes a specific proxy identified by delegate, proxy_type, and delay.
271
275
  """
276
+ # Handle confirmation prompt
272
277
  if prompt:
273
- if not confirm_action(
274
- f"This will remove a proxy of type {proxy_type.value} for delegate {delegate}."
275
- f"Do you want to proceed?",
276
- decline=decline,
277
- quiet=quiet,
278
- ):
279
- return None
278
+ if remove_all:
279
+ confirmation = Prompt.ask(
280
+ "[red]WARNING:[/red] This will remove ALL proxies associated with this account.\n"
281
+ "[red]All proxy relationships will be permanently lost.[/red]\n"
282
+ "To proceed, enter [red]REMOVE[/red]"
283
+ )
284
+ if confirmation != "REMOVE":
285
+ print_error("Invalid input. Operation cancelled.")
286
+ return None
287
+ else:
288
+ if not confirm_action(
289
+ f"This will remove a proxy of type {proxy_type.value} for delegate {delegate}. "
290
+ f"Do you want to proceed?",
291
+ decline=decline,
292
+ quiet=quiet,
293
+ ):
294
+ return None
295
+
296
+ # Unlock wallet
280
297
  if not (ulw := unlock_key(wallet, print_out=not json_output)).success:
281
298
  if not json_output:
282
299
  print_error(ulw.message)
@@ -289,15 +306,25 @@ async def remove_proxy(
289
306
  }
290
307
  )
291
308
  return None
292
- call = await subtensor.substrate.compose_call(
293
- call_module="Proxy",
294
- call_function="remove_proxy",
295
- call_params={
296
- "proxy_type": proxy_type.value,
297
- "delay": delay,
298
- "delegate": delegate,
299
- },
300
- )
309
+
310
+ # Compose the appropriate call
311
+ if remove_all:
312
+ call = await subtensor.substrate.compose_call(
313
+ call_module="Proxy",
314
+ call_function="remove_proxies",
315
+ call_params={},
316
+ )
317
+ else:
318
+ call = await subtensor.substrate.compose_call(
319
+ call_module="Proxy",
320
+ call_function="remove_proxy",
321
+ call_params={
322
+ "proxy_type": proxy_type.value,
323
+ "delay": delay,
324
+ "delegate": delegate,
325
+ },
326
+ )
327
+
301
328
  return await submit_proxy(
302
329
  subtensor=subtensor,
303
330
  wallet=wallet,