Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
89 changes: 78 additions & 11 deletions bittensor_cli/src/bittensor/subtensor_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1423,23 +1423,40 @@ async def get_stake_for_coldkeys(
return stake_info_map if stake_info_map else None

async def all_subnets(self, block_hash: Optional[str] = None) -> list[DynamicInfo]:
result = await self.query_runtime_api(
"SubnetInfoRuntimeApi",
"get_all_dynamic_info",
block_hash=block_hash,
result, prices = await asyncio.gather(
self.query_runtime_api(
"SubnetInfoRuntimeApi",
"get_all_dynamic_info",
block_hash=block_hash,
),
self.get_subnet_prices(block_hash=block_hash, page_size=129),
)
return DynamicInfo.list_from_any(result)
sns: list[DynamicInfo] = DynamicInfo.list_from_any(result)
for sn in sns:
if sn.netuid == 0:
sn.price = Balance.from_tao(1.0)
else:
try:
sn.price = prices[sn.netuid]
except KeyError:
sn.price = sn.tao_in / sn.alpha_in
return sns

async def subnet(
self, netuid: int, block_hash: Optional[str] = None
) -> "DynamicInfo":
result = await self.query_runtime_api(
"SubnetInfoRuntimeApi",
"get_dynamic_info",
params=[netuid],
block_hash=block_hash,
result, price = await asyncio.gather(
self.query_runtime_api(
"SubnetInfoRuntimeApi",
"get_dynamic_info",
params=[netuid],
block_hash=block_hash,
),
self.get_subnet_price(netuid=netuid, block_hash=block_hash),
)
return DynamicInfo.from_any(result)
subnet_ = DynamicInfo.from_any(result)
subnet_.price = price
return subnet_

async def get_owned_hotkeys(
self,
Expand Down Expand Up @@ -1581,3 +1598,53 @@ async def get_coldkey_swap_schedule_duration(
)

return result

async def get_subnet_price(
self,
netuid: int = None,
block_hash: Optional[str] = None,
) -> Balance:
"""
Gets the current Alpha price in TAO for a specific subnet.

:param netuid: The unique identifier of the subnet.
:param block_hash: The hash of the block to retrieve the price from.

:return: The current Alpha price in TAO units for the specified subnet.
"""
current_sqrt_price = await self.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_hash: Optional[str] = None, page_size: int = 100
) -> dict[int, Balance]:
"""
Gets the current Alpha prices in TAO for all subnets.

:param block_hash: The hash of the block to retrieve prices from.
:param page_size: The page size for batch queries (default: 100).

:return: A dictionary mapping netuid to the current Alpha price in TAO units.
"""
query = await self.substrate.query_map(
module="Swap",
storage_function="AlphaSqrtPrice",
page_size=page_size,
block_hash=block_hash,
)

map_ = {}
async for netuid_, current_sqrt_price in query:
current_sqrt_price_ = fixed_to_float(current_sqrt_price.value)
current_price = current_sqrt_price_**2
map_[netuid_] = Balance.from_rao(int(current_price * 1e9))

return map_
52 changes: 32 additions & 20 deletions bittensor_cli/src/commands/stake/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,12 @@ async def stake_extrinsic(
# Get subnet data and stake information for coldkey
chain_head = await subtensor.substrate.get_chain_head()
_all_subnets, _stake_info, current_wallet_balance = await asyncio.gather(
subtensor.all_subnets(),
subtensor.all_subnets(block_hash=chain_head),
subtensor.get_stake_for_coldkey(
coldkey_ss58=wallet.coldkeypub.ss58_address,
block_hash=chain_head,
),
subtensor.get_balance(wallet.coldkeypub.ss58_address),
subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=chain_head),
)
all_subnets = {di.netuid: di for di in _all_subnets}

Expand Down Expand Up @@ -307,6 +307,7 @@ async def stake_extrinsic(
return False
remaining_wallet_balance -= amount_to_stake

# TODO this should be asyncio gathered before the for loop
stake_fee = await subtensor.get_stake_fee(
origin_hotkey_ss58=None,
origin_netuid=None,
Expand All @@ -318,14 +319,20 @@ async def stake_extrinsic(
)

# Calculate slippage
try:
received_amount, slippage_pct, slippage_pct_float, rate = (
_calculate_slippage(subnet_info, amount_to_stake, stake_fee)
)
except ValueError:
return False

max_slippage = max(slippage_pct_float, max_slippage)
# TODO: Update for V3, slippage calculation is significantly different in v3
# try:
# received_amount, slippage_pct, slippage_pct_float, rate = (
# _calculate_slippage(subnet_info, amount_to_stake, stake_fee)
# )
# except ValueError:
# return False
#
# max_slippage = max(slippage_pct_float, max_slippage)

# Temporary workaround - calculations without slippage
current_price_float = float(subnet_info.price.tao)
rate = 1.0 / current_price_float
received_amount = rate * amount_to_stake

# Add rows for the table
base_row = [
Expand All @@ -336,19 +343,19 @@ async def stake_extrinsic(
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
str(received_amount.set_unit(netuid)), # received
str(stake_fee), # fee
str(slippage_pct), # slippage
# str(slippage_pct), # slippage
]

# If we are staking safe, add price tolerance
if safe_staking:
if subnet_info.is_dynamic:
rate = amount_to_stake.rao / received_amount.rao
_rate_with_tolerance = rate * (
1 + rate_tolerance
price_with_tolerance = current_price_float * (1 + rate_tolerance)
_rate_with_tolerance = (
1.0 / price_with_tolerance
) # Rate only for display
rate_with_tolerance = f"{_rate_with_tolerance:.4f}"
price_with_tolerance = Balance.from_tao(
_rate_with_tolerance
price_with_tolerance
).rao # Actual price to pass to extrinsic
else:
rate_with_tolerance = "1"
Expand Down Expand Up @@ -581,9 +588,10 @@ def _define_stake_table(
justify="center",
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
)
table.add_column(
"Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
)
# TODO: Uncomment when slippage is reimplemented for v3
# table.add_column(
# "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
# )

if safe_staking:
table.add_column(
Expand Down Expand Up @@ -628,8 +636,8 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b
- [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are staking to.
- [bold white]Amount[/bold white]: The TAO you are staking into this subnet onto this hotkey.
- [bold white]Rate[/bold white]: The rate of exchange between your TAO and the subnet's stake.
- [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage.
- [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root)."""
- [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage."""
# - [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root)."""

safe_staking_description = """
- [bold white]Rate Tolerance[/bold white]: Maximum acceptable alpha rate. If the rate exceeds this tolerance, the transaction will be limited or rejected.
Expand All @@ -654,6 +662,9 @@ def _calculate_slippage(
- slippage_str: Formatted slippage percentage string
- slippage_float: Raw slippage percentage value
- rate: Exchange rate string

TODO: Update to v3. This method only works for protocol-liquidity-only
mode (user liquidity disabled)
"""
amount_after_fee = amount - stake_fee

Expand All @@ -670,6 +681,7 @@ def _calculate_slippage(
slippage_str = f"{slippage_pct_float:.4f} %"
rate = f"{(1 / subnet_info.price.tao or 1):.4f}"
else:
# TODO: Fix this. Slippage is always zero for static networks.
slippage_pct_float = (
100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
)
Expand Down
83 changes: 35 additions & 48 deletions bittensor_cli/src/commands/stake/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ def define_table(
style=COLOR_PALETTE["POOLS"]["RATE"],
justify="center",
)
defined_table.add_column(
f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
footer_style="overline white",
style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
justify="right",
footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}"
if not verbose
else f"{total_swapped_tao_value_}",
)
# defined_table.add_column(
# f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
# footer_style="overline white",
# style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
# justify="right",
# footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}"
# if not verbose
# else f"{total_swapped_tao_value_}",
# )
defined_table.add_column(
"[white]Registered",
style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
Expand Down Expand Up @@ -168,25 +168,17 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]):
tao_value_ = pool.alpha_to_tao(alpha_value)
total_tao_value_ += tao_value_

# Swapped TAO value and slippage cell
swapped_tao_value_, _, slippage_percentage_ = (
pool.alpha_to_tao_with_slippage(substake_.stake)
)
total_swapped_tao_value_ += swapped_tao_value_

# Slippage percentage cell
if pool.is_dynamic:
slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
else:
slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
# TAO value cell
tao_value_ = pool.alpha_to_tao(substake_.stake)
total_swapped_tao_value_ += tao_value_

if netuid == 0:
swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_percentage})"
swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]"
else:
swap_value = (
f"τ {millify_tao(swapped_tao_value_.tao)} ({slippage_percentage})"
f"τ {millify_tao(tao_value_.tao)}"
if not verbose
else f"{swapped_tao_value_} ({slippage_percentage})"
else f"{tao_value_}"
)

# Per block emission cell
Expand Down Expand Up @@ -214,7 +206,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]):
else f"{symbol} {stake_value}", # Stake (a)
f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a)
# f"τ {millify_tao(tao_ownership.tao)}" if not verbose else f"{tao_ownership}", # TAO equiv
swap_value, # Swap(α) -> τ
# swap_value, # Swap(α) -> τ
"YES"
if substake_.is_registered
else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered
Expand All @@ -232,7 +224,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]):
"value": tao_value_.tao,
"stake_value": substake_.stake.tao,
"rate": pool.price.tao,
"swap_value": swap_value,
# "swap_value": swap_value,
"registered": True if substake_.is_registered else False,
"emission": {
"alpha": per_block_emission,
Expand Down Expand Up @@ -317,9 +309,7 @@ def format_cell(
alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid)
tao_value_ = pool.alpha_to_tao(alpha_value)
total_tao_value_ += tao_value_
swapped_tao_value_, slippage, slippage_pct = (
pool.alpha_to_tao_with_slippage(substake_.stake)
)
swapped_tao_value_ = pool.alpha_to_tao(substake_.stake)
total_swapped_tao_value_ += swapped_tao_value_

# Store current values for future delta tracking
Expand Down Expand Up @@ -364,19 +354,16 @@ def format_cell(
)

if netuid != 0:
swap_cell = (
format_cell(
swapped_tao_value_.tao,
prev.get("swapped_value"),
unit="τ",
unit_first_=True,
precision=4,
millify=True if not verbose else False,
)
+ f" ({slippage_pct:.2f}%)"
swap_cell = format_cell(
swapped_tao_value_.tao,
prev.get("swapped_value"),
unit="τ",
unit_first_=True,
precision=4,
millify=True if not verbose else False,
)
else:
swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_pct}%)"
swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]"

