Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
37f57f5
updates safe staking limits
ibraheem-abe Jul 7, 2025
7050309
update call
ibraheem-abe Jul 7, 2025
f6a2555
ruff
ibraheem-abe Jul 7, 2025
7bb17d2
wording
Jul 7, 2025
d59355f
update async extrinsics
Jul 7, 2025
586b8b8
update DynamicInfo
Jul 8, 2025
c0a4c2f
update subtensor
Jul 8, 2025
725682d
update async subtensor
Jul 8, 2025
7a4a14a
update SubtensorApi
Jul 8, 2025
e7c3e4c
update subtensor calls
Jul 8, 2025
5f86e9b
add unit tests
Jul 8, 2025
e9d12e7
# TODO: Improve integration tests workflow
Jul 8, 2025
8533ff0
make DynamicInfo.price Optional[Balance] + raise the error of not Non…
Jul 8, 2025
aa63fd7
use gather
Jul 8, 2025
bd588a0
update `async def subnet`
Jul 8, 2025
4e815db
Merge pull request #2952 from opentensor/feat/roman/new-logic-to-get-…
basfroman Jul 9, 2025
1002b12
Merge branch 'staging' into update/safe-staking-limits
Jul 9, 2025
8d408b6
update unstake logic
ibraheem-abe Jul 9, 2025
b816140
update stake logic
ibraheem-abe Jul 9, 2025
e94a984
update unstaking
ibraheem-abe Jul 9, 2025
d43b3dc
update staking
ibraheem-abe Jul 9, 2025
bc99690
update var name
ibraheem-abe Jul 9, 2025
382a90d
update root condition
ibraheem-abe Jul 9, 2025
477964c
bumps version and changelog
ibraheem-abe Jul 9, 2025
73cca54
Merge pull request #2950 from opentensor/update/safe-staking-limits
ibraheem-abe Jul 9, 2025
d425f94
Merge branch 'staging' into changelog/981
ibraheem-abe Jul 9, 2025
70c2855
Merge pull request #2953 from opentensor/changelog/981
ibraheem-abe Jul 9, 2025
e07cd11
Merge branch 'master' into release/9.8.1
ibraheem-abe Jul 9, 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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 9.8.1 /2025-07-08

## What's Changed
* New logic to get price for `DynamicInfo` by @basfroman in https://github.com/opentensor/bittensor/pull/2952
* Update to safe_staking limits by @ibraheem-abe in https://github.com/opentensor/bittensor/pull/2950

**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.8.0...v9.8.1

## 9.8.0 /2025-07-07

