From 68f6d4c1ffe408e897163340dc348912e9b9d5ee Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 7 Mar 2025 13:08:57 +0200 Subject: [PATCH 01/11] Uses the disk-cached async-substrate-interface. Must be used with https://github.com/opentensor/async-substrate-interface/pull/67 --- 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 9e8692e89..56d71acee 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -9,7 +9,7 @@ import typer -from async_substrate_interface.async_substrate import AsyncSubstrateInterface +from async_substrate_interface.async_substrate import DiskCachedAsyncSubstrateInterface as AsyncSubstrateInterface from bittensor_cli.src.bittensor.chain_data import ( DelegateInfo, StakeInfo, From 8a0dba56d5ae80eaf8b45495e7c532d021926456 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Mar 2025 15:19:32 +0200 Subject: [PATCH 02/11] Makes the disk cache optional (requires env var "DISK_CACHE=1") for the experimental state --- bittensor_cli/src/bittensor/subtensor_interface.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 56d71acee..f7a4ee39a 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1,4 +1,5 @@ import asyncio +import os from typing import Optional, Any, Union, TypedDict, Iterable import aiohttp @@ -9,7 +10,10 @@ import typer -from async_substrate_interface.async_substrate import DiskCachedAsyncSubstrateInterface as AsyncSubstrateInterface +from async_substrate_interface.async_substrate import ( + DiskCachedAsyncSubstrateInterface, + AsyncSubstrateInterface, +) from bittensor_cli.src.bittensor.chain_data import ( DelegateInfo, StakeInfo, @@ -34,6 +38,12 @@ u16_normalized_float, ) +SubstrateClass = ( + DiskCachedAsyncSubstrateInterface + if os.getenv("DISK_CACHE", "0") == "1" + else AsyncSubstrateInterface +) + class ParamWithTypes(TypedDict): name: str # Name of the parameter. @@ -98,7 +108,7 @@ def __init__(self, network): self.chain_endpoint = Constants.network_map[defaults.subtensor.network] self.network = defaults.subtensor.network - self.substrate = AsyncSubstrateInterface( + self.substrate = SubstrateClass( url=self.chain_endpoint, ss58_format=SS58_FORMAT, type_registry=TYPE_REGISTRY, From a19755c64245e47910b01c22c2b4e50b8d317c09 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Mar 2025 15:41:50 +0200 Subject: [PATCH 03/11] Bump required async-substrate-interface version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aa8043d0b..318179fdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ requires-python = ">=3.9,<3.13" dependencies = [ "wheel", "async-property==0.2.2", - "async-substrate-interface>=1.0.5", + "async-substrate-interface>=1.0.6", "aiohttp~=3.10.2", "backoff~=2.2.1", "GitPython>=3.0.0", From 739e3d2887b9e9d35b4c65b1792e69279a994d1b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Mar 2025 19:09:15 +0200 Subject: [PATCH 04/11] Name shadowing and general cleanup. --- bittensor_cli/src/bittensor/chain_data.py | 4 +- .../src/bittensor/subtensor_interface.py | 4 +- bittensor_cli/src/bittensor/utils.py | 5 +- bittensor_cli/src/commands/stake/__init__.py | 154 -------------- bittensor_cli/src/commands/stake/add.py | 42 ++-- .../src/commands/stake/children_hotkeys.py | 31 +-- bittensor_cli/src/commands/stake/list.py | 188 +++++++++--------- bittensor_cli/src/commands/stake/move.py | 3 - bittensor_cli/src/commands/stake/remove.py | 45 +++-- bittensor_cli/src/commands/subnets/subnets.py | 27 ++- bittensor_cli/src/commands/sudo.py | 3 +- bittensor_cli/src/commands/wallets.py | 7 +- bittensor_cli/version.py | 1 + 13 files changed, 178 insertions(+), 336 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 628821aea..ddbbc7249 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -745,7 +745,9 @@ def tao_to_alpha(self, tao: Balance) -> Balance: def alpha_to_tao(self, alpha: Balance) -> Balance: return Balance.from_tao(alpha.tao * self.price.tao) - def tao_to_alpha_with_slippage(self, tao: Balance) -> tuple[Balance, Balance]: + def tao_to_alpha_with_slippage( + self, tao: Balance + ) -> tuple[Balance, Balance, float]: """ Returns an estimate of how much Alpha would a staker receive if they stake their tao using the current pool state. Args: diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 9e8692e89..dc0606978 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -207,7 +207,7 @@ async def get_stake_for_coldkey( if result is None: return [] - stakes = StakeInfo.list_from_any(result) + stakes: list[StakeInfo] = StakeInfo.list_from_any(result) return [stake for stake in stakes if stake.stake > 0] async def get_stake_for_coldkey_and_hotkey( @@ -352,14 +352,12 @@ async def get_total_stake_for_coldkey( self, *ss58_addresses, block_hash: Optional[str] = None, - reuse_block: bool = False, ) -> dict[str, tuple[Balance, Balance]]: """ Returns the total stake held on a coldkey. :param ss58_addresses: The SS58 address(es) of the coldkey(s) :param block_hash: The hash of the block number to retrieve the stake from. - :param reuse_block: Whether to reuse the last-used block hash when retrieving info. :return: {address: Balance objects} """ diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 35e3394a2..318847c85 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -5,7 +5,6 @@ import sqlite3 import platform import webbrowser -import sys from pathlib import Path from typing import TYPE_CHECKING, Any, Collection, Optional, Union, Callable from urllib.parse import urlparse @@ -73,8 +72,8 @@ def coldkeypub(self): return self._coldkeypub -def print_console(message: str, colour: str, title: str, console: Console): - console.print( +def print_console(message: str, colour: str, title: str, console_: Console): + console_.print( f"[bold {colour}][{title}]:[/bold {colour}] [{colour}]{message}[/{colour}]\n" ) diff --git a/bittensor_cli/src/commands/stake/__init__.py b/bittensor_cli/src/commands/stake/__init__.py index d1dceeaba..e69de29bb 100644 --- a/bittensor_cli/src/commands/stake/__init__.py +++ b/bittensor_cli/src/commands/stake/__init__.py @@ -1,154 +0,0 @@ -from typing import Optional, TYPE_CHECKING - -import rich.prompt -from rich.table import Table - -from bittensor_cli.src.bittensor.chain_data import DelegateInfoLite -from bittensor_cli.src.bittensor.utils import console - -if TYPE_CHECKING: - from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface - - -async def select_delegate(subtensor: "SubtensorInterface", netuid: int): - # Get a list of delegates and sort them by total stake in descending order - delegates: list[DelegateInfoLite] = ( - await subtensor.get_delegates_by_netuid_light(netuid) - ).sort(key=lambda x: x.total_stake, reverse=True) - - # Get registered delegates details. - registered_delegate_info = await subtensor.get_delegate_identities() - - # Create a table to display delegate information - table = Table( - show_header=True, - header_style="bold", - border_style="rgb(7,54,66)", - style="rgb(0,43,54)", - ) - - # Add columns to the table with specific styles - table.add_column("Index", style="rgb(253,246,227)", no_wrap=True) - table.add_column("Delegate Name", no_wrap=True) - table.add_column("Hotkey SS58", style="rgb(211,54,130)", no_wrap=True) - table.add_column("Owner SS58", style="rgb(133,153,0)", no_wrap=True) - table.add_column("Take", style="rgb(181,137,0)", no_wrap=True) - table.add_column( - "Total Stake", style="rgb(38,139,210)", no_wrap=True, justify="right" - ) - table.add_column( - "Owner Stake", style="rgb(220,50,47)", no_wrap=True, justify="right" - ) - # table.add_column("Return per 1000", style="rgb(108,113,196)", no_wrap=True, justify="right") - # table.add_column("Total Daily Return", style="rgb(42,161,152)", no_wrap=True, justify="right") - - # List to store visible delegates - visible_delegates = [] - - def get_user_input() -> str: - return rich.prompt.Prompt.ask( - 'Press Enter to scroll, enter a number (1-N) to select, or type "h" for help: ', - choices=["", "h"] + [str(x) for x in range(1, len(delegates) - 1)], - show_choices=True, - ) - - # TODO: Add pagination to handle large number of delegates more efficiently - # Iterate through delegates and display their information - - def loop_selections() -> Optional[int]: - idx = 0 - selected_idx = None - while idx < len(delegates): - if idx < len(delegates): - delegate = delegates[idx] - - # Add delegate to visible list - visible_delegates.append(delegate) - - # Add a row to the table with delegate information - table.add_row( - str(idx), - registered_delegate_info[delegate.hotkey_ss58].name - if delegate.hotkey_ss58 in registered_delegate_info - else "", - delegate.hotkey_ss58[:5] - + "..." - + delegate.hotkey_ss58[-5:], # Show truncated hotkey - delegate.owner_ss58[:5] - + "..." - + delegate.owner_ss58[-5:], # Show truncated owner address - f"{delegate.take:.6f}", - f"τ{delegate.total_stake.tao:,.4f}", - f"τ{delegate.owner_stake.tao:,.4f}", - # f"τ{delegate.return_per_1000.tao:,.4f}", - # f"τ{delegate.total_daily_return.tao:,.4f}", - ) - - # Clear console and print updated table - console.clear() - console.print(table) - - # Prompt user for input - user_input: str = get_user_input() - - # Add a help option to display information about each column - if user_input == "h": - console.print("\nColumn Information:") - console.print( - "[rgb(253,246,227)]Index:[/rgb(253,246,227)] Position in the list of delegates" - ) - console.print( - "[rgb(211,54,130)]Hotkey SS58:[/rgb(211,54,130)] Truncated public key of the delegate's hotkey" - ) - console.print( - "[rgb(133,153,0)]Owner SS58:[/rgb(133,153,0)] Truncated public key of the delegate's owner" - ) - console.print( - "[rgb(181,137,0)]Take:[/rgb(181,137,0)] Percentage of rewards the delegate takes" - ) - console.print( - "[rgb(38,139,210)]Total Stake:[/rgb(38,139,210)] Total amount staked to this delegate" - ) - console.print( - "[rgb(220,50,47)]Owner Stake:[/rgb(220,50,47)] Amount staked by the delegate owner" - ) - console.print( - "[rgb(108,113,196)]Return per 1000:[/rgb(108,113,196)] Estimated return for 1000 Tao staked" - ) - console.print( - "[rgb(42,161,152)]Total Daily Return:[/rgb(42,161,152)] Estimated total daily return for all stake" - ) - user_input = get_user_input() - - # If user presses Enter, continue to next delegate - if user_input and user_input != "h": - selected_idx = int(user_input) - break - - if idx < len(delegates): - idx += 1 - - return selected_idx - - # TODO( const ): uncomment for check - # Add a confirmation step before returning the selected delegate - # console.print(f"\nSelected delegate: [rgb(211,54,130)]{visible_delegates[selected_idx].hotkey_ss58}[/rgb(211,54,130)]") - # console.print(f"Take: [rgb(181,137,0)]{visible_delegates[selected_idx].take:.6f}[/rgb(181,137,0)]") - # console.print(f"Total Stake: [rgb(38,139,210)]{visible_delegates[selected_idx].total_stake}[/rgb(38,139,210)]") - - # confirmation = Prompt.ask("Do you want to proceed with this delegate? (y/n)") - # if confirmation.lower() != 'yes' and confirmation.lower() != 'y': - # return select_delegate( subtensor, netuid ) - - # Return the selected delegate - while True: - selected_idx_ = loop_selections() - if selected_idx_ is None: - if not rich.prompt.Confirm.ask( - "You've reached the end of the list. You must make a selection. Loop through again?" - ): - raise IndexError - else: - continue - else: - return delegates[selected_idx_] diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 6932a2298..6d156f522 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -46,9 +46,7 @@ async def stake_add( netuid: the netuid to stake to (None indicates all subnets) stake_all: whether to stake all available balance amount: specified amount of balance to stake - delegate: whether to delegate stake, currently unused prompt: whether to prompt the user - max_stake: maximum amount to stake (used in combination with stake_all), currently unused all_hotkeys: whether to stake all hotkeys include_hotkeys: list of hotkeys to include in staking process (if not specifying `--all`) exclude_hotkeys: list of hotkeys to exclude in staking (if specifying `--all`) @@ -61,18 +59,16 @@ async def stake_add( """ async def safe_stake_extrinsic( - netuid: int, - amount: Balance, + netuid_: int, + amount_: Balance, current_stake: Balance, - hotkey_ss58: str, + hotkey_ss58_: str, price_limit: Balance, - wallet: Wallet, - subtensor: "SubtensorInterface", status=None, ) -> None: err_out = partial(print_error, status=status) failure_prelude = ( - f":cross_mark: [red]Failed[/red] to stake {amount} on Netuid {netuid}" + f":cross_mark: [red]Failed[/red] to stake {amount_} on Netuid {netuid_}" ) current_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) next_nonce = await subtensor.substrate.get_account_next_index( @@ -82,9 +78,9 @@ async def safe_stake_extrinsic( call_module="SubtensorModule", call_function="add_stake_limit", call_params={ - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": amount.rao, + "hotkey": hotkey_ss58_, + "netuid": netuid_, + "amount_staked": amount_.rao, "limit_price": price_limit, "allow_partial": allow_partial_stake, }, @@ -119,30 +115,30 @@ async def safe_stake_extrinsic( new_balance, new_stake = await asyncio.gather( subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash), subtensor.get_stake( - hotkey_ss58=hotkey_ss58, + hotkey_ss58=hotkey_ss58_, coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=netuid, + netuid=netuid_, block_hash=block_hash, ), ) console.print( - f":white_heavy_check_mark: [dark_sea_green3]Finalized. Stake added to netuid: {netuid}[/dark_sea_green3]" + f":white_heavy_check_mark: [dark_sea_green3]Finalized. Stake added to netuid: {netuid_}[/dark_sea_green3]" ) console.print( f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" ) amount_staked = current_balance - new_balance - if allow_partial_stake and (amount_staked != amount): + if allow_partial_stake and (amount_staked != amount_): console.print( "Partial stake transaction. Staked:\n" f" [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_staked}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] " f"instead of " - f"[blue]{amount}[/blue]" + f"[blue]{amount_}[/blue]" ) console.print( - f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] " + f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] " f"Stake:\n" f" [blue]{current_stake}[/blue] " f":arrow_right: " @@ -361,13 +357,11 @@ async def stake_extrinsic( else: stake_coroutines.append( safe_stake_extrinsic( - netuid=ni, - amount=am, + netuid_=ni, + amount_=am, current_stake=curr, - hotkey_ss58=staking_address, + hotkey_ss58_=staking_address, price_limit=price_with_tolerance, - wallet=wallet, - subtensor=subtensor, ) ) else: @@ -590,7 +584,9 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b console.print(base_description + (safe_staking_description if safe_staking else "")) -def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, float]: +def _calculate_slippage( + subnet_info, amount: Balance +) -> tuple[Balance, str, float, str]: """Calculate slippage when adding stake. Args: diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index a19bf5af3..fff84c65d 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -313,9 +313,12 @@ async def get_take(child: tuple, netuid__: int) -> float: """ Get the take value for a given subtensor, hotkey, and netuid. - @param child: The hotkey to retrieve the take value for. + Arguments: + child: The hotkey to retrieve the take value for. + netuid__: the netuid to retrieve the take value for. - @return: The take value as a float. If the take value is not available, it returns 0. + Returns: + The take value as a float. If the take value is not available, it returns 0. """ child_hotkey = child[1] @@ -383,7 +386,7 @@ async def _render_table( f"The total stake of parent hotkey '{parent_hotkey}'{insert_text}is {parent_total}." ) - for index, (netuid_, children_) in enumerate(netuid_children_): + for index, (child_netuid, children_) in enumerate(netuid_children_): # calculate totals total_proportion_per_netuid = 0 total_stake_weight_per_netuid = 0 @@ -393,7 +396,7 @@ async def _render_table( children_info = [] child_takes = await asyncio.gather( - *[get_take(c, netuid_) for c in children_] + *[get_take(c, child_netuid) for c in children_] ) for child, child_take in zip(children_, child_takes): proportion = child[0] @@ -408,7 +411,7 @@ async def _render_table( ( converted_proportion, child_hotkey, - hotkey_stake_dict[child_hotkey][netuid_], + hotkey_stake_dict[child_hotkey][child_netuid], child_take, ) ) @@ -420,7 +423,7 @@ async def _render_table( for proportion_, hotkey, stake, child_take in children_info: proportion_percent = proportion_ * 100 # Proportion in percent proportion_tao = ( - hotkey_stake[netuid_].tao * proportion_ + hotkey_stake[child_netuid].tao * proportion_ ) # Proportion in TAO total_proportion_per_netuid += proportion_percent @@ -433,7 +436,7 @@ async def _render_table( hotkey = Text(hotkey, style="italic red" if proportion_ == 0 else "") table.add_row( - str(netuid_), + str(child_netuid), hotkey, proportion_str, take_str, @@ -661,28 +664,28 @@ def print_all_takes(takes: list[tuple[int, float]], ss58: str): table.add_column("Netuid", justify="center", style="cyan") table.add_column("Take (%)", justify="right", style="magenta") - for netuid, take_value in takes: - table.add_row(str(netuid), f"{take_value:.2f}%") + for take_netuid, take_value in takes: + table.add_row(str(take_netuid), f"{take_value:.2f}%") console.print(table) - async def display_chk_take(ss58, netuid): + async def display_chk_take(ss58, take_netuid): """Print single key take for hotkey and netuid""" chk_take = await get_childkey_take( - subtensor=subtensor, netuid=netuid, hotkey=ss58 + subtensor=subtensor, netuid=take_netuid, hotkey=ss58 ) if chk_take is None: chk_take = 0 chk_take = u16_to_float(chk_take) console.print( - f"Child take for {ss58} is: {chk_take * 100:.2f}% on netuid {netuid}." + f"Child take for {ss58} is: {chk_take * 100:.2f}% on netuid {take_netuid}." ) async def chk_all_subnets(ss58): """Aggregate data for childkey take from all subnets""" - netuids = await subtensor.get_all_subnet_netuids() + all_netuids = await subtensor.get_all_subnet_netuids() takes = [] - for subnet in netuids: + for subnet in all_netuids: if subnet == 0: continue curr_take = await get_childkey_take( diff --git a/bittensor_cli/src/commands/stake/list.py b/bittensor_cli/src/commands/stake/list.py index d5b5493b9..eb5340fcd 100644 --- a/bittensor_cli/src/commands/stake/list.py +++ b/bittensor_cli/src/commands/stake/list.py @@ -34,38 +34,37 @@ async def stake_list( ): coldkey_address = coldkey_ss58 if coldkey_ss58 else wallet.coldkeypub.ss58_address - async def get_stake_data(block_hash: str = None): + async def get_stake_data(block_hash_: str = None): ( - sub_stakes, - registered_delegate_info, + sub_stakes_, + registered_delegate_info_, _dynamic_info, ) = await asyncio.gather( subtensor.get_stake_for_coldkey( - coldkey_ss58=coldkey_address, block_hash=block_hash + coldkey_ss58=coldkey_address, block_hash=block_hash_ ), - subtensor.get_delegate_identities(block_hash=block_hash), - subtensor.all_subnets(block_hash=block_hash), + subtensor.get_delegate_identities(block_hash=block_hash_), + subtensor.all_subnets(block_hash=block_hash_), ) # sub_stakes = substakes[coldkey_address] - dynamic_info = {info.netuid: info for info in _dynamic_info} + dynamic_info__ = {info.netuid: info for info in _dynamic_info} return ( - sub_stakes, - registered_delegate_info, - dynamic_info, + sub_stakes_, + registered_delegate_info_, + dynamic_info__, ) def define_table( - hotkey_name: str, + hotkey_name_: str, rows: list[list[str]], - total_tao_value: Balance, - total_swapped_tao_value: Balance, - live: bool = False, + total_tao_value_: Balance, + total_swapped_tao_value_: Balance, ): - title = f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {hotkey_name}\nNetwork: {subtensor.network}\n\n" + title = f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {hotkey_name_}\nNetwork: {subtensor.network}\n\n" # TODO: Add hint back in after adding columns descriptions # if not live: # title += f"[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n" - table = Table( + defined_table = Table( title=title, show_footer=True, show_edge=False, @@ -76,74 +75,74 @@ def define_table( show_lines=False, pad_edge=True, ) - table.add_column( + defined_table.add_column( "[white]Netuid", footer=f"{len(rows)}", footer_style="overline white", style="grey89", ) - table.add_column( + defined_table.add_column( "[white]Name", style="cyan", justify="left", no_wrap=True, ) - table.add_column( + defined_table.add_column( f"[white]Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", footer_style="overline white", style=COLOR_PALETTE["STAKE"]["TAO"], justify="right", - footer=f"τ {millify_tao(total_tao_value.tao)}" + footer=f"τ {millify_tao(total_tao_value_.tao)}" if not verbose - else f"{total_tao_value}", + else f"{total_tao_value_}", ) - table.add_column( + defined_table.add_column( f"[white]Stake ({Balance.get_unit(1)})", footer_style="overline white", style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="center", ) - table.add_column( + defined_table.add_column( f"[white]Price \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", footer_style="white", style=COLOR_PALETTE["POOLS"]["RATE"], justify="center", ) - table.add_column( + 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)}" + footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}" if not verbose - else f"{total_swapped_tao_value}", + else f"{total_swapped_tao_value_}", ) - table.add_column( + defined_table.add_column( "[white]Registered", style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="right", ) - table.add_column( + defined_table.add_column( f"[white]Emission \n({Balance.get_unit(1)}/block)", style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="right", ) - table.add_column( + defined_table.add_column( f"[white]Emission \n({Balance.get_unit(0)}/block)", style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="right", ) - return table + return defined_table def create_table(hotkey_: str, substakes: list[StakeInfo]): - name = ( + name_ = ( f"{registered_delegate_info[hotkey_].display} ({hotkey_})" if hotkey_ in registered_delegate_info else hotkey_ ) rows = [] - total_tao_value = Balance(0) - total_swapped_tao_value = Balance(0) + total_tao_value_ = Balance(0) + total_swapped_tao_value_ = Balance(0) root_stakes = [s for s in substakes if s.netuid == 0] other_stakes = sorted( [s for s in substakes if s.netuid != 0], @@ -162,14 +161,14 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid) # TAO value cell - tao_value = pool.alpha_to_tao(alpha_value) - total_tao_value += tao_value + tao_value_ = pool.alpha_to_tao(alpha_value) + total_tao_value_ += tao_value_ # Swapped TAO value and slippage cell - swapped_tao_value, _, slippage_percentage_ = ( + swapped_tao_value_, _, slippage_percentage_ = ( pool.alpha_to_tao_with_slippage(substake_.stake) ) - total_swapped_tao_value += swapped_tao_value + total_swapped_tao_value_ += swapped_tao_value_ # Slippage percentage cell if pool.is_dynamic: @@ -181,9 +180,9 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_percentage})" else: swap_value = ( - f"τ {millify_tao(swapped_tao_value.tao)} ({slippage_percentage})" + f"τ {millify_tao(swapped_tao_value_.tao)} ({slippage_percentage})" if not verbose - else f"{swapped_tao_value} ({slippage_percentage})" + else f"{swapped_tao_value_} ({slippage_percentage})" ) # Per block emission cell @@ -202,9 +201,9 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): [ str(netuid), # Number subnet_name_cell, # Symbol + name - f"τ {millify_tao(tao_value.tao)}" + f"τ {millify_tao(tao_value_.tao)}" if not verbose - else f"{tao_value}", # Value (α x τ/α) + else f"{tao_value_}", # Value (α x τ/α) f"{stake_value} {symbol}" if netuid != 0 else f"{symbol} {stake_value}", # Stake (a) @@ -221,27 +220,33 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): str(Balance.from_tao(per_block_tao_emission)), ] ) - table = define_table(name, rows, total_tao_value, total_swapped_tao_value) + created_table = define_table( + name_, rows, total_tao_value_, total_swapped_tao_value_ + ) for row in rows: - table.add_row(*row) - console.print(table) - return total_tao_value, total_swapped_tao_value + created_table.add_row(*row) + console.print(created_table) + return total_tao_value_, total_swapped_tao_value_ def create_live_table( substakes: list, - registered_delegate_info: dict, - dynamic_info: dict, - hotkey_name: str, - previous_data: Optional[dict] = None, - ) -> tuple[Table, dict, Balance, Balance, Balance]: + dynamic_info_for_lt: dict, + hotkey_name_: str, + previous_data_: Optional[dict] = None, + ) -> tuple[Table, dict]: rows = [] - current_data = {} + current_data_ = {} - total_tao_value = Balance(0) - total_swapped_tao_value = Balance(0) + total_tao_value_ = Balance(0) + total_swapped_tao_value_ = Balance(0) def format_cell( - value, previous_value, unit="", unit_first=False, precision=4, millify=False + value, + previous_value, + unit="", + unit_first_=False, + precision=4, + millify=False, ): if previous_value is not None: change = value - previous_value @@ -265,7 +270,7 @@ def format_cell( ) return ( f"{formatted_value} {unit}{change_text}" - if not unit_first + if not unit_first_ else f"{unit} {formatted_value}{change_text}" ) @@ -273,7 +278,7 @@ def format_cell( root_stakes = [s for s in substakes if s.netuid == 0] other_stakes = sorted( [s for s in substakes if s.netuid != 0], - key=lambda x: dynamic_info[x.netuid] + key=lambda x: dynamic_info_for_lt[x.netuid] .alpha_to_tao(Balance.from_rao(int(x.stake.rao)).set_unit(x.netuid)) .tao, reverse=True, @@ -281,41 +286,41 @@ def format_cell( sorted_substakes = root_stakes + other_stakes # Process each stake - for substake in sorted_substakes: - netuid = substake.netuid - pool = dynamic_info.get(netuid) - if substake.stake.rao == 0 or not pool: + for substake_ in sorted_substakes: + netuid = substake_.netuid + pool = dynamic_info_for_lt.get(netuid) + if substake_.stake.rao == 0 or not pool: continue # Calculate base values symbol = f"{Balance.get_unit(netuid)}\u200e" - 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 + 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) ) - total_swapped_tao_value += swapped_tao_value + total_swapped_tao_value_ += swapped_tao_value_ # Store current values for future delta tracking - current_data[netuid] = { + current_data_[netuid] = { "stake": alpha_value.tao, "price": pool.price.tao, - "tao_value": tao_value.tao, - "swapped_value": swapped_tao_value.tao, - "emission": substake.emission.tao / (pool.tempo or 1), - "tao_emission": substake.tao_emission.tao / (pool.tempo or 1), + "tao_value": tao_value_.tao, + "swapped_value": swapped_tao_value_.tao, + "emission": substake_.emission.tao / (pool.tempo or 1), + "tao_emission": substake_.tao_emission.tao / (pool.tempo or 1), } # Get previous values for delta tracking - prev = previous_data.get(netuid, {}) if previous_data else {} + prev = previous_data_.get(netuid, {}) if previous_data_ else {} unit_first = True if netuid == 0 else False stake_cell = format_cell( alpha_value.tao, prev.get("stake"), unit=symbol, - unit_first=unit_first, + unit_first_=unit_first, precision=4, millify=True if not verbose else False, ) @@ -324,16 +329,16 @@ def format_cell( pool.price.tao, prev.get("price"), unit=f"τ/{symbol}", - unit_first=False, + unit_first_=False, precision=5, millify=True if not verbose else False, ) exchange_cell = format_cell( - tao_value.tao, + tao_value_.tao, prev.get("tao_value"), unit="τ", - unit_first=True, + unit_first_=True, precision=4, millify=True if not verbose else False, ) @@ -341,10 +346,10 @@ def format_cell( if netuid != 0: swap_cell = ( format_cell( - swapped_tao_value.tao, + swapped_tao_value_.tao, prev.get("swapped_value"), unit="τ", - unit_first=True, + unit_first_=True, precision=4, millify=True if not verbose else False, ) @@ -353,27 +358,27 @@ def format_cell( else: swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_pct}%)" - emission_value = substake.emission.tao / (pool.tempo or 1) + emission_value = substake_.emission.tao / (pool.tempo or 1) emission_cell = format_cell( emission_value, prev.get("emission"), unit=symbol, - unit_first=unit_first, + unit_first_=unit_first, precision=4, ) - tao_emission_value = substake.tao_emission.tao / (pool.tempo or 1) + tao_emission_value = substake_.tao_emission.tao / (pool.tempo or 1) tao_emission_cell = format_cell( tao_emission_value, prev.get("tao_emission"), unit="τ", - unit_first=unit_first, + unit_first_=unit_first, precision=4, ) subnet_name_cell = ( f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]" - f" {get_subnet_name(dynamic_info[netuid])}" + f" {get_subnet_name(dynamic_info_for_lt[netuid])}" ) rows.append( @@ -385,25 +390,21 @@ def format_cell( rate_cell, # Rate swap_cell, # Swap value with slippage "YES" - if substake.is_registered + if substake_.is_registered else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status emission_cell, # Emission rate tao_emission_cell, # TAO emission rate ] ) - table = define_table( - hotkey_name, - rows, - total_tao_value, - total_swapped_tao_value, - live=True, + live_table = define_table( + hotkey_name_, rows, total_tao_value_, total_swapped_tao_value_ ) for row in rows: - table.add_row(*row) + live_table.add_row(*row) - return table, current_data + return live_table, current_data_ # Main execution block_hash = await subtensor.substrate.get_chain_head() @@ -448,10 +449,8 @@ def format_cell( choices=[str(i) for i in range(len(hotkeys_to_substakes))], ) selected_hotkey = list(hotkeys_to_substakes.keys())[int(selected_idx)] - selected_stakes = hotkeys_to_substakes[selected_hotkey] else: selected_hotkey = list(hotkeys_to_substakes.keys())[0] - selected_stakes = hotkeys_to_substakes[selected_hotkey] hotkey_name = ( f"{registered_delegate_info[selected_hotkey].display} ({selected_hotkey})" @@ -499,7 +498,6 @@ def format_cell( table, current_data = create_live_table( selected_stakes, - registered_delegate_info, dynamic_info_, hotkey_name, previous_data, diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index 0aa23278d..70afaa453 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -494,7 +494,6 @@ async def move_stake( ) # Determine the amount we are moving. - amount_to_move_as_balance = None if amount: amount_to_move_as_balance = Balance.from_tao(amount) elif stake_all: @@ -669,7 +668,6 @@ async def transfer_stake( ) return False - amount_to_transfer = None if amount: amount_to_transfer = Balance.from_tao(amount).set_unit(origin_netuid) elif stake_all: @@ -784,7 +782,6 @@ async def swap_stake( Args: wallet (Wallet): The wallet to swap stake from. subtensor (SubtensorInterface): Subtensor interface instance. - hotkey_ss58 (str): The SS58 address of the hotkey whose stake is being swapped. origin_netuid (int): The netuid from which stake is removed. destination_netuid (int): The netuid to which stake is added. amount (float): The amount to swap. diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 1097faf7b..61199f954 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -43,7 +43,6 @@ async def unstake( allow_partial_stake: bool, ): """Unstake from hotkey(s).""" - unstake_all_from_hk = False with console.status( f"Retrieving subnet data & identities from {subtensor.network}...", spinner="earth", @@ -145,7 +144,7 @@ async def unstake( staking_address_name, staking_address_ss58, netuid = hotkey netuids_to_process = [netuid] else: - staking_address_name, staking_address_ss58 = hotkey + staking_address_name, staking_address_ss58, _ = hotkey netuids_to_process = netuids initial_amount = amount @@ -179,7 +178,6 @@ async def unstake( if staking_address_name else staking_address_ss58, staking_address_ss58, - interactive, ) if amount_to_unstake_as_balance is None: skip_remaining_subnets = True @@ -189,8 +187,10 @@ async def unstake( amount_to_unstake_as_balance.set_unit(netuid) if amount_to_unstake_as_balance > current_stake_balance: err_console.print( - f"[red]Not enough stake to remove[/red]:\n Stake balance: [dark_orange]{current_stake_balance}[/dark_orange]" - f" < Unstaking amount: [dark_orange]{amount_to_unstake_as_balance}[/dark_orange] on netuid: {netuid}" + f"[red]Not enough stake to remove[/red]:\n" + f" Stake balance: [dark_orange]{current_stake_balance}[/dark_orange]" + f" < Unstaking amount: [dark_orange]{amount_to_unstake_as_balance}[/dark_orange]" + f" on netuid: {netuid}" ) continue # Skip to the next subnet - useful when single amount is specified for all subnets @@ -292,7 +292,6 @@ async def unstake( subtensor=subtensor, netuid=op["netuid"], amount=op["amount_to_unstake"], - current_stake=op["current_stake_balance"], hotkey_ss58=op["hotkey_ss58"], price_limit=op["price_with_tolerance"], allow_partial_stake=allow_partial_stake, @@ -320,12 +319,13 @@ async def unstake_all( hotkey_ss58_address: str, unstake_all_alpha: bool = False, all_hotkeys: bool = False, - include_hotkeys: list[str] = [], - exclude_hotkeys: list[str] = [], + include_hotkeys: Optional[list[str]] = None, + exclude_hotkeys: Optional[list[str]] = None, prompt: bool = True, ) -> bool: """Unstakes all stakes from all hotkeys in all subnets.""" - + include_hotkeys = include_hotkeys or [] + exclude_hotkeys = exclude_hotkeys or [] with console.status( f"Retrieving stake information & identities from {subtensor.network}...", spinner="earth", @@ -567,7 +567,6 @@ async def _safe_unstake_extrinsic( subtensor: "SubtensorInterface", netuid: int, amount: Balance, - current_stake: Balance, hotkey_ss58: str, price_limit: Balance, allow_partial_stake: bool, @@ -578,7 +577,6 @@ async def _safe_unstake_extrinsic( Args: netuid: The subnet ID amount: Amount to unstake - current_stake: Current stake balance hotkey_ss58: Hotkey SS58 address price_limit: Maximum acceptable price wallet: Wallet instance @@ -724,6 +722,7 @@ async def _unstake_all_extrinsic( current_balance = await subtensor.get_balance( wallet.coldkeypub.ss58_address, block_hash=block_hash ) + previous_root_stake = None call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all" call = await subtensor.substrate.compose_call( @@ -768,6 +767,7 @@ async def _unstake_all_extrinsic( new_balance = await subtensor.get_balance( wallet.coldkeypub.ss58_address, block_hash=block_hash ) + new_root_stake = None success_message = ( ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all stakes[/green]" @@ -781,7 +781,9 @@ async def _unstake_all_extrinsic( if unstake_all_alpha: console.print( - f"Root Stake for {hotkey_name}:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_root_stake}" + f"Root Stake for {hotkey_name}:\n " + f"[blue]{previous_root_stake}[/blue] :arrow_right: " + f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_root_stake}" ) except Exception as e: @@ -793,7 +795,7 @@ def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, flo """Calculate slippage and received amount for unstaking operation. Args: - dynamic_info: Subnet information containing price data + subnet_info: Subnet information containing price data amount: Amount being unstaked Returns: @@ -821,7 +823,7 @@ async def _unstake_selection( old_identities, stake_infos, netuid: Optional[int] = None, -): +) -> tuple[list[tuple[str, str, int]], bool]: if not stake_infos: print_error("You have no stakes to unstake.") raise ValueError @@ -899,7 +901,9 @@ async def _unstake_selection( # Display hotkey's staked netuids with amount. table = Table( - title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Stakes for hotkey \n[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey_name}\n{selected_hotkey_ss58}\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Stakes for hotkey \n" + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey_name}\n" + f"{selected_hotkey_ss58}\n", show_footer=True, show_edge=False, header_style="bold white", @@ -925,19 +929,20 @@ async def _unstake_selection( console.print("\n", table, "\n") # Ask which netuids to unstake from for the selected hotkey. - unstake_all = False + unstake_all_ = False if netuid is not None: selected_netuids = [netuid] else: while True: netuid_input = Prompt.ask( - "\nEnter the netuids of the [blue]subnets to unstake[/blue] from (comma-separated), or '[blue]all[/blue]' to unstake from all", + "\nEnter the netuids of the [blue]subnets to unstake[/blue] from (comma-separated), or " + "'[blue]all[/blue]' to unstake from all", default="all", ) if netuid_input.lower() == "all": selected_netuids = list(netuid_stakes.keys()) - unstake_all = True + unstake_all_ = True break else: try: @@ -960,7 +965,7 @@ async def _unstake_selection( hotkeys_to_unstake_from.append( (selected_hotkey_name, selected_hotkey_ss58, netuid_) ) - return hotkeys_to_unstake_from, unstake_all + return hotkeys_to_unstake_from, unstake_all_ def _ask_unstake_amount( @@ -968,7 +973,6 @@ def _ask_unstake_amount( netuid: int, staking_address_name: str, staking_address_ss58: str, - interactive: bool, ) -> Optional[Balance]: """Prompt the user to decide the amount to unstake. @@ -977,7 +981,6 @@ def _ask_unstake_amount( netuid: The subnet ID staking_address_name: Display name of the staking address staking_address_ss58: SS58 address of the staking address - interactive: Whether in interactive mode (affects default choice) Returns: Balance amount to unstake, or None if user chooses to quit diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 1db23a4d0..81043d8c2 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -111,6 +111,12 @@ async def _find_event_attributes_in_extrinsic_receipt( ): return False + call_params = { + "hotkey": wallet.hotkey.ss58_address, + "mechid": 1, + } + call_function = "register_network" + has_identity = any(subnet_identity.values()) if has_identity: identity_data = { @@ -136,6 +142,8 @@ async def _find_event_attributes_in_extrinsic_receipt( if subnet_identity.get("additional") else b"", } + call_params["identity"] = identity_data + call_function = "register_network_with_identity" for field, value in identity_data.items(): max_size = 64 # bytes if len(value) > max_size: @@ -149,15 +157,6 @@ async def _find_event_attributes_in_extrinsic_receipt( return False with console.status(":satellite: Registering subnet...", spinner="earth"): - call_params = { - "hotkey": wallet.hotkey.ss58_address, - "mechid": 1, - } - call_function = "register_network" - if has_identity: - call_params["identity"] = identity_data - call_function = "register_network_with_identity" - substrate = subtensor.substrate # create extrinsic call call = await substrate.compose_call( @@ -1341,12 +1340,12 @@ async def burn_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: f":satellite:Retrieving lock cost from {subtensor.network}...", spinner="aesthetic", ): - burn_cost = await subtensor.burn_cost() - if burn_cost: + current_burn_cost = await subtensor.burn_cost() + if current_burn_cost: console.print( - f"Subnet burn cost: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{burn_cost}" + f"Subnet burn cost: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_burn_cost}" ) - return burn_cost + return current_burn_cost else: err_console.print( "Subnet burn cost: [red]Failed to get subnet burn cost[/red]" @@ -1417,7 +1416,7 @@ async def pow_register( use_cuda, dev_id, threads_per_block, - prompt: bool + prompt: bool, ): """Register neuron.""" diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index ef9895d26..8184bd793 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -155,7 +155,6 @@ async def set_hyperparameter_extrinsic( `False` if the extrinsic fails to enter the block within the timeout. :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - :param prompt: If `True`, the call waits for confirmation from the user before proceeding. :return: success: `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the response is `True`. @@ -178,6 +177,7 @@ async def set_hyperparameter_extrinsic( arbitrary_extrinsic = False extrinsic, sudo_ = HYPERPARAMS.get(parameter, ("", False)) + call_params = {"netuid": netuid} if not extrinsic: arbitrary_extrinsic, call_params = search_metadata( parameter, value, netuid, subtensor.substrate.metadata @@ -207,7 +207,6 @@ async def set_hyperparameter_extrinsic( extrinsic_params = await substrate.get_metadata_call_function( "AdminUtils", extrinsic ) - call_params = {"netuid": netuid} # if input value is a list, iterate through the list and assign values if isinstance(value, list): diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 8710de3e9..82b1253ac 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -124,17 +124,18 @@ async def regen_hotkey( json_str = f.read() try: - new_hotkey = wallet.regenerate_hotkey( + new_hotkey_ = wallet.regenerate_hotkey( mnemonic=mnemonic, seed=seed, json=(json_str, json_password) if all([json_str, json_password]) else None, use_password=use_password, overwrite=overwrite, ) - if isinstance(new_hotkey, Wallet): + if isinstance(new_hotkey_, Wallet): console.print( "\n✅ [dark_sea_green]Regenerated hotkey successfully!\n", - f"[dark_sea_green]Wallet name: ({new_hotkey.name}), path: ({new_hotkey.path}), hotkey ss58: ({new_hotkey.hotkey.ss58_address})", + f"[dark_sea_green]Wallet name: " + f"({new_hotkey_.name}), path: ({new_hotkey_.path}), hotkey ss58: ({new_hotkey_.hotkey.ss58_address})", ) except ValueError: print_error("Mnemonic phrase is invalid") diff --git a/bittensor_cli/version.py b/bittensor_cli/version.py index 25e3ad899..3a916341f 100644 --- a/bittensor_cli/version.py +++ b/bittensor_cli/version.py @@ -15,5 +15,6 @@ def version_as_int(version): __new_signature_version__ = 360 return __version_as_int__ + __version__ = "9.1.2" __version_as_int__ = version_as_int(__version__) From cf8bef2dfba497d98d669be46c9228e14e983a9e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 12 Mar 2025 22:02:24 +0200 Subject: [PATCH 05/11] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 318179fdb..8d2b6986f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ requires-python = ">=3.9,<3.13" dependencies = [ "wheel", "async-property==0.2.2", - "async-substrate-interface>=1.0.6", + "async-substrate-interface>=1.0.7", "aiohttp~=3.10.2", "backoff~=2.2.1", "GitPython>=3.0.0", From 0c5bcf533554a0203048282df6b7f56abc16fe44 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Mar 2025 17:20:19 +0200 Subject: [PATCH 06/11] Type return + docstring --- bittensor_cli/src/commands/stake/remove.py | 26 ++++++++++++---------- tests/e2e_tests/test_unstaking.py | 2 ++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 61199f954..07718e405 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -356,12 +356,12 @@ async def unstake_all( old_identities=old_identities, ) elif not hotkey_ss58_address: - hotkeys = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] + hotkeys = [(wallet.hotkey_str, wallet.hotkey.ss58_address, None)] else: - hotkeys = [(None, hotkey_ss58_address)] + hotkeys = [(None, hotkey_ss58_address, None)] - hotkey_names = {ss58: name for name, ss58 in hotkeys if name is not None} - hotkey_ss58s = [ss58 for _, ss58 in hotkeys] + hotkey_names = {ss58: name for name, ss58, _ in hotkeys if name is not None} + hotkey_ss58s = [item[1] for item in hotkeys] stake_info = [ stake for stake in stake_info if stake.hotkey_ss58 in hotkey_ss58s ] @@ -1058,7 +1058,7 @@ def _get_hotkeys_to_unstake( stake_infos: list, identities: dict, old_identities: dict, -) -> list[tuple[Optional[str], str]]: +) -> list[tuple[Optional[str], str, None]]: """Get list of hotkeys to unstake from based on input parameters. Args: @@ -1069,26 +1069,28 @@ def _get_hotkeys_to_unstake( exclude_hotkeys: List of hotkey names to exclude Returns: - List of tuples containing (hotkey_name, hotkey_ss58) pairs to unstake from + List of tuples containing (hotkey_name, hotkey_ss58, None) pairs to unstake from. The final None is important + for compatibility with the `_unstake_selection` function. """ if hotkey_ss58_address: print_verbose(f"Unstaking from ss58 ({hotkey_ss58_address})") - return [(None, hotkey_ss58_address)] + return [(None, hotkey_ss58_address, None)] if all_hotkeys: print_verbose("Unstaking from all hotkeys") all_hotkeys_ = get_hotkey_wallets_for_wallet(wallet=wallet) wallet_hotkeys = [ - (wallet.hotkey_str, wallet.hotkey.ss58_address) + (wallet.hotkey_str, wallet.hotkey.ss58_address, None) for wallet in all_hotkeys_ if wallet.hotkey_str not in exclude_hotkeys ] - wallet_hotkey_addresses = {addr for _, addr in wallet_hotkeys} + wallet_hotkey_addresses = {hk[1] for hk in wallet_hotkeys} chain_hotkeys = [ ( get_hotkey_identity(stake_info.hotkey_ss58, identities, old_identities), stake_info.hotkey_ss58, + None, ) for stake_info in stake_infos if ( @@ -1103,14 +1105,14 @@ def _get_hotkeys_to_unstake( result = [] for hotkey_identifier in include_hotkeys: if is_valid_ss58_address(hotkey_identifier): - result.append((None, hotkey_identifier)) + result.append((None, hotkey_identifier, None)) else: wallet_ = Wallet( name=wallet.name, path=wallet.path, hotkey=hotkey_identifier, ) - result.append((wallet_.hotkey_str, wallet_.hotkey.ss58_address)) + result.append((wallet_.hotkey_str, wallet_.hotkey.ss58_address, None)) return result # Only cli.config.wallet.hotkey is specified @@ -1118,7 +1120,7 @@ def _get_hotkeys_to_unstake( f"Unstaking from wallet: ({wallet.name}) from hotkey: ({wallet.hotkey_str})" ) assert wallet.hotkey is not None - return [(wallet.hotkey_str, wallet.hotkey.ss58_address)] + return [(wallet.hotkey_str, wallet.hotkey.ss58_address, None)] def _create_unstake_table( diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py index b885d8adf..66dda33c0 100644 --- a/tests/e2e_tests/test_unstaking.py +++ b/tests/e2e_tests/test_unstaking.py @@ -228,8 +228,10 @@ def test_unstaking(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--all-alpha", "--no-prompt", + "--verbose", ], ) + assert ( "✅ Finalized: Successfully unstaked all Alpha stakes" in unstake_alpha.stdout ) From 5fd11040203a809b2a5fb3544483884e09c0867f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 13 Mar 2025 10:22:44 -0700 Subject: [PATCH 07/11] Bumps version and changelog --- CHANGELOG.md | 7 +++++++ bittensor_cli/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1156596e2..85c142145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 9.1.4 /2025-03-13 + +## What's Changed +* Disk-Cache Async-Substrate-Interface Calls by @thewhaleking in https://github.com/opentensor/btcli/pull/368 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.1.3...v9.1.4 + ## 9.1.3 /2025-03-12 ## What's Changed diff --git a/bittensor_cli/version.py b/bittensor_cli/version.py index d5ce174d9..528f624ba 100644 --- a/bittensor_cli/version.py +++ b/bittensor_cli/version.py @@ -16,5 +16,5 @@ def version_as_int(version): return __version_as_int__ -__version__ = "9.1.3" +__version__ = "9.1.4" __version_as_int__ = version_as_int(__version__) diff --git a/pyproject.toml b/pyproject.toml index d9933b276..d7e557dae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.1.3" +version = "9.1.4" description = "Bittensor CLI" readme = "README.md" authors = [ From 814e3769551afeb7224c0710fabca270a5c0d92c Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Mar 2025 20:41:40 +0200 Subject: [PATCH 08/11] Converts COLOR_PALETTE dict to class with getitem property --- bittensor_cli/src/__init__.py | 162 +++++++++++------- bittensor_cli/src/commands/subnets/subnets.py | 2 +- 2 files changed, 105 insertions(+), 59 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 2b879097f..07c6eba09 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -721,64 +721,110 @@ class WalletValidationTypes(Enum): }, } -COLOR_PALETTE = { - "GENERAL": { - "HEADER": "#4196D6", # Light Blue - "LINKS": "#8CB9E9", # Sky Blue - "HINT": "#A2E5B8", # Mint Green - "COLDKEY": "#9EF5E4", # Aqua - "HOTKEY": "#ECC39D", # Light Orange/Peach - "SUBHEADING_MAIN": "#7ECFEC", # Light Cyan - "SUBHEADING": "#AFEFFF", # Pale Blue - "SUBHEADING_EXTRA_1": "#96A3C5", # Grayish Blue - "SUBHEADING_EXTRA_2": "#6D7BAF", # Slate Blue - "CONFIRMATION_Y_N_Q": "#EE8DF8", # Light Purple/Pink - "SYMBOL": "#E7CC51", # Gold - "BALANCE": "#4F91C6", # Medium Blue - "COST": "#53B5A0", # Teal - "SUCCESS": "#53B5A0", # Teal - "NETUID": "#CBA880", # Tan - "NETUID_EXTRA": "#DDD5A9", # Light Khaki - "TEMPO": "#67A3A5", # Grayish Teal - }, - "STAKE": { - "STAKE_AMOUNT": "#53B5A0", # Teal - "STAKE_ALPHA": "#53B5A0", # Teal - "STAKE_SWAP": "#67A3A5", # Grayish Teal - "TAO": "#4F91C6", # Medium Blue - "SLIPPAGE_TEXT": "#C25E7C", # Rose - "SLIPPAGE_PERCENT": "#E7B195", # Light Coral - "NOT_REGISTERED": "#EB6A6C", # Salmon Red - "EXTRA_1": "#D781BB", # Pink - }, - "POOLS": { - "TAO": "#4F91C6", # Medium Blue - "ALPHA_IN": "#D09FE9", # Light Purple - "ALPHA_OUT": "#AB7CC8", # Medium Purple - "RATE": "#F8D384", # Light Orange - "TAO_EQUIV": "#8CB9E9", # Sky Blue - "EMISSION": "#F8D384", # Light Orange - "EXTRA_1": "#CAA8FB", # Lavender - "EXTRA_2": "#806DAF", # Dark Purple - }, - "GREY": { - "GREY_100": "#F8F9FA", # Almost White - "GREY_200": "#F1F3F4", # Very Light Grey - "GREY_300": "#DBDDE1", # Light Grey - "GREY_400": "#BDC1C6", # Medium Light Grey - "GREY_500": "#5F6368", # Medium Grey - "GREY_600": "#2E3134", # Medium Dark Grey - "GREY_700": "#282A2D", # Dark Grey - "GREY_800": "#17181B", # Very Dark Grey - "GREY_900": "#0E1013", # Almost Black - "BLACK": "#000000", # Pure Black - }, - "SUDO": { - "HYPERPARAMETER": "#4F91C6", # Medium Blue - "VALUE": "#D09FE9", # Light Purple - "NORMALIZED": "#AB7CC8", # Medium Purple - }, -} + +class Gettable: + def __getitem__(self, item): + return getattr(self, item) + + +class ColorPalette(Gettable): + def __init__(self): + self.GENERAL = self.General() + self.STAKE = self.Stake() + self.POOLS = self.Pools() + self.GREY = self.Grey() + self.SUDO = self.Sudo() + # aliases + self.G = self.GENERAL + self.S = self.STAKE + self.P = self.POOLS + self.GR = self.GREY + self.SU = self.SUDO + + class General(Gettable): + HEADER = "#4196D6" # Light Blue + LINKS = "#8CB9E9" # Sky Blue + HINT = "#A2E5B8" # Mint Green + COLDKEY = "#9EF5E4" # Aqua + HOTKEY = "#ECC39D" # Light Orange/Peach + SUBHEADING_MAIN = "#7ECFEC" # Light Cyan + SUBHEADING = "#AFEFFF" # Pale Blue + SUBHEADING_EXTRA_1 = "#96A3C5" # Grayish Blue + SUBHEADING_EXTRA_2 = "#6D7BAF" # Slate Blue + CONFIRMATION_Y_N_Q = "#EE8DF8" # Light Purple/Pink + SYMBOL = "#E7CC51" # Gold + BALANCE = "#4F91C6" # Medium Blue + COST = "#53B5A0" # Teal + SUCCESS = "#53B5A0" # Teal + NETUID = "#CBA880" # Tan + NETUID_EXTRA = "#DDD5A9" # Light Khaki + TEMPO = "#67A3A5" # Grayish Teal + # aliases + CK = COLDKEY + HK = HOTKEY + SUBHEAD_MAIN = SUBHEADING_MAIN + SUBHEAD = SUBHEADING + SUBHEAD_EX_1 = SUBHEADING_EXTRA_1 + SUBHEAD_EX_2 = SUBHEADING_EXTRA_2 + SYM = SYMBOL + BAL = BALANCE + + class Stake(Gettable): + STAKE_AMOUNT = "#53B5A0" # Teal + STAKE_ALPHA = "#53B5A0" # Teal + STAKE_SWAP = "#67A3A5" # Grayish Teal + TAO = "#4F91C6" # Medium Blue + SLIPPAGE_TEXT = "#C25E7C" # Rose + SLIPPAGE_PERCENT = "#E7B195" # Light Coral + NOT_REGISTERED = "#EB6A6C" # Salmon Red + EXTRA_1 = "#D781BB" # Pink + # aliases + AMOUNT = STAKE_AMOUNT + ALPHA = STAKE_ALPHA + SWAP = STAKE_SWAP + + class Pools(Gettable): + TAO = "#4F91C6" # Medium Blue + ALPHA_IN = "#D09FE9" # Light Purple + ALPHA_OUT = "#AB7CC8" # Medium Purple + RATE = "#F8D384" # Light Orange + TAO_EQUIV = "#8CB9E9" # Sky Blue + EMISSION = "#F8D384" # Light Orange + EXTRA_1 = "#CAA8FB" # Lavender + EXTRA_2 = "#806DAF" # Dark Purple + + class Grey(Gettable): + GREY_100 = "#F8F9FA" # Almost White + GREY_200 = "#F1F3F4" # Very Light Grey + GREY_300 = "#DBDDE1" # Light Grey + GREY_400 = "#BDC1C6" # Medium Light Grey + GREY_500 = "#5F6368" # Medium Grey + GREY_600 = "#2E3134" # Medium Dark Grey + GREY_700 = "#282A2D" # Dark Grey + GREY_800 = "#17181B" # Very Dark Grey + GREY_900 = "#0E1013" # Almost Black + BLACK = "#000000" # Pure Black + # aliases + G_100 = GREY_100 + G_200 = GREY_200 + G_300 = GREY_300 + G_400 = GREY_400 + G_500 = GREY_500 + G_600 = GREY_600 + G_700 = GREY_700 + G_800 = GREY_800 + G_900 = GREY_900 + + class Sudo(Gettable): + HYPERPARAMETER = "#4F91C6" # Medium Blue + VALUE = "#D09FE9" # Light Purple + NORMALIZED = "#AB7CC8" # Medium Purple + # aliases + HYPERPARAM = HYPERPARAMETER + NORMAL = NORMALIZED + + +COLOR_PALETTE = ColorPalette() SUBNETS = { diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 1db23a4d0..c4fa93aaa 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -1417,7 +1417,7 @@ async def pow_register( use_cuda, dev_id, threads_per_block, - prompt: bool + prompt: bool, ): """Register neuron.""" From 7d3ee18ba617be8945e174d9ea9d6cfb5b13759b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 13 Mar 2025 20:52:38 +0200 Subject: [PATCH 09/11] Added examples --- bittensor_cli/cli.py | 39 +++++++++++++++++++---------------- bittensor_cli/src/__init__.py | 1 + 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e041c8076..5141ee01c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -25,7 +25,7 @@ WalletOptions as WO, WalletValidationTypes as WV, Constants, - COLOR_PALETTE, + COLORS, HYPERPARAMS, ) from bittensor_cli.version import __version__, __version_as_int__ @@ -341,8 +341,8 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in return None elif netuid is None and all_netuids is False: answer = Prompt.ask( - f"Enter the [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]netuid" - f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}] to use. Leave blank for all netuids", + f"Enter the [{COLORS.G.SUBHEAD_MAIN}]netuid" + f"[/{COLORS.G.SUBHEAD_MAIN}] to use. Leave blank for all netuids", default=None, show_default=False, ) @@ -950,7 +950,8 @@ def initialize_chain( elif self.config["network"]: self.subtensor = SubtensorInterface(self.config["network"]) console.print( - f"Using the specified network [{COLOR_PALETTE['GENERAL']['LINKS']}]{self.config['network']}[/{COLOR_PALETTE['GENERAL']['LINKS']}] from config" + f"Using the specified network [{COLORS.G.LINKS}]{self.config['network']}" + f"[/{COLORS.G.LINKS}] from config" ) else: self.subtensor = SubtensorInterface(defaults.subtensor.network) @@ -1523,7 +1524,7 @@ def wallet_ask( else: wallet_name = Prompt.ask( "Enter the [blue]wallet name[/blue]" - + f" [{COLOR_PALETTE['GENERAL']['HINT']} italic](Hint: You can set this with `btcli config set --wallet-name`)", + + f" [{COLORS.G.HINT} italic](Hint: You can set this with `btcli config set --wallet-name`)", default=defaults.wallet.name, ) @@ -2049,7 +2050,7 @@ def wallet_regen_coldkey( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + f"Enter the name of the [{COLORS.G.CK}]new wallet (coldkey)", default=defaults.wallet.name, ) @@ -2106,7 +2107,7 @@ def wallet_regen_coldkey_pub( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + f"Enter the name of the [{COLORS.G.CK}]new wallet (coldkey)", default=defaults.wallet.name, ) wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) @@ -2223,13 +2224,13 @@ def wallet_new_hotkey( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]wallet name", + f"Enter the [{COLORS.G.CK}]wallet name", default=defaults.wallet.name, ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", + f"Enter the name of the [{COLORS.G.HK}]new hotkey", default=defaults.wallet.hotkey, ) @@ -2285,7 +2286,7 @@ def wallet_new_coldkey( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + f"Enter the name of the [{COLORS.G.CK}]new wallet (coldkey)", default=defaults.wallet.name, ) @@ -2359,12 +2360,12 @@ def wallet_create_wallet( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + f"Enter the name of the [{COLORS.G.CK}]new wallet (coldkey)", default=defaults.wallet.name, ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( - f"Enter the the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", + f"Enter the the name of the [{COLORS.G.HK}]new hotkey", default=defaults.wallet.hotkey, ) @@ -2742,9 +2743,10 @@ def wallet_sign( self.verbosity_handler(quiet, verbose) if use_hotkey is None: use_hotkey = Confirm.ask( - f"Would you like to sign the transaction using your [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]?" - f"\n[Type [{COLOR_PALETTE['GENERAL']['HOTKEY']}]y[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] for [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f" and [{COLOR_PALETTE['GENERAL']['COLDKEY']}]n[/{COLOR_PALETTE['GENERAL']['COLDKEY']}] for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]] (default is [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey[/{COLOR_PALETTE['GENERAL']['COLDKEY']}])", + f"Would you like to sign the transaction using your [{COLORS.G.HK}]hotkey[/{COLORS.G.HK}]?" + f"\n[Type [{COLORS.G.HK}]y[/{COLORS.G.HK}] for [{COLORS.G.HK}]hotkey[/{COLORS.G.HK}]" + f" and [{COLORS.G.CK}]n[/{COLORS.G.CK}] for [{COLORS.G.CK}]coldkey[/{COLORS.G.CK}]] " + f"(default is [{COLORS.G.CK}]coldkey[/{COLORS.G.CK}])", default=False, ) @@ -3035,11 +3037,11 @@ def stake_add( raise typer.Exit() if netuid is not None: amount = FloatPrompt.ask( - f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake (TAO τ)" + f"Amount to [{COLORS.G.SUBHEAD_MAIN}]stake (TAO τ)" ) else: amount = FloatPrompt.ask( - f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake to each netuid (TAO τ)" + f"Amount to [{COLORS.G.SUBHEAD_MAIN}]stake to each netuid (TAO τ)" ) if amount <= 0: @@ -4112,7 +4114,8 @@ def sudo_set( if not param_value: if HYPERPARAMS.get(param_name): param_value = Prompt.ask( - f"Enter the new value for [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{param_name}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] in the VALUE column format" + f"Enter the new value for [{COLORS.G.SUBHEAD}]{param_name}[/{COLORS.G.SUBHEAD}] " + f"in the VALUE column format" ) else: param_value = None diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 07c6eba09..1ecd29a33 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -825,6 +825,7 @@ class Sudo(Gettable): COLOR_PALETTE = ColorPalette() +COLORS = COLOR_PALETTE SUBNETS = { From 9650e6573e202ec6d9d42107904e07c0bfd0c8ed Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 13 Mar 2025 12:11:50 -0700 Subject: [PATCH 10/11] Adds rate-tolerance alias --- bittensor_cli/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e041c8076..1b95e904c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -241,6 +241,7 @@ class Options: "--slippage", "--slippage-tolerance", "--tolerance", + "--rate-tolerance", help="Set the rate tolerance percentage for transactions (default: 0.05%).", callback=validate_rate_tolerance, ) From d2e9c18e098f33ace36a75dc0378724781329ccd Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 13 Mar 2025 13:33:43 -0700 Subject: [PATCH 11/11] Updates changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c142145..5a5da6e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ## What's Changed * Disk-Cache Async-Substrate-Interface Calls by @thewhaleking in https://github.com/opentensor/btcli/pull/368 +* COLOR_PALETTE refactor by @thewhaleking in https://github.com/opentensor/btcli/pull/386 +* Code Cleanup by @thewhaleking in https://github.com/opentensor/btcli/pull/381 +* Adds rate-tolerance alias by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/387 **Full Changelog**: https://github.com/opentensor/btcli/compare/v9.1.3...v9.1.4