Skip to content

Commit ded01cf

Browse files
authored
Merge pull request #507 from opentensor/release/9.7.0
Release/9.7.0
2 parents 81e8c48 + 5f401aa commit ded01cf

File tree

6 files changed

+81
-21
lines changed

6 files changed

+81
-21
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 9.7.0/2025-06-16
4+
5+
## What's Changed
6+
* Add `SKIP_PULL` variable for conftest.py by @basfroman in https://github.com/opentensor/btcli/pull/502
7+
* Feat: Adds netuid support in swap_hotkeys by @ibraheem-abe in https://github.com/opentensor/btcli/pull/505
8+
9+
**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.6.0...v9.7.0
10+
311
## 9.6.0/2025-06-12
412

513
## What's Changed

bittensor_cli/cli.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,8 @@ def wallet_swap_hotkey(
18971897
wallet_name: Optional[str] = Options.wallet_name,
18981898
wallet_path: Optional[str] = Options.wallet_path,
18991899
wallet_hotkey: Optional[str] = Options.wallet_hotkey,
1900+
netuid: Optional[int] = Options.netuid_not_req,
1901+
all_netuids: bool = Options.all_netuids,
19001902
network: Optional[list[str]] = Options.network,
19011903
destination_hotkey_name: Optional[str] = typer.Argument(
19021904
None, help="Destination hotkey name."
@@ -1917,12 +1919,14 @@ def wallet_swap_hotkey(
19171919
19181920
- Make sure that your original key pair (coldkeyA, hotkeyA) is already registered.
19191921
- Make sure that you use a newly created hotkeyB in this command. A hotkeyB that is already registered cannot be used in this command.
1922+
- You can specify the netuid for which you want to swap the hotkey for. If it is not defined, the swap will be initiated for all subnets.
19201923
- Finally, note that this command requires a fee of 1 TAO for recycling and this fee is taken from your wallet (coldkeyA).
19211924
19221925
EXAMPLE
19231926
1924-
[green]$[/green] btcli wallet swap_hotkey destination_hotkey_name --wallet-name your_wallet_name --wallet-hotkey original_hotkey
1927+
[green]$[/green] btcli wallet swap_hotkey destination_hotkey_name --wallet-name your_wallet_name --wallet-hotkey original_hotkey --netuid 1
19251928
"""
1929+
netuid = get_optional_netuid(netuid, all_netuids)
19261930
self.verbosity_handler(quiet, verbose, json_output)
19271931
original_wallet = self.wallet_ask(
19281932
wallet_name,
@@ -1946,7 +1950,7 @@ def wallet_swap_hotkey(
19461950
self.initialize_chain(network)
19471951
return self._run_command(
19481952
wallets.swap_hotkey(
1949-
original_wallet, new_wallet, self.subtensor, prompt, json_output
1953+
original_wallet, new_wallet, self.subtensor, netuid, prompt, json_output
19501954
)
19511955
)
19521956

bittensor_cli/src/bittensor/extrinsics/registration.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,7 +1611,8 @@ def _update_curr_block(
16111611
"""
16121612
Update the current block data with the provided block information and difficulty.
16131613
1614-
This function updates the current block and its difficulty in a thread-safe manner. It sets the current block
1614+
This function updates the current block
1615+
and its difficulty in a thread-safe manner. It sets the current block
16151616
number, hashes the block with the hotkey, updates the current block bytes, and packs the difficulty.
16161617
16171618
:param curr_diff: Shared array to store the current difficulty.
@@ -1745,6 +1746,7 @@ async def swap_hotkey_extrinsic(
17451746
subtensor: "SubtensorInterface",
17461747
wallet: Wallet,
17471748
new_wallet: Wallet,
1749+
netuid: Optional[int] = None,
17481750
prompt: bool = False,
17491751
) -> bool:
17501752
"""
@@ -1756,43 +1758,81 @@ async def swap_hotkey_extrinsic(
17561758
netuids_registered = await subtensor.get_netuids_for_hotkey(
17571759
wallet.hotkey.ss58_address, block_hash=block_hash
17581760
)
1759-
if not len(netuids_registered) > 0:
1761+
netuids_registered_new_hotkey = await subtensor.get_netuids_for_hotkey(
1762+
new_wallet.hotkey.ss58_address, block_hash=block_hash
1763+
)
1764+
1765+
if netuid is not None and netuid not in netuids_registered:
1766+
err_console.print(
1767+
f":cross_mark: [red]Failed[/red]: Original hotkey {wallet.hotkey.ss58_address} is not registered on subnet {netuid}"
1768+
)
1769+
return False
1770+
1771+
elif not len(netuids_registered) > 0:
17601772
err_console.print(
1761-
f"Destination hotkey [dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange] is not registered. "
1773+
f"Original hotkey [dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] is not registered on any subnet. "
17621774
f"Please register and try again"
17631775
)
17641776
return False
17651777

1778+
if netuid is not None:
1779+
if netuid in netuids_registered_new_hotkey:
1780+
err_console.print(
1781+
f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} "
1782+
f"is already registered on subnet {netuid}"
1783+
)
1784+
return False
1785+
else:
1786+
if len(netuids_registered_new_hotkey) > 0:
1787+
err_console.print(
1788+
f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} "
1789+
f"is already registered on subnet(s) {netuids_registered_new_hotkey}"
1790+
)
1791+
return False
1792+
17661793
if not unlock_key(wallet).success:
17671794
return False
17681795

17691796
if prompt:
17701797
# Prompt user for confirmation.
1771-
if not Confirm.ask(
1772-
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
1773-
f"[dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] with hotkey \n\t"
1774-
f"[dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange]\n"
1775-
"This operation will cost [bold cyan]1 TAO t (recycled)[/bold cyan]"
1776-
):
1798+
if netuid is not None:
1799+
confirm_message = (
1800+
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
1801+
f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
1802+
f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n"
1803+
"This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]"
1804+
)
1805+
else:
1806+
confirm_message = (
1807+
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
1808+
f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
1809+
f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n"
1810+
"This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]"
1811+
)
1812+
1813+
if not Confirm.ask(confirm_message):
17771814
return False
17781815
print_verbose(
1779-
f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address}) with "
1780-
f"{new_wallet.name}s hotkey ({new_wallet.hotkey.ss58_address})"
1816+
f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address} - {wallet.hotkey_str}) with "
1817+
f"{new_wallet.name}'s hotkey ({new_wallet.hotkey.ss58_address} - {new_wallet.hotkey_str})"
17811818
)
17821819
with console.status(":satellite: Swapping hotkeys...", spinner="aesthetic"):
1820+
call_params = {
1821+
"hotkey": wallet.hotkey.ss58_address,
1822+
"new_hotkey": new_wallet.hotkey.ss58_address,
1823+
"netuid": netuid,
1824+
}
1825+
17831826
call = await subtensor.substrate.compose_call(
17841827
call_module="SubtensorModule",
17851828
call_function="swap_hotkey",
1786-
call_params={
1787-
"hotkey": wallet.hotkey.ss58_address,
1788-
"new_hotkey": new_wallet.hotkey.ss58_address,
1789-
},
1829+
call_params=call_params,
17901830
)
17911831
success, err_msg = await subtensor.sign_and_send_extrinsic(call, wallet)
17921832

17931833
if success:
17941834
console.print(
1795-
f"Hotkey {wallet.hotkey} swapped for new hotkey: {new_wallet.hotkey}"
1835+
f"Hotkey {wallet.hotkey.ss58_address} ({wallet.hotkey_str}) swapped for new hotkey: {new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})"
17961836
)
17971837
return True
17981838
else:

bittensor_cli/src/commands/wallets.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,7 @@ async def swap_hotkey(
16321632
original_wallet: Wallet,
16331633
new_wallet: Wallet,
16341634
subtensor: SubtensorInterface,
1635+
netuid: Optional[int],
16351636
prompt: bool,
16361637
json_output: bool,
16371638
):
@@ -1640,6 +1641,7 @@ async def swap_hotkey(
16401641
subtensor,
16411642
original_wallet,
16421643
new_wallet,
1644+
netuid=netuid,
16431645
prompt=prompt,
16441646
)
16451647
if json_output:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "bittensor-cli"
7-
version = "9.6.0"
7+
version = "9.7.0"
88
description = "Bittensor CLI"
99
readme = "README.md"
1010
authors = [

tests/e2e_tests/conftest.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,21 @@ def docker_runner(params):
107107
"""Starts a Docker container before tests and gracefully terminates it after."""
108108

109109
def is_docker_running():
110-
"""Check if Docker has been run."""
110+
"""Check if Docker is running and optionally skip pulling the image."""
111111
try:
112112
subprocess.run(
113113
["docker", "info"],
114114
stdout=subprocess.DEVNULL,
115115
stderr=subprocess.DEVNULL,
116116
check=True,
117117
)
118-
subprocess.run(["docker", "pull", LOCALNET_IMAGE_NAME], check=True)
118+
119+
skip_pull = os.getenv("SKIP_PULL", "0") == "1"
120+
if not skip_pull:
121+
subprocess.run(["docker", "pull", LOCALNET_IMAGE_NAME], check=True)
122+
else:
123+
print(f"[SKIP_PULL=1] Skipping 'docker pull {LOCALNET_IMAGE_NAME}'")
124+
119125
return True
120126
except subprocess.CalledProcessError:
121127
return False

0 commit comments

Comments
 (0)