Skip to content

Commit 3349cf6

Browse files
committed
stake remove
1 parent d64edf2 commit 3349cf6

File tree

2 files changed

+96
-77
lines changed

2 files changed

+96
-77
lines changed

bittensor_cli/cli.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3192,6 +3192,7 @@ def stake_remove(
31923192
),
31933193
quiet: bool = Options.quiet,
31943194
verbose: bool = Options.verbose,
3195+
json_output: bool = Options.json_output,
31953196
):
31963197
"""
31973198
Unstake TAO from one or more hotkeys and transfer them back to the user's coldkey wallet.
@@ -3223,7 +3224,7 @@ def stake_remove(
32233224
• [blue]--tolerance[/blue]: Max allowed rate change (0.05 = 5%)
32243225
• [blue]--partial[/blue]: Complete partial unstake if rates exceed tolerance
32253226
"""
3226-
self.verbosity_handler(quiet, verbose)
3227+
self.verbosity_handler(quiet, verbose, json_output)
32273228
if not unstake_all and not unstake_all_alpha:
32283229
safe_staking = self.ask_safe_staking(safe_staking)
32293230
if safe_staking:
@@ -3235,7 +3236,8 @@ def stake_remove(
32353236
[hotkey_ss58_address, include_hotkeys, exclude_hotkeys, all_hotkeys]
32363237
):
32373238
print_error(
3238-
"Interactive mode cannot be used with hotkey selection options like --include-hotkeys, --exclude-hotkeys, --all-hotkeys, or --hotkey."
3239+
"Interactive mode cannot be used with hotkey selection options like "
3240+
"--include-hotkeys, --exclude-hotkeys, --all-hotkeys, or --hotkey."
32393241
)
32403242
raise typer.Exit()
32413243

@@ -3372,6 +3374,7 @@ def stake_remove(
33723374
include_hotkeys=include_hotkeys,
33733375
exclude_hotkeys=exclude_hotkeys,
33743376
prompt=prompt,
3377+
json_output=json_output,
33753378
)
33763379
)
33773380
elif (
@@ -3426,6 +3429,7 @@ def stake_remove(
34263429
safe_staking=safe_staking,
34273430
rate_tolerance=rate_tolerance,
34283431
allow_partial_stake=allow_partial_stake,
3432+
json_output=json_output,
34293433
)
34303434
)
34313435

bittensor_cli/src/commands/stake/remove.py

Lines changed: 90 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import json
23
from functools import partial
34

45
from typing import TYPE_CHECKING, Optional
@@ -20,6 +21,7 @@
2021
format_error_message,
2122
group_subnets,
2223
unlock_key,
24+
json_console,
2325
)
2426

2527
if TYPE_CHECKING:
@@ -41,6 +43,7 @@ async def unstake(
4143
safe_staking: bool,
4244
rate_tolerance: float,
4345
allow_partial_stake: bool,
46+
json_output: bool,
4447
):
4548
"""Unstake from hotkey(s)."""
4649
unstake_all_from_hk = False
@@ -241,8 +244,11 @@ async def unstake(
241244
base_unstake_op["price_with_tolerance"] = price_with_tolerance
242245
base_table_row.extend(
243246
[
244-
f"{rate_with_tolerance:.4f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}", # Rate with tolerance
245-
f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]", # Partial unstake
247+
# Rate with tolerance
248+
f"{rate_with_tolerance:.4f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}",
249+
# Partial unstake
250+
f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
251+
f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
246252
]
247253
)
248254

