Skip to content

Commit 0b88eaa

Browse files
committed
Merge branch 'staging' into feat/thewhaleking/ensure-network-local-used-e2e
2 parents df661ba + 0cb20e3 commit 0b88eaa

File tree

13 files changed

+150
-23
lines changed

13 files changed

+150
-23
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,33 @@
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+
11+
## 9.6.0/2025-06-12
12+
13+
## What's Changed
14+
* Allows for staking to multiple netuids in one btcli command by @thewhaleking in https://github.com/opentensor/btcli/pull/481
15+
* improve stake add json output by @thewhaleking in https://github.com/opentensor/btcli/pull/482
16+
* Apply bittensor error formatting to btcli by @thewhaleking in https://github.com/opentensor/btcli/pull/483
17+
* Add Yuma3 Enabled for Sudo Set/Get by @thewhaleking in https://github.com/opentensor/btcli/pull/487
18+
* Adds `alpha_sigmoid_steepness` call for hyperparams set/get by @thewhaleking in https://github.com/opentensor/btcli/pull/488
19+
* unstaking test fix by @thewhaleking in https://github.com/opentensor/btcli/pull/489
20+
* Merge issue: 488 by @thewhaleking in https://github.com/opentensor/btcli/pull/490
21+
* subnets check-start formatting blocks by @thewhaleking in https://github.com/opentensor/btcli/pull/491
22+
* Str vs Tuple by @thewhaleking in https://github.com/opentensor/btcli/pull/492
23+
* Add Homebrew Install to README by @thewhaleking in https://github.com/opentensor/btcli/pull/493
24+
* Update staking test for new subtensor by @thewhaleking in https://github.com/opentensor/btcli/pull/494
25+
26+
27+
**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.5.1...v9.6.0
28+
329
## 9.5.1 /2025-06-02
30+
431
## What's Changed
532
* Declare templates in MANIFEST and include package data by @thewhaleking in https://github.com/opentensor/btcli/pull/477
633

bittensor_cli/cli.py

Lines changed: 10 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

