Skip to content

Commit 4abd5be

Browse files
authored
Merge pull request #389 from opentensor/feat/dynamic-staking-fee
Feat/dynamic staking fee
2 parents ae25df0 + d762c9a commit 4abd5be

File tree

4 files changed

+224
-28
lines changed

4 files changed

+224
-28
lines changed

bittensor_cli/src/bittensor/subtensor_interface.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,3 +1435,69 @@ async def get_owned_hotkeys(
14351435
)
14361436

14371437
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1438+
1439+
async def get_stake_fee(
1440+
self,
1441+
origin_hotkey_ss58: Optional[str],
1442+
origin_netuid: Optional[int],
1443+
origin_coldkey_ss58: str,
1444+
destination_hotkey_ss58: Optional[str],
1445+
destination_netuid: Optional[int],
1446+
destination_coldkey_ss58: str,
1447+
amount: int,
1448+
block_hash: Optional[str] = None,
1449+
) -> Balance:
1450+
"""
1451+
Calculates the fee for a staking operation.
1452+
1453+
:param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake)
1454+
:param origin_netuid: Netuid of source subnet (None for new stake)
1455+
:param origin_coldkey_ss58: SS58 address of source coldkey
1456+
:param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake)
1457+
:param destination_netuid: Netuid of destination subnet (None for removing stake)
1458+
:param destination_coldkey_ss58: SS58 address of destination coldkey
1459+
:param amount: Amount of stake to transfer in RAO
1460+
:param block_hash: Optional block hash at which to perform the calculation
1461+
1462+
:return: The calculated stake fee as a Balance object
1463+
1464+
When to use None:
1465+
1466+
1. Adding new stake (default fee):
1467+
- origin_hotkey_ss58 = None
1468+
- origin_netuid = None
1469+
- All other fields required
1470+
1471+
2. Removing stake (default fee):
1472+
- destination_hotkey_ss58 = None
1473+
- destination_netuid = None
1474+
- All other fields required
1475+
1476+
For all other operations, no None values - provide all parameters:
1477+
3. Moving between subnets
1478+
4. Moving between hotkeys
1479+
5. Moving between coldkeys
1480+
"""
1481+
1482+
origin = None
1483+
if origin_hotkey_ss58 is not None and origin_netuid is not None:
1484+
origin = (origin_hotkey_ss58, origin_netuid)
1485+
1486+
destination = None
1487+
if destination_hotkey_ss58 is not None and destination_netuid is not None:
1488+
destination = (destination_hotkey_ss58, destination_netuid)
1489+
1490+
result = await self.query_runtime_api(
1491+
runtime_api="StakeInfoRuntimeApi",
1492+
method="get_stake_fee",
1493+
params=[
1494+
origin,
1495+
origin_coldkey_ss58,
1496+
destination,
1497+
destination_coldkey_ss58,
1498+
amount,
1499+
],
1500+
block_hash=block_hash,
1501+
)
1502+
1503+
return Balance.from_rao(result)