@@ -273,45 +279,45 @@ async def unstake(
273279
if not unlock_key(wallet).success:
274280
return False
275281

282+
successes = []
276283
with console.status("\n:satellite: Performing unstaking operations...") as status:
277-
if safe_staking:
278-
for op in unstake_operations:
279-
if op["netuid"] == 0:
280-
await _unstake_extrinsic(
281-
wallet=wallet,
282-
subtensor=subtensor,
283-
netuid=op["netuid"],
284-
amount=op["amount_to_unstake"],
285-
current_stake=op["current_stake_balance"],
286-
hotkey_ss58=op["hotkey_ss58"],
287-
status=status,
288-
)
289-
else:
290-
await _safe_unstake_extrinsic(
291-
wallet=wallet,
292-
subtensor=subtensor,
293-
netuid=op["netuid"],
294-
amount=op["amount_to_unstake"],
295-
current_stake=op["current_stake_balance"],
296-
hotkey_ss58=op["hotkey_ss58"],
297-
price_limit=op["price_with_tolerance"],
298-
allow_partial_stake=allow_partial_stake,
299-
status=status,
300-
)
301-
else:
302-
for op in unstake_operations:
303-
await _unstake_extrinsic(
304-
wallet=wallet,
305-
subtensor=subtensor,
306-
netuid=op["netuid"],
307-
amount=op["amount_to_unstake"],
308-
current_stake=op["current_stake_balance"],
309-
hotkey_ss58=op["hotkey_ss58"],
310-
status=status,
311-
)
284+
for op in unstake_operations:
285+
common_args = {
286+
"wallet": wallet,
287+
"subtensor": subtensor,
288+
"netuid": op["netuid"],
289+
"amount": op["amount_to_unstake"],
290+
"current_stake": op["current_stake_balance"],
291+
"hotkey_ss58": op["hotkey_ss58"],
292+
"status": status,
293+
}
294+
295+
if safe_staking and op["netuid"] != 0:
296+
func = _safe_unstake_extrinsic
297+
specific_args = {
298+
"price_limit": op["price_with_tolerance"],
299+
"allow_partial_stake": allow_partial_stake,
300+
}
301+
else:
302+
func = _unstake_extrinsic
303+
specific_args = {}
304+
305+
suc = await func(**common_args, **specific_args)
306+
307+
successes.append(
308+
{
309+
"netuid": op["netuid"],
310+
"hotkey_ss58": op["hotkey_ss58"],
311+
"unstake_amount": op["amount_to_unstake"].tao,
312+
"success": suc,
313+
}
314+
)
315+
312316
console.print(
313317
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
314318
)
319+
if json_output:
320+
json_console.print(json.dumps(successes))
315321

316322

317323
async def unstake_all(
@@ -323,6 +329,7 @@ async def unstake_all(
323329
include_hotkeys: list[str] = [],
324330
exclude_hotkeys: list[str] = [],
325331
prompt: bool = True,
332+
json_output: bool = False,
326333
) -> bool:
327334
"""Unstakes all stakes from all hotkeys in all subnets."""
328335

@@ -448,11 +455,16 @@ async def unstake_all(
448455
slippage_pct,
449456
)
450457
console.print(table)
451-
message = ""
452458
if max_slippage > 5:
453-
message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
454-
message += f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage:.4f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n"
455-
message += "-------------------------------------------------------------------------------------------------------------------\n"
459+
message = (
460+
f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]--------------------------------------------------------------"
461+
f"-----------------------------------------------------\n"
462+
f"[bold]WARNING:[/bold] The slippage on one of your operations is high: "
463+
f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage:.4f}%"
464+
f"[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n"
465+
"----------------------------------------------------------------------------------------------------------"
466+
"---------\n"
467+
)
456468
console.print(message)
457469

