Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
a43d8ee
Sets default interval hours for subnets price to 4, bc of rate limiting.
thewhaleking Jul 29, 2025
158acb5
Merge pull request #568 from opentensor/fix/thewhaleking/change-inter…
thewhaleking Jul 29, 2025
77d7a41
Added flag for current only
thewhaleking Jul 29, 2025
a2ac201
Changed `_determine_network` to its own method to use it without inst…
thewhaleking Jul 29, 2025
dbca387
Reverted _determine_network change.
thewhaleking Jul 29, 2025
92f54f3
Reverted _determine_network change.
thewhaleking Jul 29, 2025
3b7025d
Added JSON output support
thewhaleking Jul 29, 2025
d3d3950
Handle html
thewhaleking Jul 29, 2025
6ac4232
E2E test
thewhaleking Jul 29, 2025
88a4459
Merge pull request #569 from opentensor/feat/thewhaleking/current-s-p…
thewhaleking Jul 29, 2025
5e6992c
Reconfigure the asyncio runner to use a single event loop for everyth…
thewhaleking Aug 4, 2025
1bf5e15
Merge pull request #570 from opentensor/feat/thewhaleking/reconfigure…
thewhaleking Aug 4, 2025
de0259f
Shows account balance if transferring all.
thewhaleking Aug 5, 2025
5cc5b68
Merge pull request #571 from opentensor/fix/thewhaleking/show-amount-…
thewhaleking Aug 5, 2025
2d82c63
Allows for typer>=0.16 and Click 8.2+
thewhaleking Aug 5, 2025
42331cb
Allow custom config path using env var
thewhaleking Aug 5, 2025
2438701
Respect config sets
thewhaleking Aug 5, 2025
b121e67
Add more defaults.
thewhaleking Aug 5, 2025
ba7cca5
Removed some defaults
thewhaleking Aug 5, 2025
e4ba586
More defaults
thewhaleking Aug 5, 2025
6918241
Merge pull request #572 from opentensor/feat/thewhaleking/support-cli…
thewhaleking Aug 5, 2025
4524119
Removed some defaults
thewhaleking Aug 5, 2025
f03f1f8
Merge pull request #573 from opentensor/feat/thewhaleking/allow-custo…
thewhaleking Aug 5, 2025
cfc18e3
Added info about preinstalled macOS CPython.
thewhaleking Aug 5, 2025
7c7a000
Merge pull request #574 from opentensor/feat/thewhaleking/update-inst…
thewhaleking Aug 5, 2025
da471d0
Basic implementation
thewhaleking Aug 6, 2025
b8e6d79
Ruff
thewhaleking Aug 6, 2025
580b8a7
Added commands
thewhaleking Aug 6, 2025
b959fe3
Added commands
thewhaleking Aug 6, 2025
6d975c3
Error handling
thewhaleking Aug 6, 2025
75501bb
Improved text
thewhaleking Aug 6, 2025
9aacee8
Add test
thewhaleking Aug 6, 2025
ed21df7
Updated reqs. Will need to be fixed upstream first.
thewhaleking Aug 6, 2025
8dc3a8f
Update test
thewhaleking Aug 6, 2025
1c19e0f
Ruff
thewhaleking Aug 6, 2025
5e257b5
Specify click version
thewhaleking Aug 6, 2025
20814e1
Check version
thewhaleking Aug 6, 2025
14a4e7a
Merge pull request #576 from opentensor/feat/thewhaleking/click-8.2-a…
thewhaleking Aug 6, 2025
714cafb
New fn to retrieve wallet hotkey ss58
thewhaleking Aug 6, 2025
aa70fb3
Update some commands.
thewhaleking Aug 6, 2025
b6c5876
More commands.
thewhaleking Aug 6, 2025
68e5afc
More commands.
thewhaleking Aug 6, 2025
f40e8b5
More commands.
thewhaleking Aug 6, 2025
edfac73
Ruff
thewhaleking Aug 6, 2025
95a4af5
Think this is it
thewhaleking Aug 6, 2025
411fadd
Ruff…
thewhaleking Aug 6, 2025
5428d38
Merge branch 'staging' into feat/thewhaleking/regen-hotkey-pub
thewhaleking Aug 6, 2025
29cb6b9
Req
thewhaleking Aug 6, 2025
8d48198
Typo
thewhaleking Aug 6, 2025
63bfd0f
Merge pull request #575 from opentensor/feat/thewhaleking/regen-hotke…
thewhaleking Aug 6, 2025
53adefb
Changelog + version
thewhaleking Aug 6, 2025
6a7ea74
Merge pull request #577 from opentensor/changelog/9.10.0
thewhaleking Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 9.10.0 /2025-08-06
* Sets default interval hours for subnets price to 4, bc of rate limiting. by @thewhaleking in https://github.com/opentensor/btcli/pull/568
* Subnets Price --current + improvements by @thewhaleking in https://github.com/opentensor/btcli/pull/569
* Reconfig Asyncio Runner by @thewhaleking in https://github.com/opentensor/btcli/pull/570
* Show amount on `transfer --all` by @thewhaleking in https://github.com/opentensor/btcli/pull/571
* Allows for typer>=0.16 and Click 8.2+ by @thewhaleking in https://github.com/opentensor/btcli/pull/572
* BTCLI Config Updates by @thewhaleking in https://github.com/opentensor/btcli/pull/573
* Added info about preinstalled macOS CPython by @thewhaleking in https://github.com/opentensor/btcli/pull/574
* Click 8.2+/- compatibility by @thewhaleking in https://github.com/opentensor/btcli/pull/576
* New command: `btcli w regen-hotkeypub` by @thewhaleking in https://github.com/opentensor/btcli/pull/575

