From d348fb3b6a8e4da30c5ba599480a4048c4ca24bf Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 17 Sep 2025 21:47:14 +0200 Subject: [PATCH 1/8] Initial implementation --- bittensor_cli/cli.py | 50 ++++++++++++++++++++++++++ bittensor_cli/src/__init__.py | 1 + bittensor_cli/src/commands/sudo.py | 56 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 88ae40580..d1cf71249 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -960,6 +960,9 @@ def __init__(self): self.sudo_app.command("get-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])( self.sudo_get_take ) + self.sudo_app.command("trim", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( + self.sudo_trim + ) # subnets commands self.subnets_app.command( @@ -5343,6 +5346,53 @@ def sudo_get_take( sudo.display_current_take(self.initialize_chain(network), wallet) ) + def sudo_trim( + self, + network: Optional[list[str]] = Options.network, + wallet_name: Optional[str] = Options.wallet_name, + wallet_path: Optional[str] = Options.wallet_path, + wallet_hotkey: Optional[str] = Options.wallet_hotkey, + netuid: int = Options.netuid, + max_uids: int = typer.Option( + None, + "--max", + "--max-uids", + help="The maximum number of allowed uids to which to trim", + prompt="Max UIDs", + ), + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + json_output: bool = Options.json_output, + prompt: bool = Options.prompt, + period: int = Options.period, + ): + """ + Allows subnet owners to trim UIDs on their subnet to a specified max number of netuids. + + EXAMPLE + [green]$[/green] btcli sudo trim --netuid 95 --wallet-name my_wallet --wallet-hotkey my_hotkey --max 6 + """ + self.verbosity_handler(quiet, verbose, json_output) + + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], + validate=WV.WALLET_AND_HOTKEY, + ) + self._run_command( + sudo.trim( + subtensor=self.initialize_chain(network), + wallet=wallet, + netuid=netuid, + max_n=max_uids, + period=period, + json_output=json_output, + prompt=prompt, + ) + ) + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index ba96fe488..6ee781a46 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -666,6 +666,7 @@ class WalletValidationTypes(Enum): "user_liquidity_enabled": ("toggle_user_liquidity", False), "bonds_reset_enabled": ("sudo_set_bonds_reset_enabled", False), "transfers_enabled": ("sudo_set_toggle_transfer", False), + "min_allowed_uids": ("sudo_set_min_allowed_uids", True), } HYPERPARAMS_MODULE = { diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index e6ac31185..64c9cd202 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -955,3 +955,59 @@ async def _do_set_take() -> bool: result_ = await _do_set_take() return result_ + + +async def trim( + wallet: Wallet, + subtensor: "SubtensorInterface", + netuid: int, + max_n: int, + period: int, + prompt: bool, + json_output: bool, +) -> bool: + """ + Trims a subnet's UIDs to a specified amount + """ + print_verbose("Confirming subnet owner") + subnet_owner = await subtensor.query( + module="SubtensorModule", + storage_function="SubnetOwner", + params=[netuid], + ) + if subnet_owner != wallet.coldkeypub.ss58_address: + err_msg = "This wallet doesn't own the specified subnet." + if json_output: + json_console.print_json(data={"success": False, "message": err_msg}) + else: + err_console.print(f":cross_mark: [red]{err_msg}[/red]") + return False + if prompt and not json_output: + if not Confirm.ask( + f"You are about to trim UIDs on SN{netuid} to a limit of {max_n}", + default=False, + ): + err_console.print(":cross_mark: [red]User aborted.[/red]") + call = await subtensor.substrate.compose_call( + call_module="AdminUtils", + call_function="sudo_trim_to_max_allowed_uids", + call_params=[netuid, max_n], + ) + success, err_msg = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, era={"period": period} + ) + if not success: + if json_output: + json_console.print_json(data={"success": False, "message": err_msg}) + else: + err_console.print(f":cross_mark: [red]{err_msg}[/red]") + return False + else: + msg = f"Successfully trimmed UIDs on SN{netuid} to {max_n}" + if json_output: + json_console.print_json(data={"success": True, "message": msg}) + else: + console.print( + f":white_heavy_check_mark: [dark_sea_green3]{msg}[/dark_sea_green3]" + ) + return True From c4a182a8211c6002015e59f70d6eed1a37554319 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 17 Sep 2025 21:59:17 +0200 Subject: [PATCH 2/8] Updated and working --- bittensor_cli/cli.py | 4 ++-- bittensor_cli/src/commands/sudo.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d1cf71249..4484f5288 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -5378,8 +5378,8 @@ def sudo_trim( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, + ask_for=[WO.NAME, WO.PATH], + validate=WV.WALLET, ) self._run_command( sudo.trim( diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 64c9cd202..bb4bb43de 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -991,7 +991,7 @@ async def trim( call = await subtensor.substrate.compose_call( call_module="AdminUtils", call_function="sudo_trim_to_max_allowed_uids", - call_params=[netuid, max_n], + call_params={"netuid": netuid, "max_n": max_n}, ) success, err_msg = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, era={"period": period} From cb88ee67153c9229828c48b76fe6c8387562c951 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 17 Sep 2025 22:07:13 +0200 Subject: [PATCH 3/8] Added tests --- tests/e2e_tests/test_hyperparams_setting.py | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/e2e_tests/test_hyperparams_setting.py b/tests/e2e_tests/test_hyperparams_setting.py index 3af86c140..916b00cac 100644 --- a/tests/e2e_tests/test_hyperparams_setting.py +++ b/tests/e2e_tests/test_hyperparams_setting.py @@ -120,4 +120,53 @@ def test_hyperparams_setting(local_chain, wallet_setup): cmd_json = json.loads(cmd.stdout) assert cmd_json["success"] is True, (key, new_val, cmd.stdout, cmd_json) print(f"Successfully set hyperparameter {key} to value {new_val}") + # also test hidden hyperparam + cmd = exec_command_alice( + command="sudo", + sub_command="set", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--netuid", + netuid, + "--json-out", + "--no-prompt", + "--param", + "min_allowed_uids", + "--value", + "110", + ], + ) + cmd_json = json.loads(cmd.stdout) + assert cmd_json["success"] is True, (cmd.stdout, cmd_json) print("Successfully set hyperparameters") + print("Testing trimming UIDs") + cmd = exec_command_alice( + command="sudo", + sub_command="trim", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--netuid", + netuid, + "--max", + "120", + "--json-out", + "--no-prompt", + ], + ) + cmd_json = json.loads(cmd.stdout) + assert cmd_json["success"] is True, (cmd.stdout, cmd_json) + print("Successfully trimmed UIDs") From 4448ef878f55e3f56e0a3053f01578da784a13bc Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Thu, 18 Sep 2025 16:04:35 -0700 Subject: [PATCH 4/8] fixes params + fee calc --- bittensor_cli/src/bittensor/subtensor_interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index cafef0439..27df7d94c 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1462,7 +1462,7 @@ async def subnet( if not result: raise ValueError(f"Subnet {netuid} not found") subnet_ = DynamicInfo.from_any(result) - subnet_.price = price + subnet_.price = price if netuid != 0 else Balance.from_tao(1.0) return subnet_ async def get_owned_hotkeys( @@ -1545,13 +1545,13 @@ async def sim_swap( "sim_swap_tao_for_alpha", params={ "netuid": destination_netuid, - "tao": intermediate_result.tao_amount, + "tao": intermediate_result.tao_amount.rao, }, block_hash=block_hash, ), destination_netuid, ) - secondary_fee = (result.tao_fee * sn_price).set_unit(origin_netuid) + secondary_fee = (result.tao_fee / sn_price.tao).set_unit(origin_netuid) result.alpha_fee = result.alpha_fee + secondary_fee return result elif origin_netuid > 0: From 0650ebfbaab56c07eeb42e040bc97cde8acf932b Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Thu, 18 Sep 2025 16:18:31 -0700 Subject: [PATCH 5/8] edge-case for wallets balance for corrupt file --- bittensor_cli/src/commands/wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 1112d195d..3b13a7fca 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -549,7 +549,7 @@ def _get_coldkey_ss58_addresses_for_path(path: str) -> tuple[list[str], list[str coldkey_paths = [ os.path.join(abs_path, wallet, "coldkeypub.txt") for wallet in wallets - if os.path.exists(os.path.join(abs_path, wallet, "coldkeypub.txt")) + if os.path.isfile(os.path.join(abs_path, wallet, "coldkeypub.txt")) ] ss58_addresses = [Keyfile(path).keypair.ss58_address for path in coldkey_paths] From 64405fdd1e2d1092a6c16651f20eca8a16724c0c Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 19 Sep 2025 15:19:46 +0200 Subject: [PATCH 6/8] Update changelog+version --- CHANGELOG.md | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ea625bc..27b5ca54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 9.11.2 /2025-09-19 +* Fix: Stake movement between non-root sns by @ibraheem-abe in https://github.com/opentensor/btcli/pull/629 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.11.1...v9.11.2 + ## 9.11.1 /2025-09-16 * Transfer not staking warning by @thewhaleking in https://github.com/opentensor/btcli/pull/618 diff --git a/pyproject.toml b/pyproject.toml index 56814fe27..b7c11b579 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.11.1" +version = "9.11.2" description = "Bittensor CLI" readme = "README.md" authors = [ From b9d8cd325905f950903d1fbe7b831d5a04207ae2 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 19 Sep 2025 15:22:11 +0200 Subject: [PATCH 7/8] Revert "Merge pull request #628 from opentensor/feat/thewhaleking/trim-uids" This reverts commit dd051a3cf8645ef7b315569c30ad97a47fbdc997, reversing changes made to aad303e1364cdabcf607883b279da842632f3620. --- bittensor_cli/cli.py | 50 ------------------ bittensor_cli/src/__init__.py | 1 - bittensor_cli/src/commands/sudo.py | 56 --------------------- tests/e2e_tests/test_hyperparams_setting.py | 49 ------------------ 4 files changed, 156 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 4484f5288..88ae40580 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -960,9 +960,6 @@ def __init__(self): self.sudo_app.command("get-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])( self.sudo_get_take ) - self.sudo_app.command("trim", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( - self.sudo_trim - ) # subnets commands self.subnets_app.command( @@ -5346,53 +5343,6 @@ def sudo_get_take( sudo.display_current_take(self.initialize_chain(network), wallet) ) - def sudo_trim( - self, - network: Optional[list[str]] = Options.network, - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - netuid: int = Options.netuid, - max_uids: int = typer.Option( - None, - "--max", - "--max-uids", - help="The maximum number of allowed uids to which to trim", - prompt="Max UIDs", - ), - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - json_output: bool = Options.json_output, - prompt: bool = Options.prompt, - period: int = Options.period, - ): - """ - Allows subnet owners to trim UIDs on their subnet to a specified max number of netuids. - - EXAMPLE - [green]$[/green] btcli sudo trim --netuid 95 --wallet-name my_wallet --wallet-hotkey my_hotkey --max 6 - """ - self.verbosity_handler(quiet, verbose, json_output) - - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH], - validate=WV.WALLET, - ) - self._run_command( - sudo.trim( - subtensor=self.initialize_chain(network), - wallet=wallet, - netuid=netuid, - max_n=max_uids, - period=period, - json_output=json_output, - prompt=prompt, - ) - ) - def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 6ee781a46..ba96fe488 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -666,7 +666,6 @@ class WalletValidationTypes(Enum): "user_liquidity_enabled": ("toggle_user_liquidity", False), "bonds_reset_enabled": ("sudo_set_bonds_reset_enabled", False), "transfers_enabled": ("sudo_set_toggle_transfer", False), - "min_allowed_uids": ("sudo_set_min_allowed_uids", True), } HYPERPARAMS_MODULE = { diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index bb4bb43de..e6ac31185 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -955,59 +955,3 @@ async def _do_set_take() -> bool: result_ = await _do_set_take() return result_ - - -async def trim( - wallet: Wallet, - subtensor: "SubtensorInterface", - netuid: int, - max_n: int, - period: int, - prompt: bool, - json_output: bool, -) -> bool: - """ - Trims a subnet's UIDs to a specified amount - """ - print_verbose("Confirming subnet owner") - subnet_owner = await subtensor.query( - module="SubtensorModule", - storage_function="SubnetOwner", - params=[netuid], - ) - if subnet_owner != wallet.coldkeypub.ss58_address: - err_msg = "This wallet doesn't own the specified subnet." - if json_output: - json_console.print_json(data={"success": False, "message": err_msg}) - else: - err_console.print(f":cross_mark: [red]{err_msg}[/red]") - return False - if prompt and not json_output: - if not Confirm.ask( - f"You are about to trim UIDs on SN{netuid} to a limit of {max_n}", - default=False, - ): - err_console.print(":cross_mark: [red]User aborted.[/red]") - call = await subtensor.substrate.compose_call( - call_module="AdminUtils", - call_function="sudo_trim_to_max_allowed_uids", - call_params={"netuid": netuid, "max_n": max_n}, - ) - success, err_msg = await subtensor.sign_and_send_extrinsic( - call=call, wallet=wallet, era={"period": period} - ) - if not success: - if json_output: - json_console.print_json(data={"success": False, "message": err_msg}) - else: - err_console.print(f":cross_mark: [red]{err_msg}[/red]") - return False - else: - msg = f"Successfully trimmed UIDs on SN{netuid} to {max_n}" - if json_output: - json_console.print_json(data={"success": True, "message": msg}) - else: - console.print( - f":white_heavy_check_mark: [dark_sea_green3]{msg}[/dark_sea_green3]" - ) - return True diff --git a/tests/e2e_tests/test_hyperparams_setting.py b/tests/e2e_tests/test_hyperparams_setting.py index 916b00cac..3af86c140 100644 --- a/tests/e2e_tests/test_hyperparams_setting.py +++ b/tests/e2e_tests/test_hyperparams_setting.py @@ -120,53 +120,4 @@ def test_hyperparams_setting(local_chain, wallet_setup): cmd_json = json.loads(cmd.stdout) assert cmd_json["success"] is True, (key, new_val, cmd.stdout, cmd_json) print(f"Successfully set hyperparameter {key} to value {new_val}") - # also test hidden hyperparam - cmd = exec_command_alice( - command="sudo", - sub_command="set", - extra_args=[ - "--wallet-path", - wallet_path_alice, - "--network", - "ws://127.0.0.1:9945", - "--wallet-name", - wallet_alice.name, - "--wallet-hotkey", - wallet_alice.hotkey_str, - "--netuid", - netuid, - "--json-out", - "--no-prompt", - "--param", - "min_allowed_uids", - "--value", - "110", - ], - ) - cmd_json = json.loads(cmd.stdout) - assert cmd_json["success"] is True, (cmd.stdout, cmd_json) print("Successfully set hyperparameters") - print("Testing trimming UIDs") - cmd = exec_command_alice( - command="sudo", - sub_command="trim", - extra_args=[ - "--wallet-path", - wallet_path_alice, - "--network", - "ws://127.0.0.1:9945", - "--wallet-name", - wallet_alice.name, - "--wallet-hotkey", - wallet_alice.hotkey_str, - "--netuid", - netuid, - "--max", - "120", - "--json-out", - "--no-prompt", - ], - ) - cmd_json = json.loads(cmd.stdout) - assert cmd_json["success"] is True, (cmd.stdout, cmd_json) - print("Successfully trimmed UIDs") From 036e4a06a21d95d53893203da7de78938d3937ed Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 19 Sep 2025 15:42:25 +0200 Subject: [PATCH 8/8] Fix test by making user_liquidity_enabled root sudo only --- bittensor_cli/src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index ba96fe488..f93aed504 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -663,7 +663,7 @@ class WalletValidationTypes(Enum): ), "yuma3_enabled": ("sudo_set_yuma3_enabled", False), "alpha_sigmoid_steepness": ("sudo_set_alpha_sigmoid_steepness", True), - "user_liquidity_enabled": ("toggle_user_liquidity", False), + "user_liquidity_enabled": ("toggle_user_liquidity", True), "bonds_reset_enabled": ("sudo_set_bonds_reset_enabled", False), "transfers_enabled": ("sudo_set_toggle_transfer", False), }