@@ -5021,6 +5025,7 @@ def subnets_create(
50215025
description: Optional[str] = typer.Option(
50225026
None, "--description", help="Description"
50235027
),
5028+
logo_url: Optional[str] = typer.Option(None, "--logo-url", help="Logo URL"),
50245029
additional_info: Optional[str] = typer.Option(
50255030
None, "--additional-info", help="Additional information"
50265031
),
@@ -5063,6 +5068,7 @@ def subnets_create(
50635068
subnet_url=subnet_url,
50645069
discord=discord,
50655070
description=description,
5071+
logo_url=logo_url,
50665072
additional=additional_info,
50675073
)
50685074
self._run_command(
@@ -5186,6 +5192,7 @@ def subnets_set_identity(
51865192
description: Optional[str] = typer.Option(
51875193
None, "--description", help="Description"
51885194
),
5195+
logo_url: Optional[str] = typer.Option(None, "--logo-url", help="Logo URL"),
51895196
additional_info: Optional[str] = typer.Option(
51905197
None, "--additional-info", help="Additional information"
51915198
),
@@ -5237,6 +5244,7 @@ def subnets_set_identity(
52375244
subnet_url=subnet_url,
52385245
discord=discord,
52395246
description=description,
5247+
logo_url=logo_url,
52405248
additional=additional_info,
52415249
)
52425250

bittensor_cli/src/bittensor/chain_data.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ class SubnetIdentity(InfoBase):
630630
subnet_url: str
631631
discord: str
632632
description: str
633+
logo_url: str
633634
additional: str
634635

635636
@classmethod
@@ -641,6 +642,7 @@ def _fix_decoded(cls, decoded: dict) -> "SubnetIdentity":
641642
subnet_url=bytes(decoded["subnet_url"]).decode(),
642643
discord=bytes(decoded["discord"]).decode(),
643644
description=bytes(decoded["description"]).decode(),
645+
logo_url=bytes(decoded["logo_url"]).decode(),
644646
additional=bytes(decoded["additional"]).decode(),
645647
)
646648

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/bittensor/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,7 @@ def prompt_for_subnet_identity(
11671167
subnet_url: Optional[str],
11681168
discord: Optional[str],
11691169
description: Optional[str],
1170+
logo_url: Optional[str],
11701171
additional: Optional[str],
11711172
):
11721173
"""
@@ -1227,6 +1228,13 @@ def prompt_for_subnet_identity(
12271228
lambda x: x and len(x.encode("utf-8")) > 1024,
12281229
"[red]Error:[/red] Description must be <= 1024 bytes.",
12291230
),
1231+
(
1232+
"logo_url",
1233+
"[blue]Logo URL [dim](optional)[/blue]",
1234+
logo_url,
1235+
lambda x: x and len(x.encode("utf-8")) > 1024,
1236+
"[red]Error:[/red] Logo URL must be <= 1024 bytes.",
1237+
),
12301238
(
12311239
"additional",
12321240
"[blue]Additional information [dim](optional)[/blue]",

bittensor_cli/src/commands/subnets/subnets.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ async def _find_event_attributes_in_extrinsic_receipt(
140140
"description": subnet_identity["description"].encode()
141141
if subnet_identity.get("description")
142142
else b"",
143+
"logo_url": subnet_identity["logo_url"].encode()
144+
if subnet_identity.get("logo_url")
145+
else b"",
143146
"additional": subnet_identity["additional"].encode()
144147
if subnet_identity.get("additional")
145148
else b"",
@@ -2207,6 +2210,7 @@ async def set_identity(
22072210
"subnet_url": subnet_identity.get("subnet_url", ""),
22082211
"discord": subnet_identity.get("discord", ""),
22092212
"description": subnet_identity.get("description", ""),
2213+
"logo_url": subnet_identity.get("logo_url", ""),
22102214
"additional": subnet_identity.get("additional", ""),
22112215
}
22122216

@@ -2252,6 +2256,7 @@ async def set_identity(
22522256
"subnet_url",
22532257
"discord",
22542258
"description",
2259+
"logo_url",
22552260
"additional",
22562261
]:
22572262
value = getattr(identity, key, None)
@@ -2301,6 +2306,7 @@ async def get_identity(
23012306
"subnet_url",
23022307
"discord",
23032308
"description",
2309+
"logo_url",
23042310
"additional",
23052311
]:
23062312
value = getattr(identity, key, None)

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.5.1"
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

tests/e2e_tests/test_staking_sudo.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def test_staking(local_chain, wallet_setup):
8686
"A test subnet for e2e testing",
8787
"--additional-info",
8888
"Created by Alice",
89+
"--logo-url",
90+
"https://testsubnet.com/logo.png",
8991
"--no-prompt",
9092
"--json-output",
9193
],
@@ -121,6 +123,8 @@ def test_staking(local_chain, wallet_setup):
121123
"A test subnet for e2e testing",
122124
"--additional-info",
123125
"Created by Alice",
126+
"--logo-url",
127+
"https://testsubnet.com/logo.png",
124128
"--no-prompt",
125129
"--json-output",
126130
],
@@ -198,6 +202,8 @@ def test_staking(local_chain, wallet_setup):
198202
sn_discord := "alice#1234",
199203
"--description",
200204
sn_description := "A test subnet for e2e testing",
205+
"--logo-url",
206+
sn_logo_url := "https://testsubnet.com/logo.png",
201207
"--additional-info",
202208
sn_add_info := "Created by Alice",
203209
"--json-output",
@@ -225,6 +231,7 @@ def test_staking(local_chain, wallet_setup):
225231
assert get_identity_output["subnet_url"] == sn_url
226232
assert get_identity_output["discord"] == sn_discord
227233
assert get_identity_output["description"] == sn_description
234+
assert get_identity_output["logo_url"] == sn_logo_url
228235
assert get_identity_output["additional"] == sn_add_info
229236

230237
# Start emissions on SNs

0 commit comments

Comments
 (0)