bittensor-cli 9.2.0__py3-none-any.whl → 9.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import json
2
3
  from functools import partial
3
4
 
4
5
  from typing import TYPE_CHECKING, Optional
@@ -20,6 +21,7 @@ from bittensor_cli.src.bittensor.utils import (
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,8 @@ async def unstake(
41
43
  safe_staking: bool,
42
44
  rate_tolerance: float,
43
45
  allow_partial_stake: bool,
46
+ json_output: bool,
47
+ era: int,
44
48
  ):
45
49
  """Unstake from hotkey(s)."""
46
50
  with console.status(
@@ -258,8 +262,11 @@ async def unstake(
258
262
  base_unstake_op["price_with_tolerance"] = price_with_tolerance
259
263
  base_table_row.extend(
260
264
  [
261
- f"{rate_with_tolerance:.4f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}", # Rate with tolerance
262
- f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]", # Partial unstake
265
+ # Rate with tolerance
266
+ f"{rate_with_tolerance:.4f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}",
267
+ # Partial unstake
268
+ f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
269
+ f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
263
270
  ]
264
271
  )
265
272
 
@@ -290,44 +297,45 @@ async def unstake(
290
297
  if not unlock_key(wallet).success:
291
298
  return False
292
299
 
300
+ successes = []
293
301
  with console.status("\n:satellite: Performing unstaking operations...") as status:
294
- if safe_staking:
295
- for op in unstake_operations:
296
- if op["netuid"] == 0:
297
- await _unstake_extrinsic(
298
- wallet=wallet,
299
- subtensor=subtensor,
300
- netuid=op["netuid"],
301
- amount=op["amount_to_unstake"],
302
- current_stake=op["current_stake_balance"],
303
- hotkey_ss58=op["hotkey_ss58"],
304
- status=status,
305
- )
306
- else:
307
- await _safe_unstake_extrinsic(
308
- wallet=wallet,
309
- subtensor=subtensor,
310
- netuid=op["netuid"],
311
- amount=op["amount_to_unstake"],
312
- hotkey_ss58=op["hotkey_ss58"],
313
- price_limit=op["price_with_tolerance"],
314
- allow_partial_stake=allow_partial_stake,
315
- status=status,
316
- )
317
- else:
318
- for op in unstake_operations:
319
- await _unstake_extrinsic(
320
- wallet=wallet,
321
- subtensor=subtensor,
322
- netuid=op["netuid"],
323
- amount=op["amount_to_unstake"],
324
- current_stake=op["current_stake_balance"],
325
- hotkey_ss58=op["hotkey_ss58"],
326
- status=status,
327
- )
302
+ for op in unstake_operations:
303
+ common_args = {
304
+ "wallet": wallet,
305
+ "subtensor": subtensor,
306
+ "netuid": op["netuid"],
307
+ "amount": op["amount_to_unstake"],
308
+ "hotkey_ss58": op["hotkey_ss58"],
309
+ "status": status,
310
+ "era": era,
311
+ }
312
+
313
+ if safe_staking and op["netuid"] != 0:
314
+ func = _safe_unstake_extrinsic
315
+ specific_args = {
316
+ "price_limit": op["price_with_tolerance"],
317
+ "allow_partial_stake": allow_partial_stake,
318
+ }
319
+ else:
320
+ func = _unstake_extrinsic
321
+ specific_args = {"current_stake": op["current_stake_balance"]}
322
+
323
+ suc = await func(**common_args, **specific_args)
324
+
325
+ successes.append(
326
+ {
327
+ "netuid": op["netuid"],
328
+ "hotkey_ss58": op["hotkey_ss58"],
329
+ "unstake_amount": op["amount_to_unstake"].tao,
330
+ "success": suc,
331
+ }
332
+ )
333
+
328
334
  console.print(
329
335
  f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
330
336
  )
337
+ if json_output:
338
+ json_console.print(json.dumps(successes))
331
339
 
332
340
 
333
341
  async def unstake_all(
@@ -338,7 +346,9 @@ async def unstake_all(
338
346
  all_hotkeys: bool = False,
339
347
  include_hotkeys: Optional[list[str]] = None,
340
348
  exclude_hotkeys: Optional[list[str]] = None,
349
+ era: int = 3,
341
350
  prompt: bool = True,
351
+ json_output: bool = False,
342
352
  ) -> bool:
343
353
  """Unstakes all stakes from all hotkeys in all subnets."""
344
354
  include_hotkeys = include_hotkeys or []
@@ -434,7 +444,7 @@ async def unstake_all(
434
444
  style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
435
445
  )
436
446
  table.add_column(
437
- f"Recieved ({Balance.unit})",
447
+ f"Received ({Balance.unit})",
438
448
  justify="center",
439
449
  style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
440
450
  )
@@ -484,11 +494,16 @@ async def unstake_all(
484
494
  slippage_pct,
485
495
  )
486
496
  console.print(table)
487
- message = ""
488
497
  if max_slippage > 5:
489
- message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
490
- 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"
491
- message += "-------------------------------------------------------------------------------------------------------------------\n"
498
+ message = (
499
+ f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]--------------------------------------------------------------"
500
+ f"-----------------------------------------------------\n"
501
+ f"[bold]WARNING:[/bold] The slippage on one of your operations is high: "
502
+ f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage:.4f}%"
503
+ f"[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n"
504
+ "----------------------------------------------------------------------------------------------------------"
505
+ "---------\n"
506
+ )
492
507
  console.print(message)
493
508
 
494
509
  console.print(
@@ -502,17 +517,20 @@ async def unstake_all(
502
517
 
503
518
  if not unlock_key(wallet).success:
504
519
  return False
505
-
520
+ successes = {}
506
521
  with console.status("Unstaking all stakes...") as status:
507
522
  for hotkey_ss58 in hotkey_ss58s:
508
- await _unstake_all_extrinsic(
523
+ successes[hotkey_ss58] = await _unstake_all_extrinsic(
509
524
  wallet=wallet,
510
525
  subtensor=subtensor,
511
526
  hotkey_ss58=hotkey_ss58,
512
527
  hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
513
528
  unstake_all_alpha=unstake_all_alpha,
514
529
  status=status,
530
+ era=era,
515
531
  )
532
+ if json_output:
533
+ return json_console.print(json.dumps({"success": successes}))
516
534
 
517
535
 
518
536
  # Extrinsics
@@ -524,7 +542,8 @@ async def _unstake_extrinsic(
524
542
  current_stake: Balance,
525
543
  hotkey_ss58: str,
526
544
  status=None,
527
- ) -> None:
545
+ era: int = 3,
546
+ ) -> bool:
528
547
  """Execute a standard unstake extrinsic.
529
548
 
530
549
  Args:
@@ -535,6 +554,7 @@ async def _unstake_extrinsic(
535
554
  wallet: Wallet instance
536
555
  subtensor: Subtensor interface
537
556
  status: Optional status for console updates
557
+ era: blocks for which the transaction is valid
538
558
  """
539
559
  err_out = partial(print_error, status=status)
540
560
  failure_prelude = (
@@ -546,33 +566,32 @@ async def _unstake_extrinsic(
546
566
  f"\n:satellite: Unstaking {amount} from {hotkey_ss58} on netuid: {netuid} ..."
547
567
  )
548
568
 
549
- current_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
550
- call = await subtensor.substrate.compose_call(
551
- call_module="SubtensorModule",
552
- call_function="remove_stake",
553
- call_params={
554
- "hotkey": hotkey_ss58,
555
- "netuid": netuid,
556
- "amount_unstaked": amount.rao,
557
- },
569
+ current_balance, call = await asyncio.gather(
570
+ subtensor.get_balance(wallet.coldkeypub.ss58_address),
571
+ subtensor.substrate.compose_call(
572
+ call_module="SubtensorModule",
573
+ call_function="remove_stake",
574
+ call_params={
575
+ "hotkey": hotkey_ss58,
576
+ "netuid": netuid,
577
+ "amount_unstaked": amount.rao,
578
+ },
579
+ ),
558
580
  )
559
581
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
560
- call=call, keypair=wallet.coldkey
582
+ call=call, keypair=wallet.coldkey, era={"period": era}
561
583
  )
562
584
 
563
585
  try:
564
586
  response = await subtensor.substrate.submit_extrinsic(
565
587
  extrinsic, wait_for_inclusion=True, wait_for_finalization=False
566
588
  )
567
- await response.process_events()
568
-
569
589
  if not await response.is_success:
570
590
  err_out(
571
591
  f"{failure_prelude} with error: "
572
592
  f"{format_error_message(await response.error_message)}"
573
593
  )
574
- return
575
-
594
+ return False
576
595
  # Fetch latest balance and stake
577
596
  block_hash = await subtensor.substrate.get_chain_head()
578
597
  new_balance, new_stake = await asyncio.gather(
@@ -593,9 +612,11 @@ async def _unstake_extrinsic(
593
612
  f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
594
613
  f" Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
595
614
  )
615
+ return True
596
616
 
597
617
  except Exception as e:
598
618
  err_out(f"{failure_prelude} with error: {str(e)}")
619
+ return False
599
620
 
600
621
 
601
622
  async def _safe_unstake_extrinsic(
@@ -607,7 +628,8 @@ async def _safe_unstake_extrinsic(
607
628
  price_limit: Balance,
608
629
  allow_partial_stake: bool,
609
630
  status=None,
610
- ) -> None:
631
+ era: int = 3,
632
+ ) -> bool:
611
633
  """Execute a safe unstake extrinsic with price limit.
612
634
 
613
635
  Args:
@@ -632,30 +654,31 @@ async def _safe_unstake_extrinsic(
632
654
 
633
655
  block_hash = await subtensor.substrate.get_chain_head()
634
656
 
635
- current_balance, next_nonce, current_stake = await asyncio.gather(
657
+ current_balance, next_nonce, current_stake, call = await asyncio.gather(
636
658
  subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash),
637
659
  subtensor.substrate.get_account_next_index(wallet.coldkeypub.ss58_address),
638
660
  subtensor.get_stake(
639
661
  hotkey_ss58=hotkey_ss58,
640
662
  coldkey_ss58=wallet.coldkeypub.ss58_address,
641
663
  netuid=netuid,
664
+ block_hash=block_hash,
665
+ ),
666
+ subtensor.substrate.compose_call(
667
+ call_module="SubtensorModule",
668
+ call_function="remove_stake_limit",
669
+ call_params={
670
+ "hotkey": hotkey_ss58,
671
+ "netuid": netuid,
672
+ "amount_unstaked": amount.rao,
673
+ "limit_price": price_limit,
674
+ "allow_partial": allow_partial_stake,
675
+ },
676
+ block_hash=block_hash,
642
677
  ),
643
- )
644
-
645
- call = await subtensor.substrate.compose_call(
646
- call_module="SubtensorModule",
647
- call_function="remove_stake_limit",
648
- call_params={
649
- "hotkey": hotkey_ss58,
650
- "netuid": netuid,
651
- "amount_unstaked": amount.rao,
652
- "limit_price": price_limit,
653
- "allow_partial": allow_partial_stake,
654
- },
655
678
  )
656
679
 
657
680
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
658
- call=call, keypair=wallet.coldkey, nonce=next_nonce
681
+ call=call, keypair=wallet.coldkey, nonce=next_nonce, era={"period": era}
659
682
  )
660
683
 
661
684
  try:
@@ -670,17 +693,15 @@ async def _safe_unstake_extrinsic(
670
693
  f"Either increase price tolerance or enable partial unstaking.",
671
694
  status=status,
672
695
  )
673
- return
674
696
  else:
675
697
  err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
676
- return
698
+ return False
677
699
 
678
- await response.process_events()
679
700
  if not await response.is_success:
680
701
  err_out(
681
702
  f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
682
703
  )
683
- return
704
+ return False
684
705
 
685
706
  block_hash = await subtensor.substrate.get_chain_head()
686
707
  new_balance, new_stake = await asyncio.gather(
@@ -711,6 +732,7 @@ async def _safe_unstake_extrinsic(
711
732
  f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
712
733
  f"Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
713
734
  )
735
+ return True
714
736
 
715
737
 
716
738
  async def _unstake_all_extrinsic(
@@ -720,6 +742,7 @@ async def _unstake_all_extrinsic(
720
742
  hotkey_name: str,
721
743
  unstake_all_alpha: bool,
722
744
  status=None,
745
+ era: int = 3,
723
746
  ) -> None:
724
747
  """Execute an unstake all extrinsic.
725
748
 
@@ -770,13 +793,11 @@ async def _unstake_all_extrinsic(
770
793
  try:
771
794
  response = await subtensor.substrate.submit_extrinsic(
772
795
  extrinsic=await subtensor.substrate.create_signed_extrinsic(
773
- call=call,
774
- keypair=wallet.coldkey,
796
+ call=call, keypair=wallet.coldkey, era={"period": era}
775
797
  ),
776
798
  wait_for_inclusion=True,
777
799
  wait_for_finalization=False,
778
800
  )
779
- await response.process_events()
780
801
 
781
802
  if not await response.is_success:
782
803
  err_out(
@@ -13,6 +13,7 @@ from bittensor_cli.src.bittensor.utils import (
13
13
  err_console,
14
14
  get_subnet_name,
15
15
  print_error,
16
+ json_console,
16
17
  )
17
18
 
18
19
  if TYPE_CHECKING:
@@ -26,6 +27,7 @@ async def price(
26
27
  interval_hours: int = 24,
27
28
  html_output: bool = False,
28
29
  log_scale: bool = False,
30
+ json_output: bool = False,
29
31
  ):
30
32
  """
31
33
  Fetch historical price data for subnets and display it in a chart.
@@ -60,7 +62,7 @@ async def price(
60
62
  all_subnet_infos = await asyncio.gather(*subnet_info_cors)
61
63
 
62
64
  subnet_data = _process_subnet_data(
63
- block_numbers, all_subnet_infos, netuids, all_netuids, interval_hours
65
+ block_numbers, all_subnet_infos, netuids, all_netuids
64
66
  )
65
67
 
66
68
  if not subnet_data:
@@ -71,17 +73,13 @@ async def price(
71
73
  await _generate_html_output(
72
74
  subnet_data, block_numbers, interval_hours, log_scale
73
75
  )
76
+ elif json_output:
77
+ json_console.print(json.dumps(_generate_json_output(subnet_data)))
74
78
  else:
75
79
  _generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale)
76
80
 
77
81
 
78
- def _process_subnet_data(
79
- block_numbers,
80
- all_subnet_infos,
81
- netuids,
82
- all_netuids,
83
- interval_hours,
84
- ):
82
+ def _process_subnet_data(block_numbers, all_subnet_infos, netuids, all_netuids):
85
83
  """
86
84
  Process subnet data into a structured format for price analysis.
87
85
  """
@@ -772,6 +770,10 @@ async def _generate_html_output(
772
770
  print_error(f"Error generating price chart: {e}")
773
771
 
774
772
 
773
+ def _generate_json_output(subnet_data):
774
+ return {netuid: data for netuid, data in subnet_data.items()}
775
+
776
+
775
777
  def _generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale):
776
778
  """
777
779
  Render the price data in a textual CLI style with plotille ASCII charts.
@@ -802,7 +804,7 @@ def _generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale):
802
804
 
803
805
  fig.plot(
804
806
  block_numbers,
805
- data["prices"],
807
+ prices,
806
808
  label=f"Subnet {netuid} Price",
807
809
  interp="linear",
808
810
  lc="bae98f",