## What's Changed
Expand Down
110 changes: 101 additions & 9 deletions bittensor/core/async_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,13 +646,21 @@ async def all_subnets(
)
if not block_hash and reuse_block:
block_hash = self.substrate.last_block_hash
query = await self.substrate.runtime_call(
"SubnetInfoRuntimeApi",
"get_all_dynamic_info",
block_hash=block_hash,

query, subnet_prices = await asyncio.gather(
self.substrate.runtime_call(
"SubnetInfoRuntimeApi",
"get_all_dynamic_info",
block_hash=block_hash,
),
self.get_subnet_prices(),
)
subnets = DynamicInfo.list_from_dicts(query.decode())
return subnets

decoded = query.decode()

for sn in decoded:
sn.update({"price": subnet_prices.get(sn["netuid"], Balance.from_tao(0))})
return DynamicInfo.list_from_dicts(decoded)

async def blocks_since_last_step(
self,
Expand Down Expand Up @@ -902,8 +910,13 @@ async def get_all_subnets_info(
)
if not result:
return []
else:
return SubnetInfo.list_from_dicts(result)

subnets_prices = await self.get_subnet_prices()

for subnet in result:
subnet.update({"price": subnets_prices.get(subnet["netuid"], 0)})

return SubnetInfo.list_from_dicts(result)

async def get_balance(
self,
Expand Down Expand Up @@ -2267,6 +2280,84 @@ async def get_subnet_info(
return None
return SubnetInfo.from_dict(result)

async def get_subnet_price(
self,
netuid: int,
block: Optional[int] = None,
block_hash: Optional[str] = None,
reuse_block: bool = False,
) -> Balance:
"""Gets the current Alpha price in TAO for all subnets.

Arguments:
netuid: The unique identifier of the subnet.
block: The blockchain block number for the query.
block_hash (Optional[str]): The hash of the block to retrieve the stake from. Do not specify if using block
or reuse_block
reuse_block (bool): Whether to use the last-used block. Do not set if using block_hash or block.

Returns:
The current Alpha price in TAO units for the specified subnet.
"""
# SN0 price is always 1 TAO
if netuid == 0:
return Balance.from_tao(1)

block_hash = await self.determine_block_hash(
block=block, block_hash=block_hash, reuse_block=reuse_block
)
current_sqrt_price = await self.substrate.query(
module="Swap",
storage_function="AlphaSqrtPrice",
params=[netuid],
block_hash=block_hash,
)

current_sqrt_price = fixed_to_float(current_sqrt_price)
current_price = current_sqrt_price * current_sqrt_price
return Balance.from_rao(int(current_price * 1e9))

async def get_subnet_prices(
self,
block: Optional[int] = None,
block_hash: Optional[str] = None,
reuse_block: bool = False,
) -> dict[int, Balance]:
"""Gets the current Alpha price in TAO for a specified subnet.

Args:
block: The blockchain block number for the query.
block_hash (Optional[str]): The hash of the block to retrieve the stake from. Do not specify if using block
or reuse_block
reuse_block (bool): Whether to use the last-used block. Do not set if using block_hash or block.

Returns:
dict:
- subnet unique ID
- The current Alpha price in TAO units for the specified subnet.
"""
block_hash = await self.determine_block_hash(
block=block, block_hash=block_hash, reuse_block=reuse_block
)

current_sqrt_prices = await self.substrate.query_map(
module="Swap",
storage_function="AlphaSqrtPrice",
block_hash=block_hash,
page_size=129, # total number of subnets
)

prices = {}
async for id_, current_sqrt_price in current_sqrt_prices:
current_sqrt_price = fixed_to_float(current_sqrt_price)
current_price = current_sqrt_price * current_sqrt_price
current_price_in_tao = Balance.from_rao(int(current_price * 1e9))
prices.update({id_: current_price_in_tao})

# SN0 price is always 1 TAO
prices.update({0: Balance.from_tao(1)})
return prices

async def get_unstake_fee(
self,
amount: Balance,
Expand Down Expand Up @@ -3336,7 +3427,8 @@ async def subnet(
)

if isinstance(decoded := query.decode(), dict):
return DynamicInfo.from_dict(decoded)
price = self.get_subnet_price(netuid=netuid, block=block)
return DynamicInfo.from_dict({**decoded, "price": price})
return None

async def subnet_exists(
Expand Down
13 changes: 5 additions & 8 deletions bittensor/core/chain_data/dynamic_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class DynamicInfo(InfoBase):
alpha_in: Balance
alpha_out: Balance
tao_in: Balance
price: Balance
price: Optional[Balance]
k: float
is_dynamic: bool
alpha_out_emission: Balance
Expand Down Expand Up @@ -74,13 +74,6 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo":
).set_unit(0)

subnet_volume = Balance.from_rao(decoded["subnet_volume"]).set_unit(netuid)
price = (
Balance.from_tao(1.0)
if netuid == 0
else Balance.from_tao(tao_in.tao / alpha_in.tao).set_unit(netuid)
if alpha_in.tao > 0
else Balance.from_tao(1).set_unit(netuid)
) # Root always has 1-1 price

if decoded.get("subnet_identity"):
subnet_identity = SubnetIdentity(
Expand All @@ -97,6 +90,10 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo":
)
else:
subnet_identity = None
price = decoded.get("price", None)

if price and not isinstance(price, Balance):
raise ValueError(f"price must be a Balance object, got {type(price)}.")

return cls(
netuid=netuid,
Expand Down
31 changes: 17 additions & 14 deletions bittensor/core/extrinsics/asyncex/staking.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,28 +120,31 @@ async def add_stake_extrinsic(

if safe_staking:
pool = await subtensor.subnet(netuid=netuid)
base_price = pool.price.rao
price_with_tolerance = base_price * (1 + rate_tolerance)
call_params.update(
{
"limit_price": price_with_tolerance,
"allow_partial": allow_partial_stake,
}
)
call_function = "add_stake_limit"
base_price = pool.price.tao

if pool.netuid == 0:
price_with_tolerance = base_price
else:
price_with_tolerance = base_price * (1 + rate_tolerance)

# For logging
base_rate = pool.price.tao
rate_with_tolerance = base_rate * (1 + rate_tolerance)
logging.info(
f":satellite: [magenta]Safe Staking to:[/magenta] "
f"[blue]netuid: [green]{netuid}[/green], amount: [green]{staking_balance}[/green], "
f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], "
f"price limit: [green]{rate_with_tolerance}[/green], "
f"original price: [green]{base_rate}[/green], "
f"price limit: [green]{price_with_tolerance}[/green], "
f"original price: [green]{base_price}[/green], "
f"with partial stake: [green]{allow_partial_stake}[/green] "
f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]"
)

limit_price = Balance.from_tao(price_with_tolerance).rao
call_params.update(
{
"limit_price": limit_price,
"allow_partial": allow_partial_stake,
}
)
call_function = "add_stake_limit"
else:
logging.info(
f":satellite: [magenta]Staking to:[/magenta] "
Expand Down
17 changes: 9 additions & 8 deletions bittensor/core/extrinsics/asyncex/unstaking.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,26 +107,27 @@ async def unstake_extrinsic(
}
if safe_staking:
pool = await subtensor.subnet(netuid=netuid)
base_price = pool.price.rao
price_with_tolerance = base_price * (1 - rate_tolerance)
base_price = pool.price.tao

# For logging
base_rate = pool.price.tao
rate_with_tolerance = base_rate * (1 - rate_tolerance)
if pool.netuid == 0:
price_with_tolerance = base_price
else:
price_with_tolerance = base_price * (1 - rate_tolerance)

logging.info(
f":satellite: [magenta]Safe Unstaking from:[/magenta] "
f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green], "
f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], "
f"price limit: [green]{rate_with_tolerance}[/green], "
f"original price: [green]{base_rate}[/green], "
f"price limit: [green]{price_with_tolerance}[/green], "
f"original price: [green]{base_price}[/green], "
f"with partial unstake: [green]{allow_partial_stake}[/green] "
f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]"
)

limit_price = Balance.from_tao(price_with_tolerance).rao
call_params.update(
{
"limit_price": price_with_tolerance,
"limit_price": limit_price,
"allow_partial": allow_partial_stake,
}
)
Expand Down
17 changes: 9 additions & 8 deletions bittensor/core/extrinsics/staking.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,26 +112,27 @@ def add_stake_extrinsic(

if safe_staking:
pool = subtensor.subnet(netuid=netuid)
base_price = pool.price.rao
price_with_tolerance = base_price * (1 + rate_tolerance)
base_price = pool.price.tao

# For logging
base_rate = pool.price.tao
rate_with_tolerance = base_rate * (1 + rate_tolerance)
if pool.netuid == 0:
price_with_tolerance = base_price
else:
price_with_tolerance = base_price * (1 + rate_tolerance)

logging.info(
f":satellite: [magenta]Safe Staking to:[/magenta] "
f"[blue]netuid: [green]{netuid}[/green], amount: [green]{staking_balance}[/green], "
f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], "
f"price limit: [green]{rate_with_tolerance}[/green], "
f"original price: [green]{base_rate}[/green], "
f"price limit: [green]{price_with_tolerance}[/green], "
f"original price: [green]{base_price}[/green], "
f"with partial stake: [green]{allow_partial_stake}[/green] "
f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]"
)

limit_price = Balance.from_tao(price_with_tolerance).rao
call_params.update(
{
"limit_price": price_with_tolerance,
"limit_price": limit_price,
"allow_partial": allow_partial_stake,
}
)
Expand Down
17 changes: 9 additions & 8 deletions bittensor/core/extrinsics/unstaking.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,26 +105,27 @@ def unstake_extrinsic(

if safe_staking:
pool = subtensor.subnet(netuid=netuid)
base_price = pool.price.rao
price_with_tolerance = base_price * (1 - rate_tolerance)
base_price = pool.price.tao

# For logging
base_rate = pool.price.tao
rate_with_tolerance = base_rate * (1 - rate_tolerance)
if pool.netuid == 0:
price_with_tolerance = base_price
else:
price_with_tolerance = base_price * (1 - rate_tolerance)

logging.info(
f":satellite: [magenta]Safe Unstaking from:[/magenta] "
f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green], "
f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], "
f"price limit: [green]{rate_with_tolerance}[/green], "
f"original price: [green]{base_rate}[/green], "
f"price limit: [green]{price_with_tolerance}[/green], "
f"original price: [green]{base_price}[/green], "
f"with partial unstake: [green]{allow_partial_stake}[/green] "
f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]"
)

limit_price = Balance.from_tao(price_with_tolerance).rao
call_params.update(
{
"limit_price": price_with_tolerance,
"limit_price": limit_price,
"allow_partial": allow_partial_stake,
}
)
Expand Down
Loading
Loading