Is your feature request related to a problem? Please describe.
Subtensor currently exposes two complementary stake-transition calls, but no call combines their capabilities:
move_stake can change the destination hotkey, including across subnets, but it has no limit_price.
swap_stake_limit enforces an on-chain price bound, but it preserves the same hotkey.
This leaves wallets and swap interfaces without a canonical way to atomically move Alpha from one subnet/validator position directly into another subnet/validator position while retaining slippage protection.
A frontend can try to compose the existing calls with:
utility.batch_all([
move_stake(source_hotkey, target_hotkey, origin_netuid, origin_netuid, amount),
swap_stake_limit(target_hotkey, origin_netuid, destination_netuid, amount, limit_price, false),
])
However, the same-subnet move_stake passes through validator share accounting. The move debits the exact integer amount from the source position, but SafeFloat/share representation can make the newly minted target-hotkey position immediately readable as amount - 1 RAO.
This was deterministically reproduced against Finney runtime spec 445 for a partial SN28 → SN60 route:
requested source amount: 12,266,631,756 Alpha RAO
post-move executable target amount: 12,266,631,755 Alpha RAO
difference: 1 Alpha RAO
The following swap_stake_limit(..., 12_266_631_756, ...) would then fail NotEnoughStakeToWithdraw, rolling back the batch.
The available application-level workarounds all compromise the intended operation:
- Swap
amount - 1 and add a third remove_stake_limit(..., 1, 0, false) cleanup call.
- Leave a one-RAO temporary position on the origin subnet.
- Silently adjust the user's exact amount, which is especially unsuitable for MAX.
- Block the route and ask the user to change the amount or validator.
- Swap first and move the finalized output in a second transaction, losing single-transaction atomicity.
- Use cross-subnet
move_stake directly, losing the on-chain price bound.
This is not a utility.batch_all problem: batch calls are statically SCALE-encoded and cannot pass the first call's runtime result into the second call.
Describe the solution you'd like
Please expose a price-protected stake move that accepts different origin and destination hotkeys, for example:
move_stake_limit(
origin_hotkey,
destination_hotkey,
origin_netuid,
destination_netuid,
alpha_amount,
limit_price,
allow_partial,
)
Semantically, this would combine the destination-hotkey support of move_stake with the price-bound behavior of swap_stake_limit. It should debit the source position, perform the protected cross-subnet transition, and credit the destination subnet's selected hotkey directly, without creating an intermediate position on the origin subnet.
The existing internal transition_stake_internal already accepts distinct origin/destination hotkeys together with an optional limit price and partial-execution setting. The missing capability appears to be the public dispatch surface, corresponding validation/weights, and integration coverage.
For genuine full-position/MAX execution, please also consider a full variant analogous to remove_stake_full_limit, for example:
move_stake_full_limit(
origin_hotkey,
destination_hotkey,
origin_netuid,
destination_netuid,
limit_price,
)
This variant should resolve the complete eligible source position during dispatch rather than requiring a quote-time u64 amount. If the full amount cannot satisfy the price bound or stake-lock invariants, it should fail atomically.
Suggested acceptance criteria:
- Different origin and destination hotkeys.
- Different origin and destination netuids.
- On-chain price-limit enforcement equivalent to
swap_stake_limit.
- Exact-input execution when
allow_partial = false.
- A full-position/MAX path that resolves its amount during dispatch.
- Direct credit to the requested destination hotkey, with no temporary origin-subnet target position.
- Correct single-route fee treatment.
- Existing minimum-stake, availability, collateral-lock, and root-hold invariants preserved.
- Events identify both hotkeys, both netuids, and the executed transition.
- Runtime benchmarks, signed-extension validation, proxy filtering, and integration tests cover the new calls.
Naming and whether exact/full behavior is exposed as two calls or one call with an explicit amount mode are open to maintainers.
Describe alternatives you've considered
The application-level alternatives are listed above. The only currently available one-transaction route that remains price-protected for an arbitrary destination validator requires pre-moving the stake and handling validator-share rounding with extra calls or route rejection.
A single cross-subnet move_stake is operationally simpler, but MEV privacy is not a substitute for an on-chain price bound: ordinary pool movement before inclusion can still violate the user's slippage tolerance.
Additional context
Relevant implementation areas:
pallets/subtensor/src/staking/move_stake.rs: do_move_stake, do_swap_stake_limit, and transition_stake_internal.
primitives/share-pool/src/lib.rs: SharePool::update_value_for_one and floored represented-value reads.
The goal is not to remove validator share accounting or promise that raw AMM output and represented stake can never differ by one RAO. The goal is to prevent that representation detail from making an intermediate frontend-composed route fail or require a separate one-RAO cleanup operation.
Is your feature request related to a problem? Please describe.
Subtensor currently exposes two complementary stake-transition calls, but no call combines their capabilities:
move_stakecan change the destination hotkey, including across subnets, but it has nolimit_price.swap_stake_limitenforces an on-chain price bound, but it preserves the same hotkey.This leaves wallets and swap interfaces without a canonical way to atomically move Alpha from one subnet/validator position directly into another subnet/validator position while retaining slippage protection.
A frontend can try to compose the existing calls with:
However, the same-subnet
move_stakepasses through validator share accounting. The move debits the exact integeramountfrom the source position, but SafeFloat/share representation can make the newly minted target-hotkey position immediately readable asamount - 1 RAO.This was deterministically reproduced against Finney runtime spec 445 for a partial SN28 → SN60 route:
The following
swap_stake_limit(..., 12_266_631_756, ...)would then failNotEnoughStakeToWithdraw, rolling back the batch.The available application-level workarounds all compromise the intended operation:
amount - 1and add a thirdremove_stake_limit(..., 1, 0, false)cleanup call.move_stakedirectly, losing the on-chain price bound.This is not a
utility.batch_allproblem: batch calls are statically SCALE-encoded and cannot pass the first call's runtime result into the second call.Describe the solution you'd like
Please expose a price-protected stake move that accepts different origin and destination hotkeys, for example:
Semantically, this would combine the destination-hotkey support of
move_stakewith the price-bound behavior ofswap_stake_limit. It should debit the source position, perform the protected cross-subnet transition, and credit the destination subnet's selected hotkey directly, without creating an intermediate position on the origin subnet.The existing internal
transition_stake_internalalready accepts distinct origin/destination hotkeys together with an optional limit price and partial-execution setting. The missing capability appears to be the public dispatch surface, corresponding validation/weights, and integration coverage.For genuine full-position/MAX execution, please also consider a full variant analogous to
remove_stake_full_limit, for example:This variant should resolve the complete eligible source position during dispatch rather than requiring a quote-time
u64amount. If the full amount cannot satisfy the price bound or stake-lock invariants, it should fail atomically.Suggested acceptance criteria:
swap_stake_limit.allow_partial = false.Naming and whether exact/full behavior is exposed as two calls or one call with an explicit amount mode are open to maintainers.
Describe alternatives you've considered
The application-level alternatives are listed above. The only currently available one-transaction route that remains price-protected for an arbitrary destination validator requires pre-moving the stake and handling validator-share rounding with extra calls or route rejection.
A single cross-subnet
move_stakeis operationally simpler, but MEV privacy is not a substitute for an on-chain price bound: ordinary pool movement before inclusion can still violate the user's slippage tolerance.Additional context
Relevant implementation areas:
pallets/subtensor/src/staking/move_stake.rs:do_move_stake,do_swap_stake_limit, andtransition_stake_internal.primitives/share-pool/src/lib.rs:SharePool::update_value_for_oneand floored represented-value reads.The goal is not to remove validator share accounting or promise that raw AMM output and represented stake can never differ by one RAO. The goal is to prevent that representation detail from making an intermediate frontend-composed route fail or require a separate one-RAO cleanup operation.