Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9fbb88c
add `unstake_all_extrinsic`s
May 28, 2025
3413106
add extrinsic calls in Subtensor classes
May 28, 2025
eaf3073
remove `allow_partial_unstaking` from `unstake_all`
May 28, 2025
2eeb2db
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jun 6, 2025
b14fee3
Merge branch 'staging' into feat/roman/unstake_all
Jun 17, 2025
648bf17
fix loging message format
Jun 17, 2025
9e143b8
add unstake_all to SubtensorApi
Jun 17, 2025
054b4fa
add `unstaking_all_limit_extrinsic` to extrinsic sub-package
Jun 17, 2025
9860c86
update subtensor.unstake_all logic (safety/unsafety)
Jun 17, 2025
792e669
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jun 18, 2025
fdc9f58
docstrings + TODOs
Jun 18, 2025
5d007fd
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jun 18, 2025
fb6cca4
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jun 18, 2025
f020bb7
Merge branch 'staging' into feat/roman/unstake_all
Jun 23, 2025
2be2ae6
update unstake_all_extrinsic regarding chain's changes.
Jun 23, 2025
b78d442
update unstake_all calls.
Jun 23, 2025
234d467
Optional
Jun 23, 2025
43a8719
update docstrings
Jun 23, 2025
1b00d57
add e2e tests (3 scenarios)
Jun 23, 2025
d710dd9
add unit tests for subtensor calls
Jun 23, 2025
ab3c9df
remove raise error argument from call
Jun 23, 2025
558f545
add extrinsics unit tests
Jun 23, 2025
1ca7ba0
improved docstrings examples
Jun 23, 2025
6529507
fix flaky behavior in test
Jun 23, 2025
c432f86
fix flaky behavior in test
Jun 23, 2025
9c0ddde
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jun 24, 2025
445ea9f
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jun 24, 2025
b60c5ad
weird, but ruff
Jun 25, 2025
edeb75d
Merge branch 'staging' into feat/roman/unstake_all
basfroman Jul 1, 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
85 changes: 85 additions & 0 deletions bittensor/core/async_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
)
from bittensor.core.extrinsics.asyncex.transfer import transfer_extrinsic
from bittensor.core.extrinsics.asyncex.unstaking import (
unstake_all_extrinsic,
unstake_extrinsic,
unstake_multiple_extrinsic,
)
Expand Down Expand Up @@ -4602,6 +4603,90 @@ async def unstake(
unstake_all=unstake_all,
)

async def unstake_all(
self,
wallet: "Wallet",
hotkey: str,
netuid: int,
rate_tolerance: Optional[float] = 0.005,
wait_for_inclusion: bool = True,
wait_for_finalization: bool = False,
period: Optional[int] = None,
) -> tuple[bool, str]:
"""Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network.

Arguments:
wallet: The wallet of the stake owner.
hotkey: The SS58 address of the hotkey to unstake from.
netuid: The unique identifier of the subnet.
rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum
price decrease. If not passed (None), then unstaking goes without price limit. Default is 0.005.
wait_for_inclusion: Waits for the transaction to be included in a block. Default is `True`.
wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `False`.
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
transaction is not included in a block within that number of blocks, it will expire and be rejected. You
can think of it as an expiration date for the transaction. Default is `None`.

Returns:
tuple[bool, str]:
A tuple containing:
- `True` and a success message if the unstake operation succeeded;
- `False` and an error message otherwise.

Example:
# If you would like to unstake all stakes in all subnets safely, use default `rate_tolerance` or pass your
value:
import bittensor as bt

subtensor = bt.AsyncSubtensor()
wallet = bt.Wallet("my_wallet")
netuid = 14
hotkey = "5%SOME_HOTKEY_WHERE_IS_YOUR_STAKE_NOW%"

wallet_stakes = await subtensor.get_stake_info_for_coldkey(coldkey_ss58=wallet.coldkey.ss58_address)

for stake in wallet_stakes:
result = await subtensor.unstake_all(
wallet=wallet,
hotkey_ss58=stake.hotkey_ss58,
netuid=stake.netuid,
)
print(result)

# If you would like to unstake all stakes in all subnets unsafely, use `rate_tolerance=None`:
import bittensor as bt

subtensor = bt.AsyncSubtensor()
wallet = bt.Wallet("my_wallet")
netuid = 14
hotkey = "5%SOME_HOTKEY_WHERE_IS_YOUR_STAKE_NOW%"

wallet_stakes = await subtensor.get_stake_info_for_coldkey(coldkey_ss58=wallet.coldkey.ss58_address)

for stake in wallet_stakes:
result = await subtensor.unstake_all(
wallet=wallet,
hotkey_ss58=stake.hotkey_ss58,
netuid=stake.netuid,
rate_tolerance=None,
)
print(result)
"""
if netuid != 0:
logging.debug(
f"Unstaking without Alpha price control from subnet [blue]#{netuid}[/blue]."
)
return await unstake_all_extrinsic(
subtensor=self,
wallet=wallet,
hotkey=hotkey,
netuid=netuid,
rate_tolerance=rate_tolerance,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
period=period,
)