emission_value = substake_.emission.tao / (pool.tempo or 1)
emission_cell = format_cell(
Expand Down Expand Up @@ -408,7 +395,7 @@ def format_cell(
exchange_cell, # Exchange value
stake_cell, # Stake amount
rate_cell, # Rate
swap_cell, # Swap value with slippage
# swap_cell, # Swap value
"YES"
if substake_.is_registered
else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status
Expand Down Expand Up @@ -591,12 +578,12 @@ def format_cell(
f"Wallet:\n"
f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n"
f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
f" Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
# f"\n Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
)
dict_output["free_balance"] = balance.tao
dict_output["total_tao_value"] = all_hks_tao_value.tao
dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao
# dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao
if json_output:
json_console.print(json.dumps(dict_output))
if not sub_stakes:
Expand Down Expand Up @@ -658,12 +645,12 @@ def format_cell(
),
(
"[bold tan]Exchange Value (α x τ/α)[/bold tan]",
"This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].",
),
(
"[bold tan]Swap (α → τ)[/bold tan]",
"This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].",
"This is the potential τ you will receive if you unstake from this hotkey now on this subnet. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].",
),
# (
# "[bold tan]Swap (α → τ)[/bold tan]",
# "This is the τ you will receive if you unstake from this hotkey now on this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].",
# ),
(
"[bold tan]Registered[/bold tan]",
"Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].",
Expand Down
Loading
Loading