**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.9.0...v9.10.0

## 9.9.0 /2025-07-28
* Feat/wallet verify by @ibraheem-abe in https://github.com/opentensor/btcli/pull/561
* Improved speed of query_all_identities and fetch_coldkey_hotkey_identities by @thewhaleking in https://github.com/opentensor/btcli/pull/560
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ Installation steps are described below. For a full documentation on how to use `

## Install on macOS and Linux

You can install `btcli` on your local machine directly from source, PyPI, or Homebrew. **Make sure you verify your installation after you install**:
You can install `btcli` on your local machine directly from source, PyPI, or Homebrew.
**Make sure you verify your installation after you install**.

### For macOS users
Note that the macOS preinstalled CPython installation is compiled with LibreSSL instead of OpenSSL. There are a number
of issues with LibreSSL, and as such is not fully supported by the libraries used by btcli. Thus we highly recommend, if
you are using a Mac, to first install Python from [Homebrew](https://brew.sh/). Additionally, the Rust FFI bindings
[if installing from precompiled wheels (default)] require the Homebrew-installed OpenSSL pacakge. If you choose to use
the preinstalled Python version from macOS, things may not work completely.


### Install from [PyPI](https://pypi.org/project/bittensor/)
Expand Down
219 changes: 165 additions & 54 deletions bittensor_cli/cli.py

Large diffs are not rendered by default.

54 changes: 29 additions & 25 deletions bittensor_cli/src/bittensor/extrinsics/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
print_error,
unlock_key,
hex_to_bytes,
get_hotkey_pub_ss58,
)

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -490,7 +491,7 @@ async def register_extrinsic(

async def get_neuron_for_pubkey_and_subnet():
uid = await subtensor.query(
"SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
"SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
)
if uid is None:
return NeuronInfo.get_null_neuron()
Expand Down Expand Up @@ -525,7 +526,7 @@ async def get_neuron_for_pubkey_and_subnet():
if not Confirm.ask(
f"Continue Registration?\n"
f" hotkey [{COLOR_PALETTE.G.HK}]({wallet.hotkey_str})[/{COLOR_PALETTE.G.HK}]:"
f"\t[{COLOR_PALETTE.G.HK}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE.G.HK}]\n"
f"\t[{COLOR_PALETTE.G.HK}]{get_hotkey_pub_ss58(wallet)}[/{COLOR_PALETTE.G.HK}]\n"
f" coldkey [{COLOR_PALETTE.G.CK}]({wallet.name})[/{COLOR_PALETTE.G.CK}]:"
f"\t[{COLOR_PALETTE.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE.G.CK}]\n"
f" network:\t\t[{COLOR_PALETTE.G.LINKS}]{subtensor.network}[/{COLOR_PALETTE.G.LINKS}]\n"
Expand Down Expand Up @@ -577,7 +578,7 @@ async def get_neuron_for_pubkey_and_subnet():
if not pow_result:
# might be registered already on this subnet
is_registered = await is_hotkey_registered(
subtensor, netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address
subtensor, netuid=netuid, hotkey_ss58=get_hotkey_pub_ss58(wallet)
)
if is_registered:
err_console.print(
Expand All @@ -598,7 +599,7 @@ async def get_neuron_for_pubkey_and_subnet():
"block_number": pow_result.block_number,
"nonce": pow_result.nonce,
"work": [int(byte_) for byte_ in pow_result.seal],
"hotkey": wallet.hotkey.ss58_address,
"hotkey": get_hotkey_pub_ss58(wallet),
"coldkey": wallet.coldkeypub.ss58_address,
},
)
Expand Down Expand Up @@ -639,7 +640,7 @@ async def get_neuron_for_pubkey_and_subnet():
is_registered = await is_hotkey_registered(
subtensor,
netuid=netuid,
hotkey_ss58=wallet.hotkey.ss58_address,
hotkey_ss58=get_hotkey_pub_ss58(wallet),
)
if is_registered:
console.print(
Expand Down Expand Up @@ -704,7 +705,7 @@ async def burned_register_extrinsic(
spinner="aesthetic",
) as status:
my_uid = await subtensor.query(
"SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
"SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
)
block_hash = await subtensor.substrate.get_chain_head()

Expand Down Expand Up @@ -751,7 +752,7 @@ async def burned_register_extrinsic(
call_function="burned_register",
call_params={
"netuid": netuid,
"hotkey": wallet.hotkey.ss58_address,
"hotkey": get_hotkey_pub_ss58(wallet),
},
)
success, err_msg = await subtensor.sign_and_send_extrinsic(
Expand All @@ -773,10 +774,10 @@ async def burned_register_extrinsic(
reuse_block=False,
),
subtensor.get_netuids_for_hotkey(
wallet.hotkey.ss58_address, block_hash=block_hash
get_hotkey_pub_ss58(wallet), block_hash=block_hash
),
subtensor.query(
"SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
"SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
),
)

Expand Down Expand Up @@ -1146,7 +1147,7 @@ async def _block_solver(

timeout = 0.15 if cuda else 0.15
while netuid == -1 or not await is_hotkey_registered(
subtensor, netuid, wallet.hotkey.ss58_address
subtensor, netuid, get_hotkey_pub_ss58(wallet)
):
# Wait until a solver finds a solution
try:
Expand Down Expand Up @@ -1755,37 +1756,39 @@ async def swap_hotkey_extrinsic(
:return: Success
"""
block_hash = await subtensor.substrate.get_chain_head()
hk_ss58 = get_hotkey_pub_ss58(wallet)
netuids_registered = await subtensor.get_netuids_for_hotkey(
wallet.hotkey.ss58_address, block_hash=block_hash
hk_ss58, block_hash=block_hash
)
netuids_registered_new_hotkey = await subtensor.get_netuids_for_hotkey(
new_wallet.hotkey.ss58_address, block_hash=block_hash
hk_ss58, block_hash=block_hash
)

if netuid is not None and netuid not in netuids_registered:
err_console.print(
f":cross_mark: [red]Failed[/red]: Original hotkey {wallet.hotkey.ss58_address} is not registered on subnet {netuid}"
f":cross_mark: [red]Failed[/red]: Original hotkey {hk_ss58} is not registered on subnet {netuid}"
)
return False

elif not len(netuids_registered) > 0:
err_console.print(
f"Original hotkey [dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] is not registered on any subnet. "
f"Original hotkey [dark_orange]{hk_ss58}[/dark_orange] is not registered on any subnet. "
f"Please register and try again"
)
return False

new_hk_ss58 = get_hotkey_pub_ss58(new_wallet)
if netuid is not None:
if netuid in netuids_registered_new_hotkey:
err_console.print(
f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} "
f":cross_mark: [red]Failed[/red]: New hotkey {new_hk_ss58} "
f"is already registered on subnet {netuid}"
)
return False
else:
if len(netuids_registered_new_hotkey) > 0:
err_console.print(
f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} "
f":cross_mark: [red]Failed[/red]: New hotkey {new_hk_ss58} "
f"is already registered on subnet(s) {netuids_registered_new_hotkey}"
)
return False
Expand All @@ -1798,28 +1801,28 @@ async def swap_hotkey_extrinsic(
if netuid is not None:
confirm_message = (
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n"
f"[dark_orange]{hk_ss58} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
f"[dark_orange]{new_hk_ss58} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n"
"This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]"
)
else:
confirm_message = (
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n"
f"[dark_orange]{hk_ss58} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
f"[dark_orange]{new_hk_ss58} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n"
"This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]"
)

if not Confirm.ask(confirm_message):
return False
print_verbose(
f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address} - {wallet.hotkey_str}) with "
f"{new_wallet.name}'s hotkey ({new_wallet.hotkey.ss58_address} - {new_wallet.hotkey_str})"
f"Swapping {wallet.name}'s hotkey ({hk_ss58} - {wallet.hotkey_str}) with "
f"{new_wallet.name}'s hotkey ({new_hk_ss58} - {new_wallet.hotkey_str})"
)
with console.status(":satellite: Swapping hotkeys...", spinner="aesthetic"):
call_params = {
"hotkey": wallet.hotkey.ss58_address,
"new_hotkey": new_wallet.hotkey.ss58_address,
"hotkey": hk_ss58,
"new_hotkey": new_hk_ss58,
"netuid": netuid,
}

Expand All @@ -1832,7 +1835,8 @@ async def swap_hotkey_extrinsic(

if success:
console.print(
f"Hotkey {wallet.hotkey.ss58_address} ({wallet.hotkey_str}) swapped for new hotkey: {new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})"
f"Hotkey {hk_ss58} ({wallet.hotkey_str}) swapped for new hotkey: "
f"{new_hk_ss58} ({new_wallet.hotkey_str})"
)
return True
else:
Expand Down
11 changes: 6 additions & 5 deletions bittensor_cli/src/bittensor/extrinsics/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
print_verbose,
format_error_message,
unlock_key,
get_hotkey_pub_ss58,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -310,7 +311,7 @@ async def root_register_extrinsic(

print_verbose(f"Checking if hotkey ({wallet.hotkey_str}) is registered on root")
is_registered = await is_hotkey_registered(
subtensor, netuid=0, hotkey_ss58=wallet.hotkey.ss58_address
subtensor, netuid=0, hotkey_ss58=get_hotkey_pub_ss58(wallet)
)
if is_registered:
console.print(
Expand All @@ -322,7 +323,7 @@ async def root_register_extrinsic(
call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="root_register",
call_params={"hotkey": wallet.hotkey.ss58_address},
call_params={"hotkey": get_hotkey_pub_ss58(wallet)},
)
success, err_msg = await subtensor.sign_and_send_extrinsic(
call,
Expand All @@ -341,7 +342,7 @@ async def root_register_extrinsic(
uid = await subtensor.query(
module="SubtensorModule",
storage_function="Uids",
params=[0, wallet.hotkey.ss58_address],
params=[0, get_hotkey_pub_ss58(wallet)],
)
if uid is not None:
console.print(
Expand Down Expand Up @@ -391,7 +392,7 @@ async def _do_set_weights():
"weights": weight_vals,
"netuid": 0,
"version_key": version_key,
"hotkey": wallet.hotkey.ss58_address,
"hotkey": get_hotkey_pub_ss58(wallet),
},
)
# Period dictates how long the extrinsic will stay as part of waiting pool
Expand All @@ -415,7 +416,7 @@ async def _do_set_weights():
return False, await response.error_message

my_uid = await subtensor.query(
"SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address]
"SubtensorModule", "Uids", [0, get_hotkey_pub_ss58(wallet)]
)

if my_uid is None:
Expand Down
2 changes: 1 addition & 1 deletion bittensor_cli/src/bittensor/extrinsics/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ async def do_transfer() -> tuple[bool, str, str]:
if prompt:
if not Confirm.ask(
"Do you want to transfer:[bold white]\n"
f" amount: [bright_cyan]{amount}[/bright_cyan]\n"
f" amount: [bright_cyan]{amount if not transfer_all else account_balance}[/bright_cyan]\n"
f" from: [light_goldenrod2]{wallet.name}[/light_goldenrod2] : "
f"[bright_magenta]{wallet.coldkey.ss58_address}\n[/bright_magenta]"
f" to: [bright_magenta]{destination}[/bright_magenta]\n for fee: [bright_cyan]{fee}[/bright_cyan]"
Expand Down
3 changes: 2 additions & 1 deletion bittensor_cli/src/bittensor/subtensor_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
validate_chain_endpoint,
u16_normalized_float,
U16_MAX,
get_hotkey_pub_ss58,
)

SubstrateClass = (
Expand Down Expand Up @@ -666,7 +667,7 @@ async def filter_netuids_by_registered_hotkeys(
for sublist in await asyncio.gather(
*[
self.get_netuids_for_hotkey(
wallet.hotkey.ss58_address,
get_hotkey_pub_ss58(wallet),
reuse_block=reuse_block,
block_hash=block_hash,
)
Expand Down
14 changes: 13 additions & 1 deletion bittensor_cli/src/bittensor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def get_hotkey_wallets_for_wallet(
(exists := hotkey_for_name.hotkey_file.exists_on_device())
and not hotkey_for_name.hotkey_file.is_encrypted()
# and hotkey_for_name.coldkeypub.ss58_address
and hotkey_for_name.hotkey.ss58_address
and get_hotkey_pub_ss58(hotkey_for_name)
):
hotkey_wallets.append(hotkey_for_name)
elif (
Expand Down Expand Up @@ -1431,3 +1431,15 @@ def blocks_to_duration(blocks: int) -> str:
results.append(f"{unit_count}{unit}")
# Return only the first two non-zero units
return " ".join(results[:2]) or "0s"


def get_hotkey_pub_ss58(wallet: Wallet) -> str:
"""
Helper fn to retrieve the hotkeypub ss58 of a wallet that may have been created before
bt-wallet 3.1.1 and thus not have a wallet hotkeypub. In this case, it will return the hotkey
SS58.
"""
try:
return wallet.hotkeypub.ss58_address
except KeyFileError:
return wallet.hotkey.ss58_address
7 changes: 4 additions & 3 deletions bittensor_cli/src/commands/stake/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
print_verbose,
unlock_key,
json_console,
get_hotkey_pub_ss58,
)
from bittensor_wallet import Wallet

Expand Down Expand Up @@ -552,7 +553,7 @@ def _get_hotkeys_to_stake_to(
# Stake to all hotkeys except excluded ones
all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
return [
(wallet.hotkey_str, wallet.hotkey.ss58_address)
(wallet.hotkey_str, get_hotkey_pub_ss58(wallet))
for wallet in all_hotkeys_
if wallet.hotkey_str not in (exclude_hotkeys or [])
]
Expand All @@ -572,7 +573,7 @@ def _get_hotkeys_to_stake_to(
name=wallet.name,
hotkey=hotkey_ss58_or_hotkey_name,
)
hotkeys.append((wallet_.hotkey_str, wallet_.hotkey.ss58_address))
hotkeys.append((wallet_.hotkey_str, get_hotkey_pub_ss58(wallet_)))

return hotkeys

Expand All @@ -581,7 +582,7 @@ def _get_hotkeys_to_stake_to(
f"Staking to hotkey: ({wallet.hotkey_str}) in wallet: ({wallet.name})"
)
assert wallet.hotkey is not None
return [(None, wallet.hotkey.ss58_address)]
return [(None, get_hotkey_pub_ss58(wallet))]


def _define_stake_table(
Expand Down
Loading
Loading