From 7ea545b3c6b65d5878dde2f78d64c43d17a6c629 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:30:31 -0800 Subject: [PATCH 01/20] add sudo_buyback cmd --- bittensor_cli/cli.py | 95 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index dadf57eb..e158f066 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1164,6 +1164,9 @@ def __init__(self): self.sudo_app.command("trim", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( self.sudo_trim ) + self.sudo_app.command("buyback", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( + self.sudo_buyback + ) # subnets commands self.subnets_app.command( @@ -7382,6 +7385,98 @@ def sudo_trim( ) ) + def sudo_buyback( + 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_ss58, + netuid: int = Options.netuid, + amount: float = typer.Option( + None, + "--amount", + "-a", + help="Amount of TAO to buyback", + ), + proxy: Optional[str] = Options.proxy, + rate_tolerance: Optional[float] = Options.rate_tolerance, + safe_staking: Optional[bool] = Options.safe_staking, + mev_protection: bool = Options.mev_protection, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + json_output: bool = Options.json_output, + prompt: bool = Options.prompt, + decline: bool = Options.decline, + period: int = Options.period, + ): + """ + Allows subnet owners to buy back alpha on their subnet by staking TAO and immediately burning the acquired alpha. + + [bold]Examples:[/bold] + 1. Buyback 10 TAO on subnet 14: + [green]$[/green] btcli sudo buyback --netuid 14 --amount 10 + 2. Buyback 10 TAO on subnet 14 with safe staking and 5% rate tolerance: + [green]$[/green] btcli sudo buyback --netuid 14 --amount 10 --tolerance 0.05 + 3. Buyback 10 TAO on subnet 14 with a specific hotkey: + [green]$[/green] btcli sudo buyback --netuid 14 --amount 10 --wallet-hotkey + """ + self.verbosity_handler(quiet, verbose, json_output, prompt) + proxy = self.is_valid_proxy_name_or_ss58(proxy, announce_only=False) + safe_staking = self.ask_safe_staking(safe_staking) + if safe_staking: + rate_tolerance = self.ask_rate_tolerance(rate_tolerance) + + print_protection_warnings( + mev_protection=mev_protection, + safe_staking=safe_staking, + command_name="sudo buyback", + ) + + if not wallet_hotkey: + wallet_hotkey = Prompt.ask( + "Enter the [blue]hotkey[/blue] name or " + "[blue]hotkey ss58 address[/blue] [dim](to use for the buyback)[/dim]", + default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + ) + + if wallet_hotkey and is_valid_ss58_address(wallet_hotkey): + hotkey_ss58 = wallet_hotkey + wallet = self.wallet_ask( + wallet_name, + wallet_path, + None, + ask_for=[WO.NAME, WO.PATH], + validate=WV.WALLET, + ) + else: + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], + validate=WV.WALLET_AND_HOTKEY, + ) + hotkey_ss58 = get_hotkey_pub_ss58(wallet) + + self._run_command( + sudo.buyback( + subtensor=self.initialize_chain(network), + wallet=wallet, + netuid=netuid, + amount=amount, + hotkey_ss58=hotkey_ss58, + safe_staking=safe_staking, + proxy=proxy, + rate_tolerance=rate_tolerance, + mev_protection=mev_protection, + json_output=json_output, + prompt=prompt, + decline=decline, + quiet=quiet, + period=period, + ) + ) + # Subnets def subnets_list( From 596a962103d1b925b88fa92699b11fc9a1684a74 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:31:31 -0800 Subject: [PATCH 02/20] add table definition --- bittensor_cli/src/commands/sudo.py | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index ec6d1461..bb7af614 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -18,10 +18,16 @@ DelegatesDetails, COLOR_PALETTE, ) +from bittensor_cli.src.bittensor.balances import Balance +from bittensor_cli.src.bittensor.extrinsics.mev_shield import ( + extract_mev_shield_id, + wait_for_extrinsic_by_hash, +) from bittensor_cli.src.bittensor.chain_data import decode_account_id from bittensor_cli.src.bittensor.utils import ( confirm_action, console, + create_table, print_error, print_success, print_verbose, @@ -94,6 +100,56 @@ def string_to_bool(val) -> Union[bool, Type[ValueError]]: return ValueError +def _define_buyback_table( + wallet: Wallet, + subtensor: "SubtensorInterface", + safe_staking: bool, + rate_tolerance: float, +) -> Table: + table = create_table( + title=f"\n[{COLOR_PALETTE.G.HEADER}]Subnet Buyback:\n" + f"Wallet: [{COLOR_PALETTE.G.CK}]{wallet.name}[/{COLOR_PALETTE.G.CK}], " + f"Coldkey ss58: [{COLOR_PALETTE.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE.G.CK}]\n" + f"Network: {subtensor.network}[/{COLOR_PALETTE.G.HEADER}]\n", + ) + table.add_column("Netuid", justify="center", style="grey89") + table.add_column( + "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"] + ) + table.add_column( + "Amount (τ)", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO"], + ) + table.add_column( + "Rate (per τ)", + justify="center", + style=COLOR_PALETTE["POOLS"]["RATE"], + ) + table.add_column( + "Est. Burned", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + ) + table.add_column( + "Fee (τ)", + justify="center", + style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], + ) + table.add_column( + "Extrinsic Fee (τ)", + justify="center", + style=COLOR_PALETTE.STAKE.TAO, + ) + if safe_staking: + table.add_column( + f"Rate with tolerance: [blue]({rate_tolerance * 100}%)[/blue]", + justify="center", + style=COLOR_PALETTE["POOLS"]["RATE"], + ) + return table + + def search_metadata( param_name: str, value: Union[str, bool, float, list[float]], From b7424fec4e3638f96600c997e568b1eaa702a6ca Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:32:07 -0800 Subject: [PATCH 03/20] wip --- bittensor_cli/src/commands/sudo.py | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index bb7af614..d63db5e5 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -100,6 +100,53 @@ def string_to_bool(val) -> Union[bool, Type[ValueError]]: return ValueError +async def buyback( + wallet: Wallet, + subtensor: "SubtensorInterface", + netuid: int, + amount: float, + hotkey_ss58: Optional[str], + safe_staking: bool, + proxy: Optional[str], + rate_tolerance: Optional[float], + mev_protection: bool, + json_output: bool, + prompt: bool, + decline: bool, + quiet: bool, + period: int, +) -> bool: + """ + Perform a subnet buyback (owner-only). Stakes TAO into the subnet and immediately burns the acquired alpha. + """ + subnet_owner = await subtensor.query( + module="SubtensorModule", + storage_function="SubnetOwner", + params=[netuid], + ) + if subnet_owner != wallet.coldkeypub.ss58_address: + err_msg = ( + f"Coldkey {wallet.coldkeypub.ss58_address} does not own subnet {netuid}." + ) + if json_output: + json_console.print_json( + data={ + "success": False, + "message": err_msg, + "extrinsic_identifier": None, + } + ) + else: + print_error(err_msg) + return False + + subnet_info = await subtensor.subnet(netuid=netuid) + buyback_amount = Balance.from_tao(amount) + rate_tolerance = rate_tolerance if rate_tolerance is not None else 0.0 + + + + def _define_buyback_table( wallet: Wallet, subtensor: "SubtensorInterface", From 7314d75bc07bf9c49cc2280413aa8fb6b5068043 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:32:38 -0800 Subject: [PATCH 04/20] safe staking + table output --- bittensor_cli/src/commands/sudo.py | 70 +++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index d63db5e5..40a1584d 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -144,7 +144,75 @@ async def buyback( buyback_amount = Balance.from_tao(amount) rate_tolerance = rate_tolerance if rate_tolerance is not None else 0.0 - + price_limit: Optional[Balance] = None + if safe_staking: + price_limit = Balance.from_tao(subnet_info.price.tao * (1 + rate_tolerance)) + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount": buyback_amount.rao, + "limit": price_limit.rao if price_limit else None, + } + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="subnet_buyback", + call_params=call_params, + ) + + if not json_output: + extrinsic_fee = await subtensor.get_extrinsic_fee( + call, wallet.coldkeypub, proxy=proxy + ) + amount_minus_fee = buyback_amount - extrinsic_fee + sim_swap = await subtensor.sim_swap( + origin_netuid=0, + destination_netuid=netuid, + amount=amount_minus_fee.rao, + ) + received_amount = sim_swap.alpha_amount + + current_price_float = float(subnet_info.price.tao) + rate = 1.0 / current_price_float + + table = _define_buyback_table( + wallet=wallet, + subtensor=subtensor, + safe_staking=safe_staking, + rate_tolerance=rate_tolerance, + ) + row = [ + str(netuid), + hotkey_ss58, + str(buyback_amount), + str(rate) + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", + str(received_amount.set_unit(netuid)), + str(sim_swap.tao_fee), + str(extrinsic_fee), + ] + if safe_staking: + price_with_tolerance = current_price_float * (1 + rate_tolerance) + rate_with_tolerance = 1.0 / price_with_tolerance + rate_with_tolerance_str = ( + f"{rate_with_tolerance:.4f} " + f"{Balance.get_unit(netuid)}/{Balance.get_unit(0)} " + ) + row.append(rate_with_tolerance_str) + + table.add_row(*row) + console.print(table) + + if prompt and not confirm_action( + "Would you like to continue?", decline=decline, quiet=quiet + ): + print_error("User aborted.") + return False + + if not unlock_key(wallet).success: + return False + + def _define_buyback_table( From 1cacf1b5621e2c4fe6837908eed96b980b66e503 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:32:56 -0800 Subject: [PATCH 05/20] execute buyback call --- bittensor_cli/src/commands/sudo.py | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 40a1584d..511a84f0 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -212,7 +212,71 @@ async def buyback( if not unlock_key(wallet).success: return False + with console.status( + f":satellite: Performing subnet buyback on [bold]{netuid}[/bold]...", + spinner="earth", + ) as status: + next_nonce = await subtensor.substrate.get_account_next_index( + wallet.coldkeypub.ss58_address + ) + success, err_msg, ext_receipt = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + nonce=next_nonce, + era={"period": period}, + proxy=proxy, + mev_protection=mev_protection, + ) + if not success: + if json_output: + json_console.print_json( + data={ + "success": False, + "message": err_msg, + "extrinsic_identifier": None, + } + ) + else: + print_error(err_msg, status=status) + return False + + if mev_protection: + inner_hash = err_msg + mev_shield_id = await extract_mev_shield_id(ext_receipt) + mev_success, mev_error, ext_receipt = await wait_for_extrinsic_by_hash( + subtensor=subtensor, + extrinsic_hash=inner_hash, + shield_id=mev_shield_id, + submit_block_hash=ext_receipt.block_hash, + status=status, + ) + if not mev_success: + status.stop() + if json_output: + json_console.print_json( + data={ + "success": False, + "message": mev_error, + "extrinsic_identifier": None, + } + ) + else: + print_error(mev_error, status=status) + return False + + ext_id = await ext_receipt.get_extrinsic_identifier() + + msg = f"Subnet buyback succeeded on SN{netuid}." + if json_output: + json_console.print_json( + data={"success": True, "message": msg, "extrinsic_identifier": ext_id} + ) + else: + await print_extrinsic_id(ext_receipt) + print_success(msg) + + return True def _define_buyback_table( From 3630f35f636fbe884fa35902c686d08a2136fbe8 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:34:14 -0800 Subject: [PATCH 06/20] add guardrails --- bittensor_cli/cli.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e158f066..c94fec42 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -7458,6 +7458,14 @@ def sudo_buyback( ) hotkey_ss58 = get_hotkey_pub_ss58(wallet) + if amount <= 0: + print_error(f"You entered an incorrect buyback amount: {amount}") + raise typer.Exit() + + if netuid == 0: + print_error("Cannot buyback on the root subnet.") + raise typer.Exit() + self._run_command( sudo.buyback( subtensor=self.initialize_chain(network), From 13570936ae70f04903f12a9cd23396d79a1e43e9 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:34:44 -0800 Subject: [PATCH 07/20] wip e2e test --- tests/e2e_tests/test_buyback.py | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/e2e_tests/test_buyback.py diff --git a/tests/e2e_tests/test_buyback.py b/tests/e2e_tests/test_buyback.py new file mode 100644 index 00000000..25a0339d --- /dev/null +++ b/tests/e2e_tests/test_buyback.py @@ -0,0 +1,120 @@ +import json +import time + +import pytest + +from .utils import extract_coldkey_balance + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +def test_subnet_buyback(local_chain, wallet_setup): + """ + Test subnet buyback + 1. Create a subnet + 2. Start the subnet's emission schedule + 3. Buyback the subnet + 3. Check the balance before and after the buyback upon success + 4. Try to buyback again and expect it to fail due to rate limit + """ + + _, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup("//Alice") + time.sleep(2) + netuid = 2 + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--chain", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--subnet-name", + "Test Subnet", + "--repo", + "https://github.com/username/repo", + "--contact", + "test@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "test#1234", + "--description", + "A test subnet for e2e testing", + "--logo-url", + "https://testsubnet.com/logo.png", + "--additional-info", + "Test subnet", + "--no-prompt", + "--no-mev-protection", + ], + ) + assert "✅ Registered subnetwork with netuid: 2" in result.stdout + + # Start the subnet's emission schedule + start_call_netuid_2 = exec_command_alice( + command="subnets", + sub_command="start", + extra_args=[ + "--netuid", + str(netuid), + "--wallet-name", + wallet_alice.name, + "--no-prompt", + "--chain", + "ws://127.0.0.1:9945", + "--wallet-path", + wallet_path_alice, + ], + ) + assert ( + "Successfully started subnet 2's emission schedule." + in start_call_netuid_2.stdout + ) + assert "Your extrinsic has been included" in start_call_netuid_2.stdout + time.sleep(2) + + # Balance before buyback + _balance_before = exec_command_alice( + "wallet", + "balance", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--network", + "ws://127.0.0.1:9945", + ], + ) + balance_before = extract_coldkey_balance( + _balance_before.stdout, wallet_alice.name, wallet_alice.coldkey.ss58_address + )["free_balance"] + + # First buyback + amount_tao = 5.0 + buyback_result = exec_command_alice( + "sudo", + "buyback", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--netuid", + str(netuid), + "--amount", + str(amount_tao), + "--no-prompt", + "--json-output", + ], + ) + buyback_ok_out = json.loads(buyback_result.stdout) + assert buyback_ok_out["success"] is True, buyback_result.stdout From 298f0b3642920a22a6c27e5dab4a5b09eafef495 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:40:09 -0800 Subject: [PATCH 08/20] test_subnet_buyback test --- tests/e2e_tests/test_buyback.py | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/e2e_tests/test_buyback.py b/tests/e2e_tests/test_buyback.py index 25a0339d..7112899c 100644 --- a/tests/e2e_tests/test_buyback.py +++ b/tests/e2e_tests/test_buyback.py @@ -118,3 +118,46 @@ def test_subnet_buyback(local_chain, wallet_setup): ) buyback_ok_out = json.loads(buyback_result.stdout) assert buyback_ok_out["success"] is True, buyback_result.stdout + + # Balance after buyback + _balance_after = exec_command_alice( + "wallet", + "balance", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--network", + "ws://127.0.0.1:9945", + ], + ) + balance_after = extract_coldkey_balance( + _balance_after.stdout, wallet_alice.name, wallet_alice.coldkey.ss58_address + )["free_balance"] + assert balance_after < balance_before, (balance_before, balance_after) + + # Should fail due to rate limit + buyback_ratelimited_result = exec_command_alice( + "sudo", + "buyback", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--netuid", + str(netuid), + "--amount", + str(amount_tao), + "--no-prompt", + "--json-output", + ], + ) + buyback_ratelimited = json.loads(buyback_ratelimited_result.stdout) + assert buyback_ratelimited["success"] is False, buyback_ratelimited_result.stdout + assert "SubnetBuybackRateLimitExceeded" in buyback_ratelimited["message"] From 824a43edba0242bf0ce7f6d1203f8bc44bc5aed5 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:40:51 -0800 Subject: [PATCH 09/20] rename func --- bittensor_cli/cli.py | 2 +- bittensor_cli/src/commands/sudo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index c94fec42..0df7479d 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -7467,7 +7467,7 @@ def sudo_buyback( raise typer.Exit() self._run_command( - sudo.buyback( + sudo.subnet_buyback( subtensor=self.initialize_chain(network), wallet=wallet, netuid=netuid, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 511a84f0..be58b3bc 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -100,7 +100,7 @@ def string_to_bool(val) -> Union[bool, Type[ValueError]]: return ValueError -async def buyback( +async def subnet_buyback( wallet: Wallet, subtensor: "SubtensorInterface", netuid: int, From 5ddd93b9b23e754515727a4a0ad377da05c09b46 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:41:21 -0800 Subject: [PATCH 10/20] cleanup --- bittensor_cli/cli.py | 4 ++-- bittensor_cli/src/commands/sudo.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0df7479d..592b56b2 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1165,7 +1165,7 @@ def __init__(self): self.sudo_trim ) self.sudo_app.command("buyback", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( - self.sudo_buyback + self.sudo_subnet_buyback ) # subnets commands @@ -7385,7 +7385,7 @@ def sudo_trim( ) ) - def sudo_buyback( + def sudo_subnet_buyback( self, network: Optional[list[str]] = Options.network, wallet_name: Optional[str] = Options.wallet_name, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index be58b3bc..4813150e 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -117,7 +117,8 @@ async def subnet_buyback( period: int, ) -> bool: """ - Perform a subnet buyback (owner-only). Stakes TAO into the subnet and immediately burns the acquired alpha. + Perform a subnet buyback (owner-only). + Stakes TAO into the subnet and immediately burns the acquired alpha. """ subnet_owner = await subtensor.query( module="SubtensorModule", From 82eeb3ae46c565ab09a38c5ebc502123b51ae3dd Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:58:50 -0800 Subject: [PATCH 11/20] update ck_swap test + improvs --- bittensor_cli/cli.py | 1 + bittensor_cli/src/commands/sudo.py | 4 ++-- tests/e2e_tests/test_coldkey_swap.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 592b56b2..7a83e25a 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -7397,6 +7397,7 @@ def sudo_subnet_buyback( "--amount", "-a", help="Amount of TAO to buyback", + prompt="Enter the amount of TAO to buyback", ), proxy: Optional[str] = Options.proxy, rate_tolerance: Optional[float] = Options.rate_tolerance, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 4813150e..c10327c4 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -172,9 +172,9 @@ async def subnet_buyback( destination_netuid=netuid, amount=amount_minus_fee.rao, ) + received_amount = sim_swap.alpha_amount - - current_price_float = float(subnet_info.price.tao) + current_price_float = subnet_info.price.tao rate = 1.0 / current_price_float table = _define_buyback_table( diff --git a/tests/e2e_tests/test_coldkey_swap.py b/tests/e2e_tests/test_coldkey_swap.py index dfa42378..a528bf86 100644 --- a/tests/e2e_tests/test_coldkey_swap.py +++ b/tests/e2e_tests/test_coldkey_swap.py @@ -1,6 +1,6 @@ import asyncio import json - +import time from .utils import ( find_stake_entries, ) @@ -35,7 +35,7 @@ def test_coldkey_swap_with_stake(local_chain, wallet_setup): _, wallet_bob, path_bob, exec_command_bob = wallet_setup(wallet_path_bob) _, wallet_new, path_new, _ = wallet_setup(wallet_path_new) netuid = 2 - + time.sleep(5) # Create a new subnet by Bob create_sn = exec_command_bob( command="subnets", From 7f677b8034d7c107427dbc180dca6c24116b3cbd Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 16:59:14 -0800 Subject: [PATCH 12/20] ruff --- bittensor_cli/src/commands/sudo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index c10327c4..e2f3c028 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -117,7 +117,7 @@ async def subnet_buyback( period: int, ) -> bool: """ - Perform a subnet buyback (owner-only). + Perform a subnet buyback (owner-only). Stakes TAO into the subnet and immediately burns the acquired alpha. """ subnet_owner = await subtensor.query( @@ -172,7 +172,7 @@ async def subnet_buyback( destination_netuid=netuid, amount=amount_minus_fee.rao, ) - + received_amount = sim_swap.alpha_amount current_price_float = subnet_info.price.tao rate = 1.0 / current_price_float From 13de2e85ed0917b9e4c6981b7dee0ced0d7adcb2 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 17:11:20 -0800 Subject: [PATCH 13/20] fix test_coldkey_swap_dispute --- tests/e2e_tests/test_coldkey_swap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_tests/test_coldkey_swap.py b/tests/e2e_tests/test_coldkey_swap.py index a528bf86..7d56d304 100644 --- a/tests/e2e_tests/test_coldkey_swap.py +++ b/tests/e2e_tests/test_coldkey_swap.py @@ -240,6 +240,7 @@ def test_coldkey_swap_dispute(local_chain, wallet_setup): _, wallet_bob, path_bob, exec_command_bob = wallet_setup(wallet_path_bob) _, wallet_new, _, _ = wallet_setup(wallet_path_new) + time.sleep(5) # Create subnet, start, and stake on it create_sn = exec_command_bob( command="subnets", From 7a41a098461d903383d2186cfe73d2296454d25a Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 3 Feb 2026 17:28:08 -0800 Subject: [PATCH 14/20] add waits --- tests/e2e_tests/test_coldkey_swap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_coldkey_swap.py b/tests/e2e_tests/test_coldkey_swap.py index 7d56d304..0f7f977c 100644 --- a/tests/e2e_tests/test_coldkey_swap.py +++ b/tests/e2e_tests/test_coldkey_swap.py @@ -35,7 +35,7 @@ def test_coldkey_swap_with_stake(local_chain, wallet_setup): _, wallet_bob, path_bob, exec_command_bob = wallet_setup(wallet_path_bob) _, wallet_new, path_new, _ = wallet_setup(wallet_path_new) netuid = 2 - time.sleep(5) + time.sleep(12) # Create a new subnet by Bob create_sn = exec_command_bob( command="subnets", @@ -240,7 +240,7 @@ def test_coldkey_swap_dispute(local_chain, wallet_setup): _, wallet_bob, path_bob, exec_command_bob = wallet_setup(wallet_path_bob) _, wallet_new, _, _ = wallet_setup(wallet_path_new) - time.sleep(5) + time.sleep(12) # Create subnet, start, and stake on it create_sn = exec_command_bob( command="subnets", From b7f512fb55888973a867371f82fea51d0761d58a Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Mon, 9 Feb 2026 14:02:41 -0800 Subject: [PATCH 15/20] update cmd -> stake-burn --- bittensor_cli/cli.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 7a83e25a..26b9fc87 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1164,8 +1164,8 @@ def __init__(self): self.sudo_app.command("trim", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( self.sudo_trim ) - self.sudo_app.command("buyback", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( - self.sudo_subnet_buyback + self.sudo_app.command("stake-burn", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( + self.sudo_stake_burn ) # subnets commands @@ -1301,6 +1301,7 @@ def __init__(self): self.sudo_app.command("senate_vote", hidden=True)(self.sudo_senate_vote) self.sudo_app.command("get_take", hidden=True)(self.sudo_get_take) self.sudo_app.command("set_take", hidden=True)(self.sudo_set_take) + self.sudo_app.command("buyback", hidden=True)(self.sudo_stake_burn) # Stake self.stake_app.command( @@ -7385,7 +7386,7 @@ def sudo_trim( ) ) - def sudo_subnet_buyback( + def sudo_stake_burn( self, network: Optional[list[str]] = Options.network, wallet_name: Optional[str] = Options.wallet_name, @@ -7468,7 +7469,7 @@ def sudo_subnet_buyback( raise typer.Exit() self._run_command( - sudo.subnet_buyback( + sudo.stake_burn( subtensor=self.initialize_chain(network), wallet=wallet, netuid=netuid, From f77b0d9a1eec1c0953cb0e039c30224bd93bb68f Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Mon, 9 Feb 2026 14:03:22 -0800 Subject: [PATCH 16/20] rename sudo cmd --- bittensor_cli/src/commands/sudo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index e2f3c028..3f1c04e5 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -100,7 +100,7 @@ def string_to_bool(val) -> Union[bool, Type[ValueError]]: return ValueError -async def subnet_buyback( +async def stake_burn( wallet: Wallet, subtensor: "SubtensorInterface", netuid: int, @@ -117,7 +117,7 @@ async def subnet_buyback( period: int, ) -> bool: """ - Perform a subnet buyback (owner-only). + Perform a stake burn (owner-only). Stakes TAO into the subnet and immediately burns the acquired alpha. """ subnet_owner = await subtensor.query( @@ -158,7 +158,7 @@ async def subnet_buyback( call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="subnet_buyback", + call_function="add_stake_burn", call_params=call_params, ) From d52d5ee6db01d9b98e0ea6eed2b7050050f09db8 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Mon, 9 Feb 2026 14:03:45 -0800 Subject: [PATCH 17/20] update e2e --- .../{test_buyback.py => test_stake_burn.py} | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) rename tests/e2e_tests/{test_buyback.py => test_stake_burn.py} (85%) diff --git a/tests/e2e_tests/test_buyback.py b/tests/e2e_tests/test_stake_burn.py similarity index 85% rename from tests/e2e_tests/test_buyback.py rename to tests/e2e_tests/test_stake_burn.py index 7112899c..2e2858e1 100644 --- a/tests/e2e_tests/test_buyback.py +++ b/tests/e2e_tests/test_stake_burn.py @@ -7,18 +7,18 @@ @pytest.mark.parametrize("local_chain", [False], indirect=True) -def test_subnet_buyback(local_chain, wallet_setup): +def test_stake_burn(local_chain, wallet_setup): """ - Test subnet buyback + Test stake burn 1. Create a subnet 2. Start the subnet's emission schedule - 3. Buyback the subnet + 3. Buyback the subnet (stake burn) 3. Check the balance before and after the buyback upon success 4. Try to buyback again and expect it to fail due to rate limit """ _, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup("//Alice") - time.sleep(2) + time.sleep(12) netuid = 2 result = exec_command_alice( command="subnets", @@ -94,11 +94,11 @@ def test_subnet_buyback(local_chain, wallet_setup): _balance_before.stdout, wallet_alice.name, wallet_alice.coldkey.ss58_address )["free_balance"] - # First buyback + # First stake burn amount_tao = 5.0 - buyback_result = exec_command_alice( + stake_burn_result = exec_command_alice( "sudo", - "buyback", + "stake-burn", extra_args=[ "--wallet-path", wallet_path_alice, @@ -116,10 +116,10 @@ def test_subnet_buyback(local_chain, wallet_setup): "--json-output", ], ) - buyback_ok_out = json.loads(buyback_result.stdout) - assert buyback_ok_out["success"] is True, buyback_result.stdout + stale_burn_ok_out = json.loads(stake_burn_result.stdout) + assert stale_burn_ok_out["success"] is True, stake_burn_result.stdout - # Balance after buyback + # Balance after stake burn _balance_after = exec_command_alice( "wallet", "balance", @@ -138,7 +138,7 @@ def test_subnet_buyback(local_chain, wallet_setup): assert balance_after < balance_before, (balance_before, balance_after) # Should fail due to rate limit - buyback_ratelimited_result = exec_command_alice( + stake_burn_ratelimited_result = exec_command_alice( "sudo", "buyback", extra_args=[ @@ -158,6 +158,6 @@ def test_subnet_buyback(local_chain, wallet_setup): "--json-output", ], ) - buyback_ratelimited = json.loads(buyback_ratelimited_result.stdout) - assert buyback_ratelimited["success"] is False, buyback_ratelimited_result.stdout - assert "SubnetBuybackRateLimitExceeded" in buyback_ratelimited["message"] + stake_burn_ratelimited = json.loads(stake_burn_ratelimited_result.stdout) + assert stake_burn_ratelimited["success"] is False, stake_burn_ratelimited_result.stdout + assert "AddStakeBurnRateLimitExceeded" in stake_burn_ratelimited["message"] From be7e9032471d775e7c6ae6d581af121643c07baf Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Mon, 9 Feb 2026 14:10:28 -0800 Subject: [PATCH 18/20] update help text --- bittensor_cli/cli.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 26b9fc87..39f5e312 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1164,9 +1164,9 @@ def __init__(self): self.sudo_app.command("trim", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( self.sudo_trim ) - self.sudo_app.command("stake-burn", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( - self.sudo_stake_burn - ) + self.sudo_app.command( + "stake-burn", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"] + )(self.sudo_stake_burn) # subnets commands self.subnets_app.command( @@ -7397,8 +7397,8 @@ def sudo_stake_burn( None, "--amount", "-a", - help="Amount of TAO to buyback", - prompt="Enter the amount of TAO to buyback", + help="Amount of TAO to stake and burn", + prompt="Enter the amount of TAO to stake and burn", ), proxy: Optional[str] = Options.proxy, rate_tolerance: Optional[float] = Options.rate_tolerance, @@ -7415,12 +7415,12 @@ def sudo_stake_burn( Allows subnet owners to buy back alpha on their subnet by staking TAO and immediately burning the acquired alpha. [bold]Examples:[/bold] - 1. Buyback 10 TAO on subnet 14: - [green]$[/green] btcli sudo buyback --netuid 14 --amount 10 - 2. Buyback 10 TAO on subnet 14 with safe staking and 5% rate tolerance: - [green]$[/green] btcli sudo buyback --netuid 14 --amount 10 --tolerance 0.05 - 3. Buyback 10 TAO on subnet 14 with a specific hotkey: - [green]$[/green] btcli sudo buyback --netuid 14 --amount 10 --wallet-hotkey + 1. Stake and burn 10 TAO on subnet 14: + [green]$[/green] btcli sudo stake-burn --netuid 14 --amount 10 + 2. Stake and burn 10 TAO on subnet 14 with safe staking and 5% rate tolerance: + [green]$[/green] btcli sudo stake-burn --netuid 14 --amount 10 --tolerance 0.05 + 3. Stake and burn 10 TAO on subnet 14 with a specific hotkey: + [green]$[/green] btcli sudo stake-burn --netuid 14 --amount 10 --wallet-hotkey """ self.verbosity_handler(quiet, verbose, json_output, prompt) proxy = self.is_valid_proxy_name_or_ss58(proxy, announce_only=False) @@ -7431,13 +7431,13 @@ def sudo_stake_burn( print_protection_warnings( mev_protection=mev_protection, safe_staking=safe_staking, - command_name="sudo buyback", + command_name="sudo stake-burn", ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( "Enter the [blue]hotkey[/blue] name or " - "[blue]hotkey ss58 address[/blue] [dim](to use for the buyback)[/dim]", + "[blue]hotkey ss58 address[/blue] [dim](to use for the stake burn)[/dim]", default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, ) @@ -7461,11 +7461,11 @@ def sudo_stake_burn( hotkey_ss58 = get_hotkey_pub_ss58(wallet) if amount <= 0: - print_error(f"You entered an incorrect buyback amount: {amount}") + print_error(f"You entered an incorrect stake and burn amount: {amount}") raise typer.Exit() if netuid == 0: - print_error("Cannot buyback on the root subnet.") + print_error("Cannot stake and burn on the root subnet.") raise typer.Exit() self._run_command( From c3dbf896381ca19103c6eb3ecd18935718bc88ae Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Mon, 9 Feb 2026 14:10:46 -0800 Subject: [PATCH 19/20] update cmd feedbacks --- bittensor_cli/src/commands/sudo.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 3f1c04e5..2ea78c13 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -142,7 +142,7 @@ async def stake_burn( return False subnet_info = await subtensor.subnet(netuid=netuid) - buyback_amount = Balance.from_tao(amount) + stake_burn_amount = Balance.from_tao(amount) rate_tolerance = rate_tolerance if rate_tolerance is not None else 0.0 price_limit: Optional[Balance] = None @@ -152,7 +152,7 @@ async def stake_burn( call_params = { "hotkey": hotkey_ss58, "netuid": netuid, - "amount": buyback_amount.rao, + "amount": stake_burn_amount.rao, "limit": price_limit.rao if price_limit else None, } @@ -166,7 +166,7 @@ async def stake_burn( extrinsic_fee = await subtensor.get_extrinsic_fee( call, wallet.coldkeypub, proxy=proxy ) - amount_minus_fee = buyback_amount - extrinsic_fee + amount_minus_fee = stake_burn_amount - extrinsic_fee sim_swap = await subtensor.sim_swap( origin_netuid=0, destination_netuid=netuid, @@ -177,7 +177,7 @@ async def stake_burn( current_price_float = subnet_info.price.tao rate = 1.0 / current_price_float - table = _define_buyback_table( + table = _define_stake_burn_table( wallet=wallet, subtensor=subtensor, safe_staking=safe_staking, @@ -186,7 +186,7 @@ async def stake_burn( row = [ str(netuid), hotkey_ss58, - str(buyback_amount), + str(stake_burn_amount), str(rate) + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", str(received_amount.set_unit(netuid)), str(sim_swap.tao_fee), @@ -214,7 +214,7 @@ async def stake_burn( return False with console.status( - f":satellite: Performing subnet buyback on [bold]{netuid}[/bold]...", + f":satellite: Performing subnet stake burn on [bold]{netuid}[/bold]...", spinner="earth", ) as status: next_nonce = await subtensor.substrate.get_account_next_index( @@ -268,7 +268,7 @@ async def stake_burn( ext_id = await ext_receipt.get_extrinsic_identifier() - msg = f"Subnet buyback succeeded on SN{netuid}." + msg = f"Subnet stake burn succeeded on SN{netuid}." if json_output: json_console.print_json( data={"success": True, "message": msg, "extrinsic_identifier": ext_id} @@ -280,7 +280,7 @@ async def stake_burn( return True -def _define_buyback_table( +def _define_stake_burn_table( wallet: Wallet, subtensor: "SubtensorInterface", safe_staking: bool, From fb837e2a0112700ef3784c4dfde2caf739023b2c Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Mon, 9 Feb 2026 14:12:17 -0800 Subject: [PATCH 20/20] ruff --- tests/e2e_tests/test_stake_burn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_stake_burn.py b/tests/e2e_tests/test_stake_burn.py index 2e2858e1..c6694fa5 100644 --- a/tests/e2e_tests/test_stake_burn.py +++ b/tests/e2e_tests/test_stake_burn.py @@ -159,5 +159,7 @@ def test_stake_burn(local_chain, wallet_setup): ], ) stake_burn_ratelimited = json.loads(stake_burn_ratelimited_result.stdout) - assert stake_burn_ratelimited["success"] is False, stake_burn_ratelimited_result.stdout + assert stake_burn_ratelimited["success"] is False, ( + stake_burn_ratelimited_result.stdout + ) assert "AddStakeBurnRateLimitExceeded" in stake_burn_ratelimited["message"]