bittensor_cli/src/commands/stake/add.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,24 @@ async def stake_extrinsic(
282282
return False
283283
remaining_wallet_balance -= amount_to_stake
284284

285-
# Calculate slippage
286-
received_amount, slippage_pct, slippage_pct_float, rate = (
287-
_calculate_slippage(subnet_info, amount_to_stake)
285+
stake_fee = await subtensor.get_stake_fee(
286+
origin_hotkey_ss58=None,
287+
origin_netuid=None,
288+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
289+
destination_hotkey_ss58=hotkey[1],
290+
destination_netuid=netuid,
291+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
292+
amount=amount_to_stake.rao,
288293
)
294+
295+
# Calculate slippage
296+
try:
297+
received_amount, slippage_pct, slippage_pct_float, rate = (
298+
_calculate_slippage(subnet_info, amount_to_stake, stake_fee)
299+
)
300+
except ValueError:
301+
return False
302+
289303
max_slippage = max(slippage_pct_float, max_slippage)
290304

291305
# Add rows for the table
@@ -296,6 +310,7 @@ async def stake_extrinsic(
296310
str(rate)
297311
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
298312
str(received_amount.set_unit(netuid)), # received
313+
str(stake_fee), # fee
299314
str(slippage_pct), # slippage
300315
]
301316

@@ -531,6 +546,11 @@ def _define_stake_table(
531546
justify="center",
532547
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
533548
)
549+
table.add_column(
550+
"Fee (τ)",
551+
justify="center",
552+
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
553+
)
534554
table.add_column(
535555
"Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
536556
)
@@ -585,29 +605,41 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b
585605

586606

587607
def _calculate_slippage(
588-
subnet_info, amount: Balance
608+
subnet_info, amount: Balance, stake_fee: Balance
589609
) -> tuple[Balance, str, float, str]:
590610
"""Calculate slippage when adding stake.
591611
592612
Args:
593613
subnet_info: Subnet dynamic info
594614
amount: Amount being staked
615+
stake_fee: Transaction fee for the stake operation
595616
596617
Returns:
597618
tuple containing:
598-
- received_amount: Amount received after slippage
619+
- received_amount: Amount received after slippage and fees
599620
- slippage_str: Formatted slippage percentage string
600621
- slippage_float: Raw slippage percentage value
622+
- rate: Exchange rate string
601623
"""
602-
received_amount, _, slippage_pct_float = subnet_info.tao_to_alpha_with_slippage(
603-
amount
604-
)
624+
amount_after_fee = amount - stake_fee
625+
626+
if amount_after_fee < 0:
627+
print_error("You don't have enough balance to cover the stake fee.")
628+
raise ValueError()
629+
630+
received_amount, _, _ = subnet_info.tao_to_alpha_with_slippage(amount_after_fee)
631+
605632
if subnet_info.is_dynamic:
633+
ideal_amount = subnet_info.tao_to_alpha(amount)
634+
total_slippage = ideal_amount - received_amount
635+
slippage_pct_float = 100 * (total_slippage.tao / ideal_amount.tao)
606636
slippage_str = f"{slippage_pct_float:.4f} %"
607637
rate = f"{(1 / subnet_info.price.tao or 1):.4f}"
608638
else:
609-
slippage_pct_float = 0
610-
slippage_str = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]N/A[/{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]"
639+
slippage_pct_float = (
640+
100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
641+
)
642+
slippage_str = f"{slippage_pct_float:.4f} %"
611643
rate = "1"
612644

613645
return received_amount, slippage_str, slippage_pct_float, rate

bittensor_cli/src/commands/stake/move.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,22 @@ async def display_stake_movement_cross_subnets(
3232
origin_hotkey: str,
3333
destination_hotkey: str,
3434
amount_to_move: Balance,
35+
stake_fee: Balance,
3536
) -> tuple[Balance, float, str, str]:
3637
"""Calculate and display slippage information"""
3738

3839
if origin_netuid == destination_netuid:
3940
subnet = await subtensor.subnet(origin_netuid)
4041
received_amount_tao = subnet.alpha_to_tao(amount_to_move)
41-
received_amount_tao -= MIN_STAKE_FEE
42+
received_amount_tao -= stake_fee
4243

4344
if received_amount_tao < Balance.from_tao(0):
4445
print_error("Not enough Alpha to pay the transaction fee.")
4546
raise ValueError
4647

4748
received_amount = subnet.tao_to_alpha(received_amount_tao)
4849
slippage_pct_float = (
49-
100 * float(MIN_STAKE_FEE) / float(MIN_STAKE_FEE + received_amount_tao)
50+
100 * float(stake_fee) / float(stake_fee + received_amount_tao)
5051
if received_amount_tao != 0
5152
else 0
5253
)
@@ -67,7 +68,7 @@ async def display_stake_movement_cross_subnets(
6768
received_amount_tao, _, _ = dynamic_origin.alpha_to_tao_with_slippage(
6869
amount_to_move
6970
)
70-
received_amount_tao -= MIN_STAKE_FEE
71+
received_amount_tao -= stake_fee
7172
received_amount, _, _ = dynamic_destination.tao_to_alpha_with_slippage(
7273
received_amount_tao
7374
)
@@ -135,6 +136,11 @@ async def display_stake_movement_cross_subnets(
135136
justify="center",
136137
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
137138
)
139+
table.add_column(
140+
"Fee (τ)",
141+
justify="center",
142+
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
143+
)
138144
table.add_column(
139145
"slippage",
140146
justify="center",
@@ -149,13 +155,11 @@ async def display_stake_movement_cross_subnets(
149155
str(amount_to_move),
150156
price_str,
151157
str(received_amount),
158+
str(stake_fee),
152159
str(slippage_pct),
153160
)
154161

155162
console.print(table)
156-
# console.print(
157-
# f"[dim]A fee of {MIN_STAKE_FEE} applies.[/dim]"
158-
# )
159163

160164
# Display slippage warning if necessary
161165
if slippage_pct_float > 5:
@@ -513,6 +517,16 @@ async def move_stake(
513517
)
514518
return False
515519

520+
stake_fee = await subtensor.get_stake_fee(
521+
origin_hotkey_ss58=origin_hotkey,
522+
origin_netuid=origin_netuid,
523+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
524+
destination_hotkey_ss58=destination_hotkey,
525+
destination_netuid=destination_netuid,
526+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
527+
amount=amount_to_move_as_balance.rao,
528+
)
529+
516530
# Slippage warning
517531
if prompt:
518532
try:
@@ -523,6 +537,7 @@ async def move_stake(
523537
origin_hotkey=origin_hotkey,
524538
destination_hotkey=destination_hotkey,
525539
amount_to_move=amount_to_move_as_balance,
540+
stake_fee=stake_fee,
526541
)
527542
except ValueError:
528543
return False
@@ -686,6 +701,16 @@ async def transfer_stake(
686701
)
687702
return False
688703

704+
stake_fee = await subtensor.get_stake_fee(
705+
origin_hotkey_ss58=origin_hotkey,
706+
origin_netuid=origin_netuid,
707+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
708+
destination_hotkey_ss58=origin_hotkey,
709+
destination_netuid=dest_netuid,
710+
destination_coldkey_ss58=dest_coldkey_ss58,
711+
amount=amount_to_transfer.rao,
712+
)
713+
689714
# Slippage warning
690715
if prompt:
691716
try:
@@ -696,6 +721,7 @@ async def transfer_stake(
696721
origin_hotkey=origin_hotkey,
697722
destination_hotkey=origin_hotkey,
698723
amount_to_move=amount_to_transfer,
724+
stake_fee=stake_fee,
699725
)
700726
except ValueError:
701727
return False
@@ -844,6 +870,16 @@ async def swap_stake(
844870
)
845871
return False
846872

873+
stake_fee = await subtensor.get_stake_fee(
874+
origin_hotkey_ss58=hotkey_ss58,
875+
origin_netuid=origin_netuid,
876+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
877+
destination_hotkey_ss58=hotkey_ss58,
878+
destination_netuid=destination_netuid,
879+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
880+
amount=amount_to_swap.rao,
881+
)
882+
847883
# Slippage warning
848884
if prompt:
849885
try:
@@ -854,6 +890,7 @@ async def swap_stake(
854890
origin_hotkey=hotkey_ss58,
855891
destination_hotkey=hotkey_ss58,
856892
amount_to_move=amount_to_swap,
893+
stake_fee=stake_fee,
857894
)
858895
except ValueError:
859896
return False

0 commit comments

Comments
 (0)