async def unstake_multiple(
self,
wallet: "Wallet",
Expand Down
122 changes: 95 additions & 27 deletions bittensor/core/extrinsics/asyncex/unstaking.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,28 @@ async def unstake_extrinsic(
"""Removes stake into the wallet coldkey from the specified hotkey ``uid``.

Args:
subtensor (bittensor.core.async_subtensor.AsyncSubtensor): AsyncSubtensor instance.
wallet (bittensor_wallet.Wallet): Bittensor wallet object.
hotkey_ss58 (Optional[str]): The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey
is used.
netuid (Optional[int]): The subnet uid to unstake from.
amount (Union[Balance, float]): Amount to stake as Bittensor balance, or ``float`` interpreted as Tao.
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or
returns ``False`` if the extrinsic fails to enter the block within the timeout.
wait_for_finalization (bool): 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.
subtensor: AsyncSubtensor instance.
wallet: Bittensor wallet object.
hotkey_ss58: The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey is used.
netuid: The subnet uid to unstake from.
amount: Amount to stake as Bittensor balance, or ``float`` interpreted as Tao.
wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns
``False`` if the extrinsic fails to enter the block within the timeout.
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.
safe_staking: If true, enables price safety checks
allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded
rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%)
period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted.
If the transaction is not included in a block within that number of blocks, it will expire and be rejected.
You can think of it as an expiration date for the transaction.
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
transaction is not included in a block within that number of blocks, it will expire and be rejected. You can
think of it as an expiration date for the transaction.
unstake_all: If true, unstakes all tokens. Default is ``False``.

Returns:
success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for
finalization / inclusion, the response is ``True``.
tuple[bool, str]:
A tuple containing:
- `True` and a success message if the unstake operation succeeded;
- `False` and an error message otherwise.
"""
if amount and unstake_all:
raise ValueError("Cannot specify both `amount` and `unstake_all`.")
Expand Down Expand Up @@ -200,6 +201,71 @@ async def unstake_extrinsic(
return False


async def unstake_all_extrinsic(
subtensor: "AsyncSubtensor",
wallet: "Wallet",
hotkey: str,
netuid: int,
rate_tolerance: Optional[float] = 0.005,
wait_for_inclusion: bool = True,
wait_for_finalization: bool = False,
period: Optional[int] = None,
) -> tuple[bool, str]:
"""Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network.

Arguments:
subtensor: Subtensor instance.
wallet: The wallet of the stake owner.
hotkey: The SS58 address of the hotkey to unstake from.
netuid: The unique identifier of the subnet.
rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum
price decrease. If not passed (None), then unstaking goes without price limit. Default is `0.005`.
wait_for_inclusion: Waits for the transaction to be included in a block. Default is `True`.
wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `False`.
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
transaction is not included in a block within that number of blocks, it will expire and be rejected. You can
think of it as an expiration date for the transaction. Default is `None`.

Returns:
tuple[bool, str]:
A tuple containing:
- `True` and a success message if the unstake operation succeeded;
- `False` and an error message otherwise.
"""
if not (unlock := unlock_key(wallet)).success:
logging.error(unlock.message)
return False, unlock.message

call_params = {
"hotkey": hotkey,
"netuid": netuid,
"limit_price": None,
}

if rate_tolerance:
current_price = (await subtensor.subnet(netuid=netuid)).price
limit_price = current_price * (1 - rate_tolerance)
call_params.update({"limit_price": limit_price})

async with subtensor.substrate as substrate:
call = await substrate.compose_call(
call_module="SubtensorModule",
call_function="remove_stake_full_limit",
call_params=call_params,
)

return await subtensor.sign_and_send_extrinsic(
call=call,
wallet=wallet,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
nonce_key="coldkeypub",
sign_with="coldkey",
use_nonce=True,
period=period,
)


async def unstake_multiple_extrinsic(
subtensor: "AsyncSubtensor",
wallet: "Wallet",
Expand All @@ -214,23 +280,25 @@ async def unstake_multiple_extrinsic(
"""Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey.

Args:
subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance.
wallet (bittensor_wallet.Wallet): The wallet with the coldkey to unstake to.
hotkey_ss58s (List[str]): List of hotkeys to unstake from.
netuids (List[int]): List of netuids to unstake from.
amounts (List[Union[Balance, float]]): List of amounts to unstake. If ``None``, unstake all.
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or
subtensor: Subtensor instance.
wallet: The wallet with the coldkey to unstake to.
hotkey_ss58s: List of hotkeys to unstake from.
netuids: List of netuids to unstake from.
amounts: List of amounts to unstake. If ``None``, unstake all.
wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or
returns ``False`` if the extrinsic fails to enter the block within the timeout.
wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning
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.
period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted.
If the transaction is not included in a block within that number of blocks, it will expire and be rejected.
You can think of it as an expiration date for the transaction.
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
transaction is not included in a block within that number of blocks, it will expire and be rejected. You can
think of it as an expiration date for the transaction.
unstake_all: If true, unstakes all tokens. Default is ``False``.

Returns:
success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. Flag is ``True`` if any
wallet was unstaked. If we did not wait for finalization / inclusion, the response is ``True``.
tuple[bool, str]:
A tuple containing:
- `True` and a success message if the unstake operation succeeded;
- `False` and an error message otherwise.
"""
if amounts and unstake_all:
raise ValueError("Cannot specify both `amounts` and `unstake_all`.")
Expand Down
2 changes: 1 addition & 1 deletion bittensor/core/extrinsics/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def transfer_extrinsic(
logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]")
return False

logging.info(":satellite: [magenta]Transferring...</magenta")
logging.info(":satellite: [magenta]Transferring...[/magenta]")
success, block_hash, err_msg = _do_transfer(
subtensor=subtensor,
wallet=wallet,
Expand Down
Loading
Loading