Skip to content
This repository was archived by the owner on Jul 10, 2026. It is now read-only.

Commit 7e6773f

Browse files
authored
Merge pull request #3062 from opentensor/feat/roman/autostaking
set auto hotkey per subnet
2 parents 2cdd572 + 37d44b6 commit 7e6773f

11 files changed

Lines changed: 589 additions & 0 deletions

File tree

bittensor/core/async_subtensor.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from bittensor.core.extrinsics.asyncex.staking import (
7474
add_stake_extrinsic,
7575
add_stake_multiple_extrinsic,
76+
set_auto_stake_extrinsic,
7677
)
7778
from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic
7879
from bittensor.core.extrinsics.asyncex.mechanism import (
@@ -1201,6 +1202,42 @@ async def get_all_subnets_info(
12011202

12021203
return SubnetInfo.list_from_dicts(result)
12031204

1205+
async def get_auto_stakes(
1206+
self,
1207+
coldkey_ss58: str,
1208+
block: Optional[int] = None,
1209+
block_hash: Optional[str] = None,
1210+
reuse_block: bool = False,
1211+
) -> dict[int, str]:
1212+
"""Fetches auto stake destinations for a given wallet across all subnets.
1213+
1214+
Parameters:
1215+
coldkey_ss58: Coldkey ss58 address.
1216+
block: The block number for the query.
1217+
block_hash: The block hash for the query.
1218+
reuse_block: Whether to reuse the last-used block hash.
1219+
1220+
Returns:
1221+
dict[int, str]:
1222+
- netuid: The unique identifier of the subnet.
1223+
- hotkey: The hotkey of the wallet.
1224+
"""
1225+
block_hash = await self.determine_block_hash(block, block_hash, reuse_block)
1226+
query = await self.substrate.query_map(
1227+
module="SubtensorModule",
1228+
storage_function="AutoStakeDestination",
1229+
params=[coldkey_ss58],
1230+
block_hash=block_hash,
1231+
)
1232+
1233+
pairs = {}
1234+
async for netuid, destination in query:
1235+
hotkey_ss58 = decode_account_id(destination.value[0])
1236+
if hotkey_ss58:
1237+
pairs[int(netuid)] = hotkey_ss58
1238+
1239+
return pairs
1240+
12041241
async def get_balance(
12051242
self,
12061243
address: str,
@@ -5170,6 +5207,46 @@ async def root_set_weights(
51705207
period=period,
51715208
)
51725209

5210+
async def set_auto_stake(
5211+
self,
5212+
wallet: "Wallet",
5213+
netuid: int,
5214+
hotkey_ss58: str,
5215+
period: Optional[int] = None,
5216+
raise_error: bool = False,
5217+
wait_for_inclusion: bool = True,
5218+
wait_for_finalization: bool = True,
5219+
) -> tuple[bool, str]:
5220+
"""Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism.
5221+
5222+
Parameters:
5223+
wallet: Bittensor Wallet instance.
5224+
netuid: The subnet unique identifier.
5225+
hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards
5226+
received from the specified subnet immediately upon receipt.
5227+
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
5228+
transaction is not included in a block within that number of blocks, it will expire and be rejected. You
5229+
can think of it as an expiration date for the transaction.
5230+
raise_error: Raises a relevant exception rather than returning `False` if unsuccessful.
5231+
wait_for_inclusion: Whether to wait for the inclusion of the transaction.
5232+
wait_for_finalization: Whether to wait for the finalization of the transaction.
5233+
5234+
Returns:
5235+
tuple[bool, str]:
5236+
`True` if the extrinsic executed successfully, `False` otherwise.
5237+
`message` is a string value describing the success or potential error.
5238+
"""
5239+
return await set_auto_stake_extrinsic(
5240+
subtensor=self,
5241+
wallet=wallet,
5242+
netuid=netuid,
5243+
hotkey_ss58=hotkey_ss58,
5244+
period=period,
5245+
raise_error=raise_error,
5246+
wait_for_inclusion=wait_for_inclusion,
5247+
wait_for_finalization=wait_for_finalization,
5248+
)
5249+
51735250
async def set_children(
51745251
self,
51755252
wallet: "Wallet",

bittensor/core/extrinsics/asyncex/staking.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,71 @@ async def add_stake_multiple_extrinsic(
426426
return True
427427

428428
return False
429+
430+
431+
async def set_auto_stake_extrinsic(
432+
subtensor: "AsyncSubtensor",
433+
wallet: "Wallet",
434+
netuid: int,
435+
hotkey_ss58: str,
436+
period: Optional[int] = None,
437+
raise_error: bool = False,
438+
wait_for_inclusion: bool = True,
439+
wait_for_finalization: bool = True,
440+
) -> tuple[bool, str]:
441+
"""Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism.
442+
443+
Parameters:
444+
subtensor: AsyncSubtensor instance.
445+
wallet: Bittensor Wallet instance.
446+
netuid: The subnet unique identifier.
447+
hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards
448+
received from the specified subnet immediately upon receipt.
449+
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
450+
transaction is not included in a block within that number of blocks, it will expire and be rejected. You can
451+
think of it as an expiration date for the transaction.
452+
raise_error: Raises a relevant exception rather than returning `False` if unsuccessful.
453+
wait_for_inclusion: Whether to wait for the inclusion of the transaction.
454+
wait_for_finalization: Whether to wait for the finalization of the transaction.
455+
456+
Returns:
457+
tuple[bool, str]:
458+
`True` if the extrinsic executed successfully, `False` otherwise.
459+
`message` is a string value describing the success or potential error.
460+
"""
461+
try:
462+
unlock = unlock_key(wallet, raise_error=raise_error)
463+
if not unlock.success:
464+
logging.error(unlock.message)
465+
return False, unlock.message
466+
467+
call = await subtensor.substrate.compose_call(
468+
call_module="SubtensorModule",
469+
call_function="set_coldkey_auto_stake_hotkey",
470+
call_params={
471+
"netuid": netuid,
472+
"hotkey": hotkey_ss58,
473+
},
474+
)
475+
success, message = await subtensor.sign_and_send_extrinsic(
476+
call=call,
477+
wallet=wallet,
478+
period=period,
479+
raise_error=raise_error,
480+
wait_for_inclusion=wait_for_inclusion,
481+
wait_for_finalization=wait_for_finalization,
482+
)
483+
484+
if success:
485+
logging.debug(message)
486+
return True, message
487+
488+
logging.error(message)
489+
return False, message
490+
491+
except Exception as error:
492+
if raise_error:
493+
raise error
494+
logging.error(str(error))
495+
496+
return False, str(error)

bittensor/core/extrinsics/staking.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,71 @@ def add_stake_multiple_extrinsic(
405405
return True
406406

407407
return False
408+
409+
410+
def set_auto_stake_extrinsic(
411+
subtensor: "Subtensor",
412+
wallet: "Wallet",
413+
netuid: int,
414+
hotkey_ss58: str,
415+
period: Optional[int] = None,
416+
raise_error: bool = False,
417+
wait_for_inclusion: bool = True,
418+
wait_for_finalization: bool = True,
419+
) -> tuple[bool, str]:
420+
"""Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism.
421+
422+
Parameters:
423+
subtensor: AsyncSubtensor instance.
424+
wallet: Bittensor Wallet instance.
425+
netuid: The subnet unique identifier.
426+
hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards
427+
received from the specified subnet immediately upon receipt.
428+
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
429+
transaction is not included in a block within that number of blocks, it will expire and be rejected. You can
430+
think of it as an expiration date for the transaction.
431+
raise_error: Raises a relevant exception rather than returning `False` if unsuccessful.
432+
wait_for_inclusion: Whether to wait for the inclusion of the transaction.
433+
wait_for_finalization: Whether to wait for the finalization of the transaction.
434+
435+
Returns:
436+
tuple[bool, str]:
437+
`True` if the extrinsic executed successfully, `False` otherwise.
438+
`message` is a string value describing the success or potential error.
439+
"""
440+
try:
441+
unlock = unlock_key(wallet, raise_error=raise_error)
442+
if not unlock.success:
443+
logging.error(unlock.message)
444+
return False, unlock.message
445+
446+
call = subtensor.substrate.compose_call(
447+
call_module="SubtensorModule",
448+
call_function="set_coldkey_auto_stake_hotkey",
449+
call_params={
450+
"netuid": netuid,
451+
"hotkey": hotkey_ss58,
452+
},
453+
)
454+
success, message = subtensor.sign_and_send_extrinsic(
455+
call=call,
456+
wallet=wallet,
457+
period=period,
458+
raise_error=raise_error,
459+
wait_for_inclusion=wait_for_inclusion,
460+
wait_for_finalization=wait_for_finalization,
461+
)
462+
463+
if success:
464+
logging.debug(message)
465+
return True, message
466+
467+
logging.error(message)
468+
return False, message
469+
470+
except Exception as error:
471+
if raise_error:
472+
raise error
473+
logging.error(str(error))
474+
475+
return False, str(error)

bittensor/core/subtensor.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
from bittensor.core.extrinsics.staking import (
7878
add_stake_extrinsic,
7979
add_stake_multiple_extrinsic,
80+
set_auto_stake_extrinsic,
8081
)
8182
from bittensor.core.extrinsics.start_call import start_call_extrinsic
8283
from bittensor.core.extrinsics.take import (
@@ -687,6 +688,38 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo"
687688

688689
return SubnetInfo.list_from_dicts(result)
689690

691+
def get_auto_stakes(
692+
self,
693+
coldkey_ss58: str,
694+
block: Optional[int] = None,
695+
) -> dict[int, str]:
696+
"""Fetches auto stake destinations for a given wallet across all subnets.
697+
698+
Parameters:
699+
coldkey_ss58: Coldkey ss58 address.
700+
block: Subnet unique identifier.
701+
702+
Returns:
703+
dict[int, str]:
704+
- netuid: The unique identifier of the subnet.
705+
- hotkey: The hotkey of the wallet.
706+
"""
707+
block_hash = self.determine_block_hash(block=block)
708+
query = self.substrate.query_map(
709+
module="SubtensorModule",
710+
storage_function="AutoStakeDestination",
711+
params=[coldkey_ss58],
712+
block_hash=block_hash,
713+
)
714+
715+
pairs = {}
716+
for netuid, destination in query:
717+
hotkey_ss58 = decode_account_id(destination.value[0])
718+
if hotkey_ss58:
719+
pairs[int(netuid)] = hotkey_ss58
720+
721+
return pairs
722+
690723
def get_balance(self, address: str, block: Optional[int] = None) -> Balance:
691724
"""
692725
Retrieves the balance for given coldkey. Always in TAO.
@@ -4005,6 +4038,46 @@ def root_set_weights(
40054038
period=period,
40064039
)
40074040

4041+
def set_auto_stake(
4042+
self,
4043+
wallet: "Wallet",
4044+
netuid: int,
4045+
hotkey_ss58: str,
4046+
period: Optional[int] = None,
4047+
raise_error: bool = False,
4048+
wait_for_inclusion: bool = True,
4049+
wait_for_finalization: bool = True,
4050+
) -> tuple[bool, str]:
4051+
"""Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism.
4052+
4053+
Parameters:
4054+
wallet: Bittensor Wallet instance.
4055+
netuid: The subnet unique identifier.
4056+
hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards
4057+
received from the specified subnet immediately upon receipt.
4058+
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
4059+
transaction is not included in a block within that number of blocks, it will expire and be rejected. You
4060+
can think of it as an expiration date for the transaction.
4061+
raise_error: Raises a relevant exception rather than returning `False` if unsuccessful.
4062+
wait_for_inclusion: Whether to wait for the inclusion of the transaction.
4063+
wait_for_finalization: Whether to wait for the finalization of the transaction.
4064+
4065+
Returns:
4066+
tuple[bool, str]:
4067+
`True` if the extrinsic executed successfully, `False` otherwise.
4068+
`message` is a string value describing the success or potential error.
4069+
"""
4070+
return set_auto_stake_extrinsic(
4071+
subtensor=self,
4072+
wallet=wallet,
4073+
netuid=netuid,
4074+
hotkey_ss58=hotkey_ss58,
4075+
period=period,
4076+
raise_error=raise_error,
4077+
wait_for_inclusion=wait_for_inclusion,
4078+
wait_for_finalization=wait_for_finalization,
4079+
)
4080+
40084081
def set_children(
40094082
self,
40104083
wallet: "Wallet",

bittensor/core/subtensor_api/staking.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Staking:
99
def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
1010
self.add_stake = subtensor.add_stake
1111
self.add_stake_multiple = subtensor.add_stake_multiple
12+
self.get_auto_stakes = subtensor.get_auto_stakes
1213
self.get_hotkey_stake = subtensor.get_hotkey_stake
1314
self.get_minimum_required_stake = subtensor.get_minimum_required_stake
1415
self.get_stake = subtensor.get_stake
@@ -23,6 +24,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
2324
self.get_stake_weight = subtensor.get_stake_weight
2425
self.get_unstake_fee = subtensor.get_unstake_fee
2526
self.move_stake = subtensor.move_stake
27+
self.set_auto_stake = subtensor.set_auto_stake
2628
self.unstake = subtensor.unstake
2729
self.unstake_all = subtensor.unstake_all
2830
self.unstake_multiple = subtensor.unstake_multiple

bittensor/core/subtensor_api/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"):
3535
subtensor._subtensor.get_all_revealed_commitments
3636
)
3737
subtensor.get_all_subnets_info = subtensor._subtensor.get_all_subnets_info
38+
subtensor.get_auto_stakes = subtensor._subtensor.get_auto_stakes
3839
subtensor.get_balance = subtensor._subtensor.get_balance
3940
subtensor.get_balances = subtensor._subtensor.get_balances
4041
subtensor.get_block_hash = subtensor._subtensor.get_block_hash
@@ -161,6 +162,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"):
161162
)
162163
subtensor.root_set_weights = subtensor._subtensor.root_set_weights
163164
subtensor.serve_axon = subtensor._subtensor.serve_axon
165+
subtensor.set_auto_stake = subtensor._subtensor.set_auto_stake
164166
subtensor.set_children = subtensor._subtensor.set_children
165167
subtensor.set_commitment = subtensor._subtensor.set_commitment
166168
subtensor.set_delegate_take = subtensor._subtensor.set_delegate_take

0 commit comments

Comments
 (0)