1
1
import asyncio
2
+ import json
2
3
from functools import partial
3
4
4
5
from typing import TYPE_CHECKING , Optional
20
21
format_error_message ,
21
22
group_subnets ,
22
23
unlock_key ,
24
+ json_console ,
23
25
)
24
26
25
27
if TYPE_CHECKING :
@@ -41,6 +43,7 @@ async def unstake(
41
43
safe_staking : bool ,
42
44
rate_tolerance : float ,
43
45
allow_partial_stake : bool ,
46
+ json_output : bool ,
44
47
):
45
48
"""Unstake from hotkey(s)."""
46
49
unstake_all_from_hk = False
@@ -241,8 +244,11 @@ async def unstake(
241
244
base_unstake_op ["price_with_tolerance" ] = price_with_tolerance
242
245
base_table_row .extend (
243
246
[
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' } ]" ,
246
252
]
247
253
)
248
254
@@ -273,45 +279,45 @@ async def unstake(
273
279
if not unlock_key (wallet ).success :
274
280
return False
275
281
282
+ successes = []
276
283
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
+
312
316
console .print (
313
317
f"[{ COLOR_PALETTE ['STAKE' ]['STAKE_AMOUNT' ]} ]Unstaking operations completed."
314
318
)
319
+ if json_output :
320
+ json_console .print (json .dumps (successes ))
315
321
316
322
317
323
async def unstake_all (
@@ -323,6 +329,7 @@ async def unstake_all(
323
329
include_hotkeys : list [str ] = [],
324
330
exclude_hotkeys : list [str ] = [],
325
331
prompt : bool = True ,
332
+ json_output : bool = False ,
326
333
) -> bool :
327
334
"""Unstakes all stakes from all hotkeys in all subnets."""
328
335
@@ -448,11 +455,16 @@ async def unstake_all(
448
455
slippage_pct ,
449
456
)
450
457
console .print (table )
451
- message = ""
452
458
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
+ )
456
468
console .print (message )
457
469
458
470
console .print (
@@ -466,17 +478,19 @@ async def unstake_all(
466
478
467
479
if not unlock_key (wallet ).success :
468
480
return False
469
-
481
+ successes = {}
470
482
with console .status ("Unstaking all stakes..." ) as status :
471
483
for hotkey_ss58 in hotkey_ss58s :
472
- await _unstake_all_extrinsic (
484
+ successes [ hotkey_ss58 ] = await _unstake_all_extrinsic (
473
485
wallet = wallet ,
474
486
subtensor = subtensor ,
475
487
hotkey_ss58 = hotkey_ss58 ,
476
488
hotkey_name = hotkey_names .get (hotkey_ss58 , hotkey_ss58 ),
477
489
unstake_all_alpha = unstake_all_alpha ,
478
490
status = status ,
479
491
)
492
+ if json_output :
493
+ return json_console .print (json .dumps ({"success" : successes }))
480
494
481
495
482
496
# Extrinsics
@@ -488,7 +502,7 @@ async def _unstake_extrinsic(
488
502
current_stake : Balance ,
489
503
hotkey_ss58 : str ,
490
504
status = None ,
491
- ) -> None :
505
+ ) -> bool :
492
506
"""Execute a standard unstake extrinsic.
493
507
494
508
Args:
@@ -510,15 +524,17 @@ async def _unstake_extrinsic(
510
524
f"\n :satellite: Unstaking { amount } from { hotkey_ss58 } on netuid: { netuid } ..."
511
525
)
512
526
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
+ ),
522
538
)
523
539
extrinsic = await subtensor .substrate .create_signed_extrinsic (
524
540
call = call , keypair = wallet .coldkey
@@ -528,15 +544,12 @@ async def _unstake_extrinsic(
528
544
response = await subtensor .substrate .submit_extrinsic (
529
545
extrinsic , wait_for_inclusion = True , wait_for_finalization = False
530
546
)
531
- await response .process_events ()
532
-
533
547
if not await response .is_success :
534
548
err_out (
535
549
f"{ failure_prelude } with error: "
536
550
f"{ format_error_message (await response .error_message )} "
537
551
)
538
- return
539
-
552
+ return False
540
553
# Fetch latest balance and stake
541
554
block_hash = await subtensor .substrate .get_chain_head ()
542
555
new_balance , new_stake = await asyncio .gather (
@@ -557,9 +570,11 @@ async def _unstake_extrinsic(
557
570
f"Subnet: [{ COLOR_PALETTE ['GENERAL' ]['SUBHEADING' ]} ]{ netuid } [/{ COLOR_PALETTE ['GENERAL' ]['SUBHEADING' ]} ]"
558
571
f" Stake:\n [blue]{ current_stake } [/blue] :arrow_right: [{ COLOR_PALETTE ['STAKE' ]['STAKE_AMOUNT' ]} ]{ new_stake } "
559
572
)
573
+ return True
560
574
561
575
except Exception as e :
562
576
err_out (f"{ failure_prelude } with error: { str (e )} " )
577
+ return False
563
578
564
579
565
580
async def _safe_unstake_extrinsic (
@@ -572,7 +587,7 @@ async def _safe_unstake_extrinsic(
572
587
price_limit : Balance ,
573
588
allow_partial_stake : bool ,
574
589
status = None ,
575
- ) -> None :
590
+ ) -> bool :
576
591
"""Execute a safe unstake extrinsic with price limit.
577
592
578
593
Args:
@@ -598,26 +613,27 @@ async def _safe_unstake_extrinsic(
598
613
599
614
block_hash = await subtensor .substrate .get_chain_head ()
600
615
601
- current_balance , next_nonce , current_stake = await asyncio .gather (
616
+ current_balance , next_nonce , current_stake , call = await asyncio .gather (
602
617
subtensor .get_balance (wallet .coldkeypub .ss58_address , block_hash ),
603
618
subtensor .substrate .get_account_next_index (wallet .coldkeypub .ss58_address ),
604
619
subtensor .get_stake (
605
620
hotkey_ss58 = hotkey_ss58 ,
606
621
coldkey_ss58 = wallet .coldkeypub .ss58_address ,
607
622
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 ,
608
636
),
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
- },
621
637
)
622
638
623
639
extrinsic = await subtensor .substrate .create_signed_extrinsic (
@@ -636,17 +652,15 @@ async def _safe_unstake_extrinsic(
636
652
f"Either increase price tolerance or enable partial unstaking." ,
637
653
status = status ,
638
654
)
639
- return
640
655
else :
641
656
err_out (f"\n { failure_prelude } with error: { format_error_message (e )} " )
642
- return
657
+ return False
643
658
644
- await response .process_events ()
645
659
if not await response .is_success :
646
660
err_out (
647
661
f"\n { failure_prelude } with error: { format_error_message (await response .error_message )} "
648
662
)
649
- return
663
+ return False
650
664
651
665
block_hash = await subtensor .substrate .get_chain_head ()
652
666
new_balance , new_stake = await asyncio .gather (
@@ -677,6 +691,7 @@ async def _safe_unstake_extrinsic(
677
691
f"Subnet: [{ COLOR_PALETTE ['GENERAL' ]['SUBHEADING' ]} ]{ netuid } [/{ COLOR_PALETTE ['GENERAL' ]['SUBHEADING' ]} ] "
678
692
f"Stake:\n [blue]{ current_stake } [/blue] :arrow_right: [{ COLOR_PALETTE ['STAKE' ]['STAKE_AMOUNT' ]} ]{ new_stake } "
679
693
)
694
+ return True
680
695
681
696
682
697
async def _unstake_all_extrinsic (
0 commit comments