458470
console.print(
@@ -466,17 +478,19 @@ async def unstake_all(
466478

467479
if not unlock_key(wallet).success:
468480
return False
469-
481+
successes = {}
470482
with console.status("Unstaking all stakes...") as status:
471483
for hotkey_ss58 in hotkey_ss58s:
472-
await _unstake_all_extrinsic(
484+
successes[hotkey_ss58] = await _unstake_all_extrinsic(
473485
wallet=wallet,
474486
subtensor=subtensor,
475487
hotkey_ss58=hotkey_ss58,
476488
hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
477489
unstake_all_alpha=unstake_all_alpha,
478490
status=status,
479491
)
492+
if json_output:
493+
return json_console.print(json.dumps({"success": successes}))
480494

481495

482496
# Extrinsics
@@ -488,7 +502,7 @@ async def _unstake_extrinsic(
488502
current_stake: Balance,
489503
hotkey_ss58: str,
490504
status=None,
491-
) -> None:
505+
) -> bool:
492506
"""Execute a standard unstake extrinsic.
493507
494508
Args:
@@ -510,15 +524,17 @@ async def _unstake_extrinsic(
510524
f"\n:satellite: Unstaking {amount} from {hotkey_ss58} on netuid: {netuid} ..."
511525
)
512526

513-
current_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
514-
call = await subtensor.substrate.compose_call(
515-
call_module="SubtensorModule",
516-
call_function="remove_stake",
517-
call_params={
518-
"hotkey": hotkey_ss58,
519-
"netuid": netuid,
520-
"amount_unstaked": amount.rao,
521-
},
527+
current_balance, call = await asyncio.gather(
528+
subtensor.get_balance(wallet.coldkeypub.ss58_address),
529+
subtensor.substrate.compose_call(
530+
call_module="SubtensorModule",
531+
call_function="remove_stake",
532+
call_params={
533+
"hotkey": hotkey_ss58,
534+
"netuid": netuid,
535+
"amount_unstaked": amount.rao,
536+
},
537+
),
522538
)
523539
extrinsic = await subtensor.substrate.create_signed_extrinsic(
524540
call=call, keypair=wallet.coldkey
@@ -528,15 +544,12 @@ async def _unstake_extrinsic(
528544
response = await subtensor.substrate.submit_extrinsic(
529545
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
530546
)
531-
await response.process_events()
532-
533547
if not await response.is_success:
534548
err_out(
535549
f"{failure_prelude} with error: "
536550
f"{format_error_message(await response.error_message)}"
537551
)
538-
return
539-
552+
return False
540553
# Fetch latest balance and stake
541554
block_hash = await subtensor.substrate.get_chain_head()
542555
new_balance, new_stake = await asyncio.gather(
@@ -557,9 +570,11 @@ async def _unstake_extrinsic(
557570
f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
558571
f" Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
559572
)
573+
return True
560574

561575
except Exception as e:
562576
err_out(f"{failure_prelude} with error: {str(e)}")
577+
return False
563578

564579

565580
async def _safe_unstake_extrinsic(
@@ -572,7 +587,7 @@ async def _safe_unstake_extrinsic(
572587
price_limit: Balance,
573588
allow_partial_stake: bool,
574589
status=None,
575-
) -> None:
590+
) -> bool:
576591
"""Execute a safe unstake extrinsic with price limit.
577592
578593
Args:
@@ -598,26 +613,27 @@ async def _safe_unstake_extrinsic(
598613

599614
block_hash = await subtensor.substrate.get_chain_head()
600615

601-
current_balance, next_nonce, current_stake = await asyncio.gather(
616+
current_balance, next_nonce, current_stake, call = await asyncio.gather(
602617
subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash),
603618
subtensor.substrate.get_account_next_index(wallet.coldkeypub.ss58_address),
604619
subtensor.get_stake(
605620
hotkey_ss58=hotkey_ss58,
606621
coldkey_ss58=wallet.coldkeypub.ss58_address,
607622
netuid=netuid,
623+
block_hash=block_hash,
624+
),
625+
subtensor.substrate.compose_call(
626+
call_module="SubtensorModule",
627+
call_function="remove_stake_limit",
628+
call_params={
629+
"hotkey": hotkey_ss58,
630+
"netuid": netuid,
631+
"amount_unstaked": amount.rao,
632+
"limit_price": price_limit,
633+
"allow_partial": allow_partial_stake,
634+
},
635+
block_hash=block_hash,
608636
),
609-
)
610-
611-
call = await subtensor.substrate.compose_call(
612-
call_module="SubtensorModule",
613-
call_function="remove_stake_limit",
614-
call_params={
615-
"hotkey": hotkey_ss58,
616-
"netuid": netuid,
617-
"amount_unstaked": amount.rao,
618-
"limit_price": price_limit,
619-
"allow_partial": allow_partial_stake,
620-
},
621637
)
622638

623639
extrinsic = await subtensor.substrate.create_signed_extrinsic(
@@ -636,17 +652,15 @@ async def _safe_unstake_extrinsic(
636652
f"Either increase price tolerance or enable partial unstaking.",
637653
status=status,
638654
)
639-
return
640655
else:
641656
err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
642-
return
657+
return False
643658

644-
await response.process_events()
645659
if not await response.is_success:
646660
err_out(
647661
f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
648662
)
649-
return
663+
return False
650664

651665
block_hash = await subtensor.substrate.get_chain_head()
652666
new_balance, new_stake = await asyncio.gather(
@@ -677,6 +691,7 @@ async def _safe_unstake_extrinsic(
677691
f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
678692
f"Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
679693
)
694+
return True
680695

681696

682697
async def _unstake_all_extrinsic(

0 commit comments

Comments
 (0)