Skip to content
Closed
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
45 changes: 44 additions & 1 deletion bittensor_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5439,6 +5439,9 @@ def stake_transfer(
announce_only: bool = Options.announce_only,
prompt: bool = Options.prompt,
decline: bool = Options.decline,
rate_tolerance: Optional[float] = Options.rate_tolerance,
safe_staking: Optional[bool] = Options.safe_staking,
allow_partial_stake: Optional[bool] = Options.allow_partial_stake,
quiet: bool = Options.quiet,
verbose: bool = Options.verbose,
json_output: bool = Options.json_output,
Expand Down Expand Up @@ -5477,6 +5480,15 @@ def stake_transfer(

Transfer stake without MEV protection:
[green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --amount 100 --no-mev-protection

Safe transfer with rate tolerance of 10%:
[green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --amount 100 --safe --tolerance 0.1

Allow partial stake if rates change with tolerance of 10%:
[green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --amount 100 --safe --partial --tolerance 0.1

Unsafe transfer with no rate protection:
[green]$[/green] btcli stake transfer --origin-netuid 1 --dest-netuid 2 --amount 100 --unsafe
"""
self.verbosity_handler(quiet, verbose, json_output, prompt, decline)
proxy = self.is_valid_proxy_name_or_ss58(proxy, announce_only)
Expand Down Expand Up @@ -5565,6 +5577,11 @@ def stake_transfer(
dest_netuid = IntPrompt.ask(
"Enter the [blue]destination subnet[/blue] (netuid)"
)
safe_staking_transfer = self.ask_safe_staking(safe_staking)
if safe_staking_transfer:
rate_tolerance_val = self.ask_rate_tolerance(rate_tolerance)
allow_partial_stake_val = self.ask_partial_stake(allow_partial_stake)

logger.debug(
"args:\n"
f"network: {network}\n"
Expand All @@ -5576,7 +5593,10 @@ def stake_transfer(
f"amount: {amount}\n"
f"era: {period}\n"
f"stake_all: {stake_all}\n"
f"mev_protection: {mev_protection}"
f"mev_protection: {mev_protection}\n"
f"safe_staking: {safe_staking_transfer}\n"
f"rate_tolerance: {rate_tolerance_val}\n"
f"allow_partial_stake: {allow_partial_stake_val}\n"
f"proxy: {proxy}"
)
result, ext_id = self._run_command(
Expand Down Expand Up @@ -5644,6 +5664,9 @@ def stake_swap(
wait_for_inclusion: bool = Options.wait_for_inclusion,
wait_for_finalization: bool = Options.wait_for_finalization,
mev_protection: bool = Options.mev_protection,
rate_tolerance: Optional[float] = Options.rate_tolerance,
safe_staking: Optional[bool] = Options.safe_staking,
allow_partial_stake: Optional[bool] = Options.allow_partial_stake,
quiet: bool = Options.quiet,
verbose: bool = Options.verbose,
json_output: bool = Options.json_output,
Expand All @@ -5670,6 +5693,15 @@ def stake_swap(

2. Swap stake without MEV protection:
[green]$[/green] btcli stake swap --origin-netuid 1 --dest-netuid 2 --amount 100 --no-mev-protection

3. Safe swapping with rate tolerance of 10%:
[green]$[/green] btcli stake swap --origin-netuid 1 --dest-netuid 2 --amount 100 --safe --tolerance 0.1

4. Allow partial stake if rates change with tolerance of 10%:
[green]$[/green] btcli stake swap --origin-netuid 1 --dest-netuid 2 --amount 100 --safe --partial --tolerance 0.1

5. Unsafe swapping with no rate protection:
[green]$[/green] btcli stake swap --origin-netuid 1 --dest-netuid 2 --amount 100 --unsafe
"""
self.verbosity_handler(quiet, verbose, json_output, prompt, decline)
proxy = self.is_valid_proxy_name_or_ss58(proxy, announce_only)
Expand All @@ -5686,6 +5718,11 @@ def stake_swap(
validate=WV.WALLET_AND_HOTKEY,
)

safe_staking = self.ask_safe_staking(safe_staking)
if safe_staking:
rate_tolerance = self.ask_rate_tolerance(rate_tolerance)
allow_partial_stake = self.ask_partial_stake(allow_partial_stake)

interactive_selection = False
if origin_netuid is None and dest_netuid is None and not amount:
interactive_selection = True
Expand Down Expand Up @@ -5714,6 +5751,9 @@ def stake_swap(
f"wait_for_inclusion: {wait_for_inclusion}\n"
f"wait_for_finalization: {wait_for_finalization}\n"
f"mev_protection: {mev_protection}\n"
f"safe_staking: {safe_staking}\n"
f"rate_tolerance: {rate_tolerance}\n"
f"allow_partial_stake: {allow_partial_stake}\n"
)
result, ext_id = self._run_command(
move_stake.swap_stake(
Expand All @@ -5732,6 +5772,9 @@ def stake_swap(
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
mev_protection=mev_protection,
safe_staking=safe_staking,
allow_partial_stake=allow_partial_stake,
rate_tolerance=rate_tolerance,
)
)
if json_output:
Expand Down
53 changes: 43 additions & 10 deletions bittensor_cli/src/commands/stake/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,9 @@ async def swap_stake(
wait_for_inclusion: bool = True,
wait_for_finalization: bool = False,
mev_protection: bool = True,
safe_staking: bool = False,
allow_partial_stake: bool = False,
rate_tolerance: float = 0.005,
) -> tuple[bool, str]:
"""Swaps stake between subnets while keeping the same coldkey-hotkey pair ownership.

Expand All @@ -901,6 +904,12 @@ async def swap_stake(
wait_for_inclusion: If true, waits for the transaction to be included in a block.
wait_for_finalization: If true, waits for the transaction to be finalized.
mev_protection: If true, will encrypt the extrinsic behind the mev protection shield.
safe_swapping: If true, enables price safety checks to protect against fluctuating prices.
allow_partial_stake: If true and safe_swapping is enabled, allows partial stake swaps when the full amount
would exceed the price tolerance.
rate_tolerance: The maximum allowed increase in the price ratio between subnets
(origin_price/destination_price). For example, 0.005 = 0.5% maximum increase. Only used when
safe_swapping is True.

Returns:
(success, extrinsic_identifier):
Expand Down Expand Up @@ -958,16 +967,40 @@ async def swap_stake(
)
return False, ""

call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="swap_stake",
call_params={
"hotkey": hotkey_ss58,
"origin_netuid": origin_netuid,
"destination_netuid": destination_netuid,
"alpha_amount": amount_to_swap.rao,
},
)
# Build call with or without safe swapping
if safe_staking:
# Get subnet prices to calculate rate ratio with tolerance
origin_subnet, destination_subnet = await asyncio.gather(
subtensor.subnet(origin_netuid),
subtensor.subnet(destination_netuid),
)
swap_rate_ratio = origin_subnet.price.rao / destination_subnet.price.rao
swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 - rate_tolerance)
limit_price_rao = Balance.from_tao(swap_rate_ratio_with_tolerance).rao

call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="swap_stake_limit",
call_params={
"hotkey": hotkey_ss58,
"origin_netuid": origin_netuid,
"destination_netuid": destination_netuid,
"alpha_amount": amount_to_swap.rao,
"limit_price": limit_price_rao,
"allow_partial": allow_partial_stake,
},
)
else:
call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="swap_stake",
call_params={
"hotkey": hotkey_ss58,
"origin_netuid": origin_netuid,
"destination_netuid": destination_netuid,
"alpha_amount": amount_to_swap.rao,
},
)
sim_swap, extrinsic_fee, next_nonce = await asyncio.gather(
subtensor.sim_swap(
origin_netuid=origin_netuid,
Expand Down
Loading