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

Commit 6411041

Browse files
authored
Merge pull request #3089 from opentensor/release/9.12.0
Release v9.12.0
2 parents de964b4 + 3c1efb8 commit 6411041

19 files changed

Lines changed: 8809 additions & 4751 deletions

CHANGELOG.md

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

3+
## 9.12.0 /2025-10-08
4+
5+
## What's Changed
6+
* Websocket Stub Generator Script by @thewhaleking in https://github.com/opentensor/bittensor/pull/3067
7+
* set auto hotkey per subnet by @basfroman in https://github.com/opentensor/bittensor/pull/3062
8+
* Update `get_neuron_for_pubkey_and_subnet` to use block_hash instead of block and block_hash by @yuma-doc in https://github.com/opentensor/bittensor/pull/3084
9+
* Improves `determine_block_hash` method of `AsyncSubtensor` by @thewhaleking in https://github.com/opentensor/bittensor/pull/3087
10+
11+
## New Contributors
12+
* @yuma-doc made their first contribution in https://github.com/opentensor/bittensor/pull/3084
13+
14+
**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.11.1...v9.12.0
15+
316
## 9.11.1 /2025-09-25
417

518
## What's Changed

bittensor/core/async_subtensor.py

Lines changed: 100 additions & 14 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 (
@@ -307,7 +308,7 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
307308

308309
async def determine_block_hash(
309310
self,
310-
block: Optional[int],
311+
block: Optional[int] = None,
311312
block_hash: Optional[str] = None,
312313
reuse_block: bool = False,
313314
) -> Optional[str]:
@@ -317,31 +318,41 @@ async def determine_block_hash(
317318
for blockchain queries.
318319
319320
Arguments:
320-
block: The block number to get the hash for. Do not specify if using block_hash or reuse_block.
321-
block_hash: The hash of the blockchain block. Do not specify if using block or reuse_block.
322-
reuse_block: Whether to reuse the last-used block hash. Do not set if using block or reuse_block.
321+
block: The block number to get the hash for. If specifying along with `block_hash`, the hash of `block` will
322+
be checked and compared with the supplied block hash, raising a ValueError if the two do not match.
323+
block_hash: The hash of the blockchain block. If specifying along with `block`, the hash of `block` will be
324+
checked and compared with the supplied block hash, raising a ValueError if the two do not match.
325+
reuse_block: Whether to reuse the last-used block hash. Do not set if using block or block_hash.
323326
324327
Returns:
325328
Optional[str]: The block hash if one can be determined, None otherwise.
326329
327330
Raises:
328-
ValueError: If more than one of block, block_hash, or reuse_block is specified.
331+
ValueError: If reuse_block is set, while also supplying a block/block_hash, or if supplying a block and
332+
block_hash, but the hash of the block does not match the supplied block hash.
329333
330334
Example:
331335
# Get hash for specific block
332336
block_hash = await subtensor.determine_block_hash(block=1000000)
333337
334338
# Use provided block hash
335-
hash = await subtensor.determine_block_hash(block_hash="0x1234...")
339+
block_hash = await subtensor.determine_block_hash(block_hash="0x1234...")
336340
337341
# Reuse last block hash
338-
hash = await subtensor.determine_block_hash(reuse_block=True)
339-
"""
340-
# Ensure that only one of the parameters is specified.
341-
if sum(bool(x) for x in [block, block_hash, reuse_block]) > 1:
342-
raise ValueError(
343-
"Only one of ``block``, ``block_hash``, or ``reuse_block`` can be specified."
344-
)
342+
block_hash = await subtensor.determine_block_hash(reuse_block=True)
343+
"""
344+
if reuse_block and any([block, block_hash]):
345+
raise ValueError("Cannot specify both reuse_block and block_hash/block")
346+
if block and block_hash:
347+
retrieved_block_hash = await self.get_block_hash(block)
348+
if retrieved_block_hash != block_hash:
349+
raise ValueError(
350+
"You have supplied a `block_hash` and a `block`, but the block does not map to the same hash as "
351+
f"the one you supplied. You supplied `block_hash={block_hash}` for `block={block}`, but this block"
352+
f"maps to the block hash {retrieved_block_hash}."
353+
)
354+
else:
355+
return retrieved_block_hash
345356

346357
# Return the appropriate value.
347358
if block_hash:
@@ -1201,6 +1212,42 @@ async def get_all_subnets_info(
12011212

12021213
return SubnetInfo.list_from_dicts(result)
12031214

1215+
async def get_auto_stakes(
1216+
self,
1217+
coldkey_ss58: str,
1218+
block: Optional[int] = None,
1219+
block_hash: Optional[str] = None,
1220+
reuse_block: bool = False,
1221+
) -> dict[int, str]:
1222+
"""Fetches auto stake destinations for a given wallet across all subnets.
1223+
1224+
Parameters:
1225+
coldkey_ss58: Coldkey ss58 address.
1226+
block: The block number for the query.
1227+
block_hash: The block hash for the query.
1228+
reuse_block: Whether to reuse the last-used block hash.
1229+
1230+
Returns:
1231+
dict[int, str]:
1232+
- netuid: The unique identifier of the subnet.
1233+
- hotkey: The hotkey of the wallet.
1234+
"""
1235+
block_hash = await self.determine_block_hash(block, block_hash, reuse_block)
1236+
query = await self.substrate.query_map(
1237+
module="SubtensorModule",
1238+
storage_function="AutoStakeDestination",
1239+
params=[coldkey_ss58],
1240+
block_hash=block_hash,
1241+
)
1242+
1243+
pairs = {}
1244+
async for netuid, destination in query:
1245+
hotkey_ss58 = decode_account_id(destination.value[0])
1246+
if hotkey_ss58:
1247+
pairs[int(netuid)] = hotkey_ss58
1248+
1249+
return pairs
1250+
12041251
async def get_balance(
12051252
self,
12061253
address: str,
@@ -2530,7 +2577,6 @@ async def get_neuron_for_pubkey_and_subnet(
25302577
return await self.neuron_for_uid(
25312578
uid=uid,
25322579
netuid=netuid,
2533-
block=block,
25342580
block_hash=block_hash,
25352581
reuse_block=reuse_block,
25362582
)
@@ -5170,6 +5216,46 @@ async def root_set_weights(
51705216
period=period,
51715217
)
51725218

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

0 commit comments

Comments
 (0)