From c394ec0757d8de16bb0336dff5fc26ba12010bd6 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 16:55:15 +0200 Subject: [PATCH 01/20] Fixed broken type annotation. --- bittensor_cli/src/commands/sudo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 8d7950392..88032df89 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -79,7 +79,7 @@ def allowed_value( return True, value -def string_to_bool(val) -> bool | type[ValueError]: +def string_to_bool(val) -> bool: try: return {"true": True, "1": True, "0": False, "false": False}[val.lower()] except KeyError: From 4344a24d0009687b3d426358c2a9ef82fe8dd208 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 8 Jul 2025 14:27:28 -0400 Subject: [PATCH 02/20] Partially fix slippage display --- .../src/bittensor/subtensor_interface.py | 18 +++++ bittensor_cli/src/commands/stake/add.py | 68 +++++++++++-------- bittensor_cli/src/commands/stake/remove.py | 60 +++++++++------- 3 files changed, 94 insertions(+), 52 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index c274246ff..e9c338ba1 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1581,3 +1581,21 @@ async def get_coldkey_swap_schedule_duration( ) return result + + async def get_subnet_price( + self, + netuid: int = None, + block_hash: Optional[str] = None, + ) -> Balance: + if not block_hash: + block_hash = await self.substrate.get_chain_head() + current_sqrt_price = await self.substrate.query( + module="Swap", + storage_function="AlphaSqrtPrice", + params=[netuid], + block_hash=block_hash, + ) + + current_sqrt_price = fixed_to_float(current_sqrt_price) + current_price = current_sqrt_price * current_sqrt_price + return Balance.from_rao(int(current_price * 1e9)) diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index a8d1ecc59..36345c2db 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -307,27 +307,36 @@ async def stake_extrinsic( return False remaining_wallet_balance -= amount_to_stake - stake_fee = await subtensor.get_stake_fee( - origin_hotkey_ss58=None, - origin_netuid=None, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=hotkey[1], - destination_netuid=netuid, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - amount=amount_to_stake.rao, + stake_fee, current_price = await asyncio.gather( + subtensor.get_stake_fee( + origin_hotkey_ss58=None, + origin_netuid=None, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=hotkey[1], + destination_netuid=netuid, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + amount=amount_to_stake.rao, + ), + subtensor.get_subnet_price(netuid=netuid), ) # Calculate slippage - try: - received_amount, slippage_pct, slippage_pct_float, rate = ( - _calculate_slippage(subnet_info, amount_to_stake, stake_fee) - ) - except ValueError: - return False - - max_slippage = max(slippage_pct_float, max_slippage) - - # Add rows for the table + # TODO: Update for V3, slippage calculation is significantly different in v3 + # try: + # received_amount, slippage_pct, slippage_pct_float, rate = ( + # _calculate_slippage(subnet_info, amount_to_stake, stake_fee) + # ) + # except ValueError: + # return False + # + # max_slippage = max(slippage_pct_float, max_slippage) + + # Temporary workaround - calculations without slippage + current_price_float = float(current_price.tao) + rate = 1. / current_price_float + received_amount = rate * amount_to_stake + + # Add rows for the table base_row = [ str(netuid), # netuid f"{hotkey[1]}", # hotkey @@ -336,16 +345,16 @@ async def stake_extrinsic( + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate str(received_amount.set_unit(netuid)), # received str(stake_fee), # fee - str(slippage_pct), # slippage + # str(slippage_pct), # slippage ] # If we are staking safe, add price tolerance if safe_staking: if subnet_info.is_dynamic: - rate = amount_to_stake.rao / received_amount.rao - _rate_with_tolerance = rate * ( + price_with_tolerance_float = current_price_float * ( 1 + rate_tolerance - ) # Rate only for display + ) + _rate_with_tolerance = 1. / price_with_tolerance_float # Rate only for display rate_with_tolerance = f"{_rate_with_tolerance:.4f}" price_with_tolerance = Balance.from_tao( _rate_with_tolerance @@ -581,9 +590,10 @@ def _define_stake_table( justify="center", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], ) - table.add_column( - "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] - ) + # TODO: Uncomment when slippage is reimplemented for v3 + # table.add_column( + # "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] + # ) if safe_staking: table.add_column( @@ -628,8 +638,8 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b - [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are staking to. - [bold white]Amount[/bold white]: The TAO you are staking into this subnet onto this hotkey. - [bold white]Rate[/bold white]: The rate of exchange between your TAO and the subnet's stake. - - [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage. - - [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root).""" + - [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage.""" + # - [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root).""" safe_staking_description = """ - [bold white]Rate Tolerance[/bold white]: Maximum acceptable alpha rate. If the rate exceeds this tolerance, the transaction will be limited or rejected. @@ -654,6 +664,9 @@ def _calculate_slippage( - slippage_str: Formatted slippage percentage string - slippage_float: Raw slippage percentage value - rate: Exchange rate string + + TODO: Update to v3. This method only works for protocol-liquidity-only + mode (user liquidity disabled) """ amount_after_fee = amount - stake_fee @@ -670,6 +683,7 @@ def _calculate_slippage( slippage_str = f"{slippage_pct_float:.4f} %" rate = f"{(1 / subnet_info.price.tao or 1):.4f}" else: + # TODO: Fix this. Slippage is always zero for static networks. slippage_pct_float = ( 100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0 ) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index b2ef8b608..f8584ef07 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -65,7 +65,13 @@ async def unstake( wallet.coldkeypub.ss58_address, block_hash=chain_head ), ) - all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} + prices = await asyncio.gather(*[ + subtensor.get_subnet_price(netuid=netuid) for _ in all_sn_dynamic_info_ + ]) + + for item, price in zip(all_sn_dynamic_info_, prices): + item.price = price + all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} if interactive: try: @@ -241,8 +247,8 @@ async def unstake( str(subnet_info.price.tao) + f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", # Rate str(stake_fee), # Fee - str(received_amount), # Received Amount - slippage_pct, # Slippage Percent + # str(received_amount), # Received Amount + # slippage_pct, # Slippage Percent ] # Additional fields for safe unstaking @@ -443,16 +449,16 @@ async def unstake_all( justify="center", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], ) - table.add_column( - f"Received ({Balance.unit})", - justify="center", - style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], - ) - table.add_column( - "Slippage", - justify="center", - style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"], - ) + # table.add_column( + # f"Received ({Balance.unit})", + # justify="center", + # style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + # ) + # table.add_column( + # "Slippage", + # justify="center", + # style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"], + # ) # Calculate slippage and total received max_slippage = 0.0 @@ -490,8 +496,8 @@ async def unstake_all( str(float(subnet_info.price)) + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})", str(stake_fee), - str(received_amount), - slippage_pct, + # str(received_amount), + # slippage_pct, ) console.print(table) if max_slippage > 5: @@ -863,6 +869,9 @@ def _calculate_slippage( - received_amount: Balance after slippage deduction - slippage_pct: Formatted string of slippage percentage - slippage_pct_float: Float value of slippage percentage + + TODO: Update to v3. This method only works for protocol-liquidity-only + mode (user liquidity disabled) """ received_amount, _, _ = subnet_info.alpha_to_tao_with_slippage(amount) received_amount -= stake_fee @@ -884,6 +893,7 @@ def _calculate_slippage( ) slippage_pct = f"{slippage_pct_float:.4f} %" else: + # TODO: Fix this. Slippage is always zero for static networks. # Root will only have fee-based slippage slippage_pct_float = ( 100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0 @@ -993,7 +1003,7 @@ async def _unstake_selection( table.add_column("Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"]) table.add_column( - f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", + f"[bold white]Rate ({Balance.get_unit(0)}/{Balance.get_unit(1)})", style=COLOR_PALETTE["POOLS"]["RATE"], justify="left", ) @@ -1255,15 +1265,15 @@ def _create_unstake_table( justify="center", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], ) - table.add_column( - f"Received ({Balance.get_unit(0)})", - justify="center", - style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], - footer=str(total_received_amount), - ) - table.add_column( - "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] - ) + # table.add_column( + # f"Received ({Balance.get_unit(0)})", + # justify="center", + # style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + # footer=str(total_received_amount), + # ) + # table.add_column( + # "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] + # ) if safe_staking: table.add_column( f"Rate with tolerance: [blue]({rate_tolerance * 100}%)[/blue]", From a3430186a6cff7b0211bc421100c80cdef007188 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 20:31:16 +0200 Subject: [PATCH 03/20] Fixes issue where netuid is 0 and treated as None --- bittensor_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 9128f83fe..bb1c5126a 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3352,7 +3352,7 @@ def stake_add( ) else: netuid_ = get_optional_netuid(None, all_netuids) - netuids = [netuid_] if netuid_ else None + netuids = [netuid_] if netuid_ is not None else None if netuids: for netuid_ in netuids: # ensure no negative netuids make it into our list From 01963a01cb74180e7a0675aaa5cf0a6d9e882d31 Mon Sep 17 00:00:00 2001 From: Ibraheem <165814940+ibraheem-abe@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:32:11 -0700 Subject: [PATCH 04/20] Update bittensor_cli/src/bittensor/subtensor_interface.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor_cli/src/bittensor/subtensor_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index e9c338ba1..d5ed32759 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1589,7 +1589,7 @@ async def get_subnet_price( ) -> Balance: if not block_hash: block_hash = await self.substrate.get_chain_head() - current_sqrt_price = await self.substrate.query( + current_sqrt_price = await self.query( module="Swap", storage_function="AlphaSqrtPrice", params=[netuid], From 73498c445acb5b812962746167b61d3402f98d18 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 22:05:17 +0200 Subject: [PATCH 05/20] Updated to use query map --- .../src/bittensor/subtensor_interface.py | 20 +++++++++- bittensor_cli/src/commands/stake/add.py | 40 ++++++++++--------- bittensor_cli/src/commands/stake/remove.py | 14 +++---- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index d5ed32759..f28b7b50c 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1587,8 +1587,6 @@ async def get_subnet_price( netuid: int = None, block_hash: Optional[str] = None, ) -> Balance: - if not block_hash: - block_hash = await self.substrate.get_chain_head() current_sqrt_price = await self.query( module="Swap", storage_function="AlphaSqrtPrice", @@ -1599,3 +1597,21 @@ async def get_subnet_price( current_sqrt_price = fixed_to_float(current_sqrt_price) current_price = current_sqrt_price * current_sqrt_price return Balance.from_rao(int(current_price * 1e9)) + + async def get_subnet_prices( + self, block_hash: Optional[str] = None, page_size: int = 100 + ) -> dict[int, Balance]: + query = await self.substrate.query_map( + module="Swap", + storage_function="AlphaSqrtPrice", + page_size=page_size, + block_hash=block_hash, + ) + + map_ = {} + async for netuid_, current_sqrt_price in query: + current_sqrt_price_ = fixed_to_float(current_sqrt_price.value) + current_price = current_sqrt_price_ ^ 2 + map_[netuid_] = Balance.from_rao(int(current_price * 1e9)) + + return map_ diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 36345c2db..9edc3d465 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -245,12 +245,12 @@ async def stake_extrinsic( # Get subnet data and stake information for coldkey chain_head = await subtensor.substrate.get_chain_head() _all_subnets, _stake_info, current_wallet_balance = await asyncio.gather( - subtensor.all_subnets(), + subtensor.all_subnets(block_hash=chain_head), subtensor.get_stake_for_coldkey( coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=chain_head, ), - subtensor.get_balance(wallet.coldkeypub.ss58_address), + subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=chain_head), ) all_subnets = {di.netuid: di for di in _all_subnets} @@ -275,6 +275,10 @@ async def stake_extrinsic( remaining_wallet_balance = current_wallet_balance max_slippage = 0.0 + current_prices = await subtensor.get_subnet_prices( + block_hash=chain_head, page_size=len(all_subnets) + ) + for hotkey in hotkeys_to_stake_to: for netuid in netuids: # Check that the subnet exists. @@ -307,17 +311,15 @@ async def stake_extrinsic( return False remaining_wallet_balance -= amount_to_stake - stake_fee, current_price = await asyncio.gather( - subtensor.get_stake_fee( - origin_hotkey_ss58=None, - origin_netuid=None, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=hotkey[1], - destination_netuid=netuid, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - amount=amount_to_stake.rao, - ), - subtensor.get_subnet_price(netuid=netuid), + # TODO this should be asyncio gathered before the for loop + stake_fee = await subtensor.get_stake_fee( + origin_hotkey_ss58=None, + origin_netuid=None, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_hotkey_ss58=hotkey[1], + destination_netuid=netuid, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + amount=amount_to_stake.rao, ) # Calculate slippage @@ -332,11 +334,11 @@ async def stake_extrinsic( # max_slippage = max(slippage_pct_float, max_slippage) # Temporary workaround - calculations without slippage - current_price_float = float(current_price.tao) - rate = 1. / current_price_float + current_price_float = float(current_prices[netuid].tao) + rate = 1.0 / current_price_float received_amount = rate * amount_to_stake - # Add rows for the table + # Add rows for the table base_row = [ str(netuid), # netuid f"{hotkey[1]}", # hotkey @@ -354,7 +356,9 @@ async def stake_extrinsic( price_with_tolerance_float = current_price_float * ( 1 + rate_tolerance ) - _rate_with_tolerance = 1. / price_with_tolerance_float # Rate only for display + _rate_with_tolerance = ( + 1.0 / price_with_tolerance_float + ) # Rate only for display rate_with_tolerance = f"{_rate_with_tolerance:.4f}" price_with_tolerance = Balance.from_tao( _rate_with_tolerance @@ -665,7 +669,7 @@ def _calculate_slippage( - slippage_float: Raw slippage percentage value - rate: Exchange rate string - TODO: Update to v3. This method only works for protocol-liquidity-only + TODO: Update to v3. This method only works for protocol-liquidity-only mode (user liquidity disabled) """ amount_after_fee = amount - stake_fee diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index f8584ef07..80dfafb92 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -65,13 +65,11 @@ async def unstake( wallet.coldkeypub.ss58_address, block_hash=chain_head ), ) - prices = await asyncio.gather(*[ - subtensor.get_subnet_price(netuid=netuid) for _ in all_sn_dynamic_info_ - ]) - - for item, price in zip(all_sn_dynamic_info_, prices): - item.price = price - all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} + prices = await subtensor.get_subnet_prices(page_size=len(all_sn_dynamic_info_)) + all_sn_dynamic_info = {} + for subnet in all_sn_dynamic_info_: + subnet.price = prices[subnet.netuid] + all_sn_dynamic_info[subnet.netuid] = subnet if interactive: try: @@ -870,7 +868,7 @@ def _calculate_slippage( - slippage_pct: Formatted string of slippage percentage - slippage_pct_float: Float value of slippage percentage - TODO: Update to v3. This method only works for protocol-liquidity-only + TODO: Update to v3. This method only works for protocol-liquidity-only mode (user liquidity disabled) """ received_amount, _, _ = subnet_info.alpha_to_tao_with_slippage(amount) From da67a42397342e9eb4bf04fcb508ae73caa3ffdb Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 22:08:40 +0200 Subject: [PATCH 06/20] typo --- bittensor_cli/src/bittensor/subtensor_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index f28b7b50c..6ab0eaa41 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1611,7 +1611,7 @@ async def get_subnet_prices( map_ = {} async for netuid_, current_sqrt_price in query: current_sqrt_price_ = fixed_to_float(current_sqrt_price.value) - current_price = current_sqrt_price_ ^ 2 + current_price = current_sqrt_price_**2 map_[netuid_] = Balance.from_rao(int(current_price * 1e9)) return map_ From 8ce752b66ea6fe80331dd121896724d16066e457 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 22:28:55 +0200 Subject: [PATCH 07/20] Better calculation --- bittensor_cli/src/commands/stake/add.py | 9 ++++++++- bittensor_cli/src/commands/stake/remove.py | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 9edc3d465..4770c1ed1 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -334,7 +334,14 @@ async def stake_extrinsic( # max_slippage = max(slippage_pct_float, max_slippage) # Temporary workaround - calculations without slippage - current_price_float = float(current_prices[netuid].tao) + if netuid == 0: + current_price = Balance.from_tao(1.0) + else: + try: + current_price = current_prices[netuid] + except KeyError: # indicates no price + current_price = subnet_info.tao_in / subnet_info.alpha_in + current_price_float = float(current_price.tao) rate = 1.0 / current_price_float received_amount = rate * amount_to_stake diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 80dfafb92..ed09195a8 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -68,7 +68,13 @@ async def unstake( prices = await subtensor.get_subnet_prices(page_size=len(all_sn_dynamic_info_)) all_sn_dynamic_info = {} for subnet in all_sn_dynamic_info_: - subnet.price = prices[subnet.netuid] + if subnet.netuid == 0: + subnet.price = Balance.from_tao(1.0) + else: + try: + subnet.price = prices[subnet.netuid] + except KeyError: + subnet.price = subnet.tao_in / subnet.alpha_in all_sn_dynamic_info[subnet.netuid] = subnet if interactive: From 1c449fa08c875513c6fa7105cb3da3bd64300ab5 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 22:39:20 +0200 Subject: [PATCH 08/20] Refactored to included the price in the initial all_subnets call --- .../src/bittensor/subtensor_interface.py | 22 ++++++++++++++----- bittensor_cli/src/commands/stake/add.py | 13 +---------- bittensor_cli/src/commands/stake/remove.py | 12 +--------- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 6ab0eaa41..0873a9539 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1423,12 +1423,24 @@ async def get_stake_for_coldkeys( return stake_info_map if stake_info_map else None async def all_subnets(self, block_hash: Optional[str] = None) -> list[DynamicInfo]: - result = await self.query_runtime_api( - "SubnetInfoRuntimeApi", - "get_all_dynamic_info", - block_hash=block_hash, + result, prices = await asyncio.gather( + self.query_runtime_api( + "SubnetInfoRuntimeApi", + "get_all_dynamic_info", + block_hash=block_hash, + ), + self.get_subnet_prices(block_hash=block_hash, page_size=129), ) - return DynamicInfo.list_from_any(result) + sns: list[DynamicInfo] = DynamicInfo.list_from_any(result) + for sn in sns: + if sn.netuid == 0: + sn.price = Balance.from_tao(1.0) + else: + try: + sn.price = prices[sn.netuid] + except KeyError: + sn.price = sn.tao_in / sn.alpha_in + return sns async def subnet( self, netuid: int, block_hash: Optional[str] = None diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 4770c1ed1..cf909fdd2 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -275,10 +275,6 @@ async def stake_extrinsic( remaining_wallet_balance = current_wallet_balance max_slippage = 0.0 - current_prices = await subtensor.get_subnet_prices( - block_hash=chain_head, page_size=len(all_subnets) - ) - for hotkey in hotkeys_to_stake_to: for netuid in netuids: # Check that the subnet exists. @@ -334,14 +330,7 @@ async def stake_extrinsic( # max_slippage = max(slippage_pct_float, max_slippage) # Temporary workaround - calculations without slippage - if netuid == 0: - current_price = Balance.from_tao(1.0) - else: - try: - current_price = current_prices[netuid] - except KeyError: # indicates no price - current_price = subnet_info.tao_in / subnet_info.alpha_in - current_price_float = float(current_price.tao) + current_price_float = float(subnet_info.price.tao) rate = 1.0 / current_price_float received_amount = rate * amount_to_stake diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index ed09195a8..73e8aa716 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -65,17 +65,7 @@ async def unstake( wallet.coldkeypub.ss58_address, block_hash=chain_head ), ) - prices = await subtensor.get_subnet_prices(page_size=len(all_sn_dynamic_info_)) - all_sn_dynamic_info = {} - for subnet in all_sn_dynamic_info_: - if subnet.netuid == 0: - subnet.price = Balance.from_tao(1.0) - else: - try: - subnet.price = prices[subnet.netuid] - except KeyError: - subnet.price = subnet.tao_in / subnet.alpha_in - all_sn_dynamic_info[subnet.netuid] = subnet + all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} if interactive: try: From 182ea5e37e20af40ac0601fecfaae99e2f08a4b3 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 22:41:21 +0200 Subject: [PATCH 09/20] Added the same logic to SubtensorInterface.subnet --- .../src/bittensor/subtensor_interface.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 0873a9539..2cc26bc2f 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1445,13 +1445,18 @@ async def all_subnets(self, block_hash: Optional[str] = None) -> list[DynamicInf async def subnet( self, netuid: int, block_hash: Optional[str] = None ) -> "DynamicInfo": - result = await self.query_runtime_api( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[netuid], - block_hash=block_hash, + result, price = await asyncio.gather( + self.query_runtime_api( + "SubnetInfoRuntimeApi", + "get_dynamic_info", + params=[netuid], + block_hash=block_hash, + ), + self.get_subnet_price(netuid=netuid, block_hash=block_hash), ) - return DynamicInfo.from_any(result) + subnet_ = DynamicInfo.from_any(result) + subnet_.price = price + return subnet_ async def get_owned_hotkeys( self, From 617889b283637c0c87113ef5a302c5ff90bb28d0 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 8 Jul 2025 22:46:01 +0200 Subject: [PATCH 10/20] Adds a unit test lol --- tests/unit_tests/test_cli.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index 34c9b2d3c..b7933e226 100644 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -2,6 +2,7 @@ import typer from bittensor_cli.cli import parse_mnemonic +from unittest.mock import AsyncMock, patch, MagicMock def test_parse_mnemonic(): @@ -16,3 +17,37 @@ def test_parse_mnemonic(): parse_mnemonic("1-hello 1-how 2-are 3-you") # missing numbers parse_mnemonic("1-hello 3-are 4-you") + + +@pytest.mark.asyncio +async def test_subnet_sets_price_correctly(): + from bittensor_cli.src.bittensor.subtensor_interface import ( + SubtensorInterface, + DynamicInfo, + ) + + mock_result = {"some": "data"} + mock_price = 42.0 + mock_dynamic_info = MagicMock() + mock_dynamic_info.price = None + + with ( + patch.object( + SubtensorInterface, "query_runtime_api", new_callable=AsyncMock + ) as mock_query, + patch.object( + SubtensorInterface, "get_subnet_price", new_callable=AsyncMock + ) as mock_price_method, + patch.object(DynamicInfo, "from_any", return_value=mock_dynamic_info), + ): + mock_query.return_value = mock_result + mock_price_method.return_value = mock_price + + subtensor = SubtensorInterface("finney") + subnet_info = await subtensor.subnet(netuid=1) + + mock_query.assert_awaited_once_with( + "SubnetInfoRuntimeApi", "get_dynamic_info", params=[1], block_hash=None + ) + mock_price_method.assert_awaited_once_with(netuid=1, block_hash=None) + assert subnet_info.price == mock_price From 158feb2b67eb787d7bdffa0dd84eac546bcc4c18 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 14:19:20 -0700 Subject: [PATCH 11/20] updates price sent to extrinsic --- bittensor_cli/src/commands/stake/add.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index cf909fdd2..86c270abc 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -349,15 +349,15 @@ async def stake_extrinsic( # If we are staking safe, add price tolerance if safe_staking: if subnet_info.is_dynamic: - price_with_tolerance_float = current_price_float * ( + price_with_tolerance = current_price_float * ( 1 + rate_tolerance ) _rate_with_tolerance = ( - 1.0 / price_with_tolerance_float + 1.0 / price_with_tolerance ) # Rate only for display rate_with_tolerance = f"{_rate_with_tolerance:.4f}" price_with_tolerance = Balance.from_tao( - _rate_with_tolerance + price_with_tolerance ).rao # Actual price to pass to extrinsic else: rate_with_tolerance = "1" From 7ad84c21b8297fdd7da46a7e0b517e7e657175f3 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 14:20:26 -0700 Subject: [PATCH 12/20] ruff --- bittensor_cli/src/commands/stake/add.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 86c270abc..45b17dfbf 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -349,9 +349,7 @@ async def stake_extrinsic( # If we are staking safe, add price tolerance if safe_staking: if subnet_info.is_dynamic: - price_with_tolerance = current_price_float * ( - 1 + rate_tolerance - ) + price_with_tolerance = current_price_float * (1 + rate_tolerance) _rate_with_tolerance = ( 1.0 / price_with_tolerance ) # Rate only for display From 9ba1ff88dd9f5c92558b050ef6a73f3959771a59 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 14:57:36 -0700 Subject: [PATCH 13/20] updates stake remove --- bittensor_cli/src/commands/stake/remove.py | 42 +++++++++------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 73e8aa716..7a526c7aa 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -209,16 +209,12 @@ async def unstake( ) try: - received_amount, slippage_pct, slippage_pct_float = _calculate_slippage( - subnet_info=subnet_info, - amount=amount_to_unstake_as_balance, - stake_fee=stake_fee, - ) + current_price = subnet_info.price.tao + rate = current_price + received_amount = amount_to_unstake_as_balance * rate except ValueError: continue - total_received_amount += received_amount - max_float_slippage = max(max_float_slippage, slippage_pct_float) base_unstake_op = { "netuid": netuid, @@ -229,8 +225,6 @@ async def unstake( "amount_to_unstake": amount_to_unstake_as_balance, "current_stake_balance": current_stake_balance, "received_amount": received_amount, - "slippage_pct": slippage_pct, - "slippage_pct_float": slippage_pct_float, "dynamic_info": subnet_info, } @@ -241,17 +235,15 @@ async def unstake( str(subnet_info.price.tao) + f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", # Rate str(stake_fee), # Fee - # str(received_amount), # Received Amount + str(received_amount), # Received Amount # slippage_pct, # Slippage Percent ] # Additional fields for safe unstaking if safe_staking: if subnet_info.is_dynamic: - rate = received_amount.rao / amount_to_unstake_as_balance.rao - rate_with_tolerance = rate * ( - 1 - rate_tolerance - ) # Rate only for display + price_with_tolerance = current_price * (1 - rate_tolerance) + rate_with_tolerance = price_with_tolerance price_with_tolerance = Balance.from_tao( rate_with_tolerance ).rao # Actual price to pass to extrinsic @@ -443,11 +435,11 @@ async def unstake_all( justify="center", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], ) - # table.add_column( - # f"Received ({Balance.unit})", - # justify="center", - # style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], - # ) + table.add_column( + f"Received ({Balance.unit})", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + ) # table.add_column( # "Slippage", # justify="center", @@ -1259,12 +1251,12 @@ def _create_unstake_table( justify="center", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], ) - # table.add_column( - # f"Received ({Balance.get_unit(0)})", - # justify="center", - # style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], - # footer=str(total_received_amount), - # ) + table.add_column( + f"Received ({Balance.get_unit(0)})", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + footer=str(total_received_amount), + ) # table.add_column( # "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] # ) From 069234c407dd8d0ddec9adb021dc7cb27f9d18c6 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 15:25:45 -0700 Subject: [PATCH 14/20] updates move cmds --- bittensor_cli/src/commands/stake/move.py | 62 +++++++----------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index 42f61934b..267875969 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -33,8 +33,8 @@ async def display_stake_movement_cross_subnets( destination_hotkey: str, amount_to_move: Balance, stake_fee: Balance, -) -> tuple[Balance, float, str, str]: - """Calculate and display slippage information""" +) -> tuple[Balance, str]: + """Calculate and display stake movement information""" if origin_netuid == destination_netuid: subnet = await subtensor.subnet(origin_netuid) @@ -46,45 +46,32 @@ async def display_stake_movement_cross_subnets( raise ValueError received_amount = subnet.tao_to_alpha(received_amount_tao) - slippage_pct_float = ( - 100 * float(stake_fee) / float(stake_fee + received_amount_tao) - if received_amount_tao != 0 - else 0 - ) - slippage_pct = f"{slippage_pct_float:.4f}%" - price = Balance.from_tao(1).set_unit(origin_netuid) + price = subnet.price.tao price_str = ( - str(float(price.tao)) - + f"{Balance.get_unit(origin_netuid)}/{Balance.get_unit(origin_netuid)}" + str(float(price)) + + f"({Balance.get_unit(0)}/{Balance.get_unit(origin_netuid)})" ) else: dynamic_origin, dynamic_destination = await asyncio.gather( subtensor.subnet(origin_netuid), subtensor.subnet(destination_netuid), ) - price = ( - float(dynamic_origin.price) * 1 / (float(dynamic_destination.price) or 1) - ) - received_amount_tao, _, _ = dynamic_origin.alpha_to_tao_with_slippage( - amount_to_move - ) + price_origin = dynamic_origin.price.tao + price_destination = dynamic_destination.price.tao + rate = price_origin / (price_destination or 1) + + received_amount_tao = dynamic_origin.alpha_to_tao(amount_to_move) received_amount_tao -= stake_fee - received_amount, _, _ = dynamic_destination.tao_to_alpha_with_slippage( - received_amount_tao - ) + received_amount = dynamic_destination.tao_to_alpha(received_amount_tao) received_amount.set_unit(destination_netuid) if received_amount < Balance.from_tao(0): print_error("Not enough Alpha to pay the transaction fee.") raise ValueError - ideal_amount = amount_to_move * price - total_slippage = ideal_amount - received_amount - slippage_pct_float = 100 * (total_slippage.tao / ideal_amount.tao) - slippage_pct = f"{slippage_pct_float:.4f} %" price_str = ( - f"{price:.5f}" - + f"{Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)}" + f"{rate:.5f}" + + f"({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})" ) # Create and display table @@ -141,11 +128,6 @@ async def display_stake_movement_cross_subnets( justify="center", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], ) - table.add_column( - "slippage", - justify="center", - style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"], - ) table.add_row( f"{Balance.get_unit(origin_netuid)}({origin_netuid})", @@ -156,19 +138,11 @@ async def display_stake_movement_cross_subnets( price_str, str(received_amount), str(stake_fee), - str(slippage_pct), ) console.print(table) - # Display slippage warning if necessary - if slippage_pct_float > 5: - message = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n" - message += f"[bold]WARNING:\tSlippage is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_pct}[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.[/bold] \n" - message += "-------------------------------------------------------------------------------------------------------------------\n" - console.print(message) - - return received_amount, slippage_pct_float, slippage_pct, price_str + return received_amount, price_str def prompt_stake_amount( @@ -414,7 +388,7 @@ async def stake_swap_selection( origin_stake = hotkey_stakes[origin_netuid]["stake"] # Ask for amount to swap - amount, all_balance = prompt_stake_amount(origin_stake, origin_netuid, "swap") + amount, _ = prompt_stake_amount(origin_stake, origin_netuid, "swap") all_netuids = sorted(await subtensor.get_all_subnet_netuids()) destination_choices = [ @@ -530,7 +504,7 @@ async def move_stake( amount=amount_to_move_as_balance.rao, ) - # Slippage warning + # Display stake movement details if prompt: try: await display_stake_movement_cross_subnets( @@ -714,7 +688,7 @@ async def transfer_stake( amount=amount_to_transfer.rao, ) - # Slippage warning + # Display stake movement details if prompt: try: await display_stake_movement_cross_subnets( @@ -883,7 +857,7 @@ async def swap_stake( amount=amount_to_swap.rao, ) - # Slippage warning + # Display stake movement details if prompt: try: await display_stake_movement_cross_subnets( From 7b40887130000657565fbeb40447b17bbd523304 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 15:42:23 -0700 Subject: [PATCH 15/20] update remove all --- bittensor_cli/src/commands/stake/remove.py | 90 ++++------------------ 1 file changed, 16 insertions(+), 74 deletions(-) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 7a526c7aa..617b8581b 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -232,7 +232,7 @@ async def unstake( str(netuid), # Netuid staking_address_name, # Hotkey Name str(amount_to_unstake_as_balance), # Amount to Unstake - str(subnet_info.price.tao) + f"{subnet_info.price.tao:.6f}" + f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", # Rate str(stake_fee), # Fee str(received_amount), # Received Amount @@ -255,7 +255,7 @@ async def unstake( base_table_row.extend( [ # Rate with tolerance - f"{rate_with_tolerance:.4f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}", + f"{rate_with_tolerance:.6f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}", # Partial unstake f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]" f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]", @@ -446,8 +446,7 @@ async def unstake_all( # style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"], # ) - # Calculate slippage and total received - max_slippage = 0.0 + # Calculate total received total_received_value = Balance(0) for stake in stake_info: if stake.stake.rao == 0: @@ -465,41 +464,33 @@ async def unstake_all( destination_coldkey_ss58=wallet.coldkeypub.ss58_address, amount=stake_amount.rao, ) + try: - received_amount, slippage_pct, slippage_pct_float = _calculate_slippage( - subnet_info=subnet_info, amount=stake_amount, stake_fee=stake_fee - ) - except ValueError: + current_price = subnet_info.price.tao + rate = current_price + received_amount = stake_amount * rate - stake_fee + + if received_amount < Balance.from_tao(0): + print_error("Not enough Alpha to pay the transaction fee.") + continue + except (AttributeError, ValueError): continue - max_slippage = max(max_slippage, slippage_pct_float) total_received_value += received_amount table.add_row( str(stake.netuid), hotkey_display, str(stake_amount), - str(float(subnet_info.price)) + f"{float(subnet_info.price):.6f}" + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})", str(stake_fee), - # str(received_amount), - # slippage_pct, + str(received_amount), ) console.print(table) - if max_slippage > 5: - message = ( - f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]--------------------------------------------------------------" - f"-----------------------------------------------------\n" - f"[bold]WARNING:[/bold] The slippage on one of your operations is high: " - f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage:.4f}%" - f"[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n" - "----------------------------------------------------------------------------------------------------------" - "---------\n" - ) - console.print(message) console.print( - f"Expected return after slippage: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{total_received_value}" + f"Total expected return: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{total_received_value}" ) if prompt and not Confirm.ask( @@ -840,55 +831,6 @@ async def _unstake_all_extrinsic( # Helpers -def _calculate_slippage( - subnet_info, amount: Balance, stake_fee: Balance -) -> tuple[Balance, str, float]: - """Calculate slippage and received amount for unstaking operation. - - Args: - subnet_info: Subnet information containing price data - amount: Amount being unstaked - stake_fee: Stake fee to include in slippage calculation - - Returns: - tuple containing: - - received_amount: Balance after slippage deduction - - slippage_pct: Formatted string of slippage percentage - - slippage_pct_float: Float value of slippage percentage - - TODO: Update to v3. This method only works for protocol-liquidity-only - mode (user liquidity disabled) - """ - received_amount, _, _ = subnet_info.alpha_to_tao_with_slippage(amount) - received_amount -= stake_fee - - if received_amount < Balance.from_tao(0): - print_error("Not enough Alpha to pay the transaction fee.") - raise ValueError - - if subnet_info.is_dynamic: - # Ideal amount w/o slippage - ideal_amount = subnet_info.alpha_to_tao(amount) - - # Total slippage including fees - total_slippage = ideal_amount - received_amount - slippage_pct_float = ( - 100 * (float(total_slippage.tao) / float(ideal_amount.tao)) - if ideal_amount.tao != 0 - else 0 - ) - slippage_pct = f"{slippage_pct_float:.4f} %" - else: - # TODO: Fix this. Slippage is always zero for static networks. - # Root will only have fee-based slippage - slippage_pct_float = ( - 100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0 - ) - slippage_pct = f"{slippage_pct_float:.4f} %" - - return received_amount, slippage_pct, slippage_pct_float - - async def _unstake_selection( dynamic_info, identities, @@ -996,7 +938,7 @@ async def _unstake_selection( for netuid_, stake_amount in netuid_stakes.items(): symbol = dynamic_info[netuid_].symbol - rate = f"{dynamic_info[netuid_].price.tao:.4f} τ/{symbol}" + rate = f"{dynamic_info[netuid_].price.tao:.6f} τ/{symbol}" table.add_row(str(netuid_), symbol, str(stake_amount), rate) console.print("\n", table, "\n") From ee15a4c0c76326db96698c8b14f9c891a0234426 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 15:49:24 -0700 Subject: [PATCH 16/20] updates wallet balance --- bittensor_cli/src/commands/wallets.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 1c2de6a3e..99842ef98 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -542,14 +542,12 @@ async def wallet_balance( total_free_balance = sum(free_balances.values()) total_staked_balance = sum(stake[0] for stake in staked_balances.values()) - total_staked_with_slippage = sum(stake[1] for stake in staked_balances.values()) balances = { name: ( coldkey, free_balances[coldkey], staked_balances[coldkey][0], - staked_balances[coldkey][1], ) for (name, coldkey) in zip(wallet_names, coldkeys) } @@ -577,24 +575,12 @@ async def wallet_balance( style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], no_wrap=True, ), - Column( - "[white]Staked (w/slippage)", - justify="right", - style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], - no_wrap=True, - ), Column( "[white]Total Balance", justify="right", style=COLOR_PALETTE["GENERAL"]["BALANCE"], no_wrap=True, ), - Column( - "[white]Total (w/slippage)", - justify="right", - style=COLOR_PALETTE["GENERAL"]["BALANCE"], - no_wrap=True, - ), title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Wallet Coldkey Balance[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Network: {subtensor.network}\n", show_footer=True, show_edge=False, @@ -605,15 +591,13 @@ async def wallet_balance( leading=True, ) - for name, (coldkey, free, staked, staked_slippage) in balances.items(): + for name, (coldkey, free, staked) in balances.items(): table.add_row( name, coldkey, str(free), str(staked), - str(staked_slippage), str(free + staked), - str(free + staked_slippage), ) table.add_row() table.add_row( @@ -621,9 +605,7 @@ async def wallet_balance( "", str(total_free_balance), str(total_staked_balance), - str(total_staked_with_slippage), str(total_free_balance + total_staked_balance), - str(total_free_balance + total_staked_with_slippage), ) console.print(Padding(table, (0, 0, 0, 4))) await subtensor.substrate.close() @@ -633,9 +615,7 @@ async def wallet_balance( "coldkey": value[0], "free": value[1].tao, "staked": value[2].tao, - "staked_with_slippage": value[3].tao, "total": (value[1] + value[2]).tao, - "total_with_slippage": (value[1] + value[3]).tao, } for (key, value) in balances.items() } @@ -644,11 +624,7 @@ async def wallet_balance( "totals": { "free": total_free_balance.tao, "staked": total_staked_balance.tao, - "staked_with_slippage": total_staked_with_slippage.tao, "total": (total_free_balance + total_staked_balance).tao, - "total_with_slippage": ( - total_free_balance + total_staked_with_slippage - ).tao, }, } json_console.print(json.dumps(output_dict)) From 4be75cb856b13ecb1712d9233e5d5ff79998757f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 16:02:19 -0700 Subject: [PATCH 17/20] updates stake list --- bittensor_cli/src/commands/stake/list.py | 83 ++++++++++-------------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/bittensor_cli/src/commands/stake/list.py b/bittensor_cli/src/commands/stake/list.py index 2e6d39d76..4a8e17145 100644 --- a/bittensor_cli/src/commands/stake/list.py +++ b/bittensor_cli/src/commands/stake/list.py @@ -111,15 +111,15 @@ def define_table( style=COLOR_PALETTE["POOLS"]["RATE"], justify="center", ) - defined_table.add_column( - f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", - footer_style="overline white", - style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], - justify="right", - footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}" - if not verbose - else f"{total_swapped_tao_value_}", - ) + # defined_table.add_column( + # f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", + # footer_style="overline white", + # style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], + # justify="right", + # footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}" + # if not verbose + # else f"{total_swapped_tao_value_}", + # ) defined_table.add_column( "[white]Registered", style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], @@ -168,25 +168,17 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): tao_value_ = pool.alpha_to_tao(alpha_value) total_tao_value_ += tao_value_ - # Swapped TAO value and slippage cell - swapped_tao_value_, _, slippage_percentage_ = ( - pool.alpha_to_tao_with_slippage(substake_.stake) - ) - total_swapped_tao_value_ += swapped_tao_value_ - - # Slippage percentage cell - if pool.is_dynamic: - slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]" - else: - slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]" + # TAO value cell + tao_value_ = pool.alpha_to_tao(substake_.stake) + total_swapped_tao_value_ += tao_value_ if netuid == 0: - swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_percentage})" + swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]" else: swap_value = ( - f"τ {millify_tao(swapped_tao_value_.tao)} ({slippage_percentage})" + f"τ {millify_tao(tao_value_.tao)}" if not verbose - else f"{swapped_tao_value_} ({slippage_percentage})" + else f"{tao_value_}" ) # Per block emission cell @@ -214,7 +206,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): else f"{symbol} {stake_value}", # Stake (a) f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) # f"τ {millify_tao(tao_ownership.tao)}" if not verbose else f"{tao_ownership}", # TAO equiv - swap_value, # Swap(α) -> τ + # swap_value, # Swap(α) -> τ "YES" if substake_.is_registered else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered @@ -232,7 +224,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): "value": tao_value_.tao, "stake_value": substake_.stake.tao, "rate": pool.price.tao, - "swap_value": swap_value, + # "swap_value": swap_value, "registered": True if substake_.is_registered else False, "emission": { "alpha": per_block_emission, @@ -317,9 +309,7 @@ def format_cell( alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid) tao_value_ = pool.alpha_to_tao(alpha_value) total_tao_value_ += tao_value_ - swapped_tao_value_, slippage, slippage_pct = ( - pool.alpha_to_tao_with_slippage(substake_.stake) - ) + swapped_tao_value_ = pool.alpha_to_tao(substake_.stake) total_swapped_tao_value_ += swapped_tao_value_ # Store current values for future delta tracking @@ -364,19 +354,16 @@ def format_cell( ) if netuid != 0: - swap_cell = ( - format_cell( - swapped_tao_value_.tao, - prev.get("swapped_value"), - unit="τ", - unit_first_=True, - precision=4, - millify=True if not verbose else False, - ) - + f" ({slippage_pct:.2f}%)" + swap_cell = format_cell( + swapped_tao_value_.tao, + prev.get("swapped_value"), + unit="τ", + unit_first_=True, + precision=4, + millify=True if not verbose else False, ) else: - swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_pct}%)" + swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]" emission_value = substake_.emission.tao / (pool.tempo or 1) emission_cell = format_cell( @@ -408,7 +395,7 @@ def format_cell( exchange_cell, # Exchange value stake_cell, # Stake amount rate_cell, # Rate - swap_cell, # Swap value with slippage + # swap_cell, # Swap value "YES" if substake_.is_registered else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status @@ -591,12 +578,12 @@ def format_cell( f"Wallet:\n" f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" - f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" - f" Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" + f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" + # f"\n Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" ) dict_output["free_balance"] = balance.tao dict_output["total_tao_value"] = all_hks_tao_value.tao - dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao + # dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao if json_output: json_console.print(json.dumps(dict_output)) if not sub_stakes: @@ -658,12 +645,12 @@ def format_cell( ), ( "[bold tan]Exchange Value (α x τ/α)[/bold tan]", - "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].", - ), - ( - "[bold tan]Swap (α → τ)[/bold tan]", - "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].", + "This is the potential τ you will receive if you unstake from this hotkey now on this subnet. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].", ), + # ( + # "[bold tan]Swap (α → τ)[/bold tan]", + # "This is the τ you will receive if you unstake from this hotkey now on this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].", + # ), ( "[bold tan]Registered[/bold tan]", "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", From 86c032dd33b82335a9a0d2c7bea8ee7b19fa6562 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 16:05:22 -0700 Subject: [PATCH 18/20] ruff --- bittensor_cli/src/commands/stake/move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index 267875969..482dc70b5 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -59,7 +59,7 @@ async def display_stake_movement_cross_subnets( price_origin = dynamic_origin.price.tao price_destination = dynamic_destination.price.tao rate = price_origin / (price_destination or 1) - + received_amount_tao = dynamic_origin.alpha_to_tao(amount_to_move) received_amount_tao -= stake_fee received_amount = dynamic_destination.tao_to_alpha(received_amount_tao) From 59ba1daea33dd75d67d464db7c90aaa0a9a801dc Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 16:30:40 -0700 Subject: [PATCH 19/20] added docstrings --- .../src/bittensor/subtensor_interface.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 2cc26bc2f..e5b5eac12 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1604,6 +1604,14 @@ async def get_subnet_price( netuid: int = None, block_hash: Optional[str] = None, ) -> Balance: + """ + Gets the current Alpha price in TAO for a specific subnet. + + :param netuid: The unique identifier of the subnet. + :param block_hash: The hash of the block to retrieve the price from. + + :return: The current Alpha price in TAO units for the specified subnet. + """ current_sqrt_price = await self.query( module="Swap", storage_function="AlphaSqrtPrice", @@ -1618,6 +1626,14 @@ async def get_subnet_price( async def get_subnet_prices( self, block_hash: Optional[str] = None, page_size: int = 100 ) -> dict[int, Balance]: + """ + Gets the current Alpha prices in TAO for all subnets. + + :param block_hash: The hash of the block to retrieve prices from. + :param page_size: The page size for batch queries (default: 100). + + :return: A dictionary mapping netuid to the current Alpha price in TAO units. + """ query = await self.substrate.query_map( module="Swap", storage_function="AlphaSqrtPrice", From 82445e4ec663dc565c464c16e8cdd30222f6449c Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 8 Jul 2025 17:52:37 -0700 Subject: [PATCH 20/20] bumps version and changelog --- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cded2eb43..2b5e82c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 9.8.1/2025-07-08 + +## What's Changed +* Fixed broken type annotation. by @thewhaleking in https://github.com/opentensor/btcli/pull/523 +* Update/slippage price calcs by @ibraheem-abe in https://github.com/opentensor/btcli/pull/526 +* Partially fix slippage display by @gztensor in https://github.com/opentensor/btcli/pull/524 +* stake add: netuid 0 by @thewhaleking in https://github.com/opentensor/btcli/pull/525 + +## New Contributors +* @gztensor made their first contribution in https://github.com/opentensor/btcli/pull/524 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.8.0...v9.8.1 + ## 9.8.0/2025-07-07 ## What's Changed diff --git a/pyproject.toml b/pyproject.toml index d733a105e..439448ce5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.8.0" +version = "9.8.1" description = "Bittensor CLI" readme = "README.md" authors = [