bittensor-cli 9.1.4__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(
@@ -194,9 +198,25 @@ async def unstake(
194
198
  )
195
199
  continue # Skip to the next subnet - useful when single amount is specified for all subnets
196
200
 
197
- received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
198
- subnet_info=subnet_info, amount=amount_to_unstake_as_balance
201
+ stake_fee = await subtensor.get_stake_fee(
202
+ origin_hotkey_ss58=staking_address_ss58,
203
+ origin_netuid=netuid,
204
+ origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
205
+ destination_hotkey_ss58=None,
206
+ destination_netuid=None,
207
+ destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
208
+ amount=amount_to_unstake_as_balance.rao,
199
209
  )
210
+
211
+ try:
212
+ received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
213
+ subnet_info=subnet_info,
214
+ amount=amount_to_unstake_as_balance,
215
+ stake_fee=stake_fee,
216
+ )
217
+ except ValueError:
218
+ continue
219
+
200
220
  total_received_amount += received_amount
201
221
  max_float_slippage = max(max_float_slippage, slippage_pct_float)
202
222
 
@@ -220,6 +240,7 @@ async def unstake(
220
240
  str(amount_to_unstake_as_balance), # Amount to Unstake
221
241
  str(subnet_info.price.tao)
222
242
  + f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", # Rate
243
+ str(stake_fee), # Fee
223
244
  str(received_amount), # Received Amount
224
245
  slippage_pct, # Slippage Percent
225
246
  ]
@@ -241,8 +262,11 @@ async def unstake(
241
262
  base_unstake_op["price_with_tolerance"] = price_with_tolerance
242
263
  base_table_row.extend(
243
264
  [
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
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'}]",
246
270
  ]
247
271
  )
248
272
 
@@ -273,44 +297,45 @@ async def unstake(
273
297
  if not unlock_key(wallet).success:
274
298
  return False
275
299
 
300
+ successes = []
276
301
  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
- hotkey_ss58=op["hotkey_ss58"],
296
- price_limit=op["price_with_tolerance"],
297
- allow_partial_stake=allow_partial_stake,
298
- status=status,
299
- )
300
- else:
301
- for op in unstake_operations:
302
- await _unstake_extrinsic(
303
- wallet=wallet,
304
- subtensor=subtensor,
305
- netuid=op["netuid"],
306
- amount=op["amount_to_unstake"],
307
- current_stake=op["current_stake_balance"],
308
- hotkey_ss58=op["hotkey_ss58"],
309
- status=status,
310
- )
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
+
311
334
  console.print(
312
335
  f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
313
336
  )
337
+ if json_output:
338
+ json_console.print(json.dumps(successes))
314
339
 
315
340
 
316
341
  async def unstake_all(
@@ -321,7 +346,9 @@ async def unstake_all(
321
346
  all_hotkeys: bool = False,
322
347
  include_hotkeys: Optional[list[str]] = None,
323
348
  exclude_hotkeys: Optional[list[str]] = None,
349
+ era: int = 3,
324
350
  prompt: bool = True,
351
+ json_output: bool = False,
325
352
  ) -> bool:
326
353
  """Unstakes all stakes from all hotkeys in all subnets."""
327
354
  include_hotkeys = include_hotkeys or []
@@ -412,7 +439,12 @@ async def unstake_all(
412
439
  style=COLOR_PALETTE["POOLS"]["RATE"],
413
440
  )
414
441
  table.add_column(
415
- f"Recieved ({Balance.unit})",
442
+ f"Fee ({Balance.unit})",
443
+ justify="center",
444
+ style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
445
+ )
446
+ table.add_column(
447
+ f"Received ({Balance.unit})",
416
448
  justify="center",
417
449
  style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
418
450
  )
@@ -432,9 +464,22 @@ async def unstake_all(
432
464
  hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58)
433
465
  subnet_info = all_sn_dynamic_info.get(stake.netuid)
434
466
  stake_amount = stake.stake
435
- received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
436
- subnet_info=subnet_info, amount=stake_amount
467
+ stake_fee = await subtensor.get_stake_fee(
468
+ origin_hotkey_ss58=stake.hotkey_ss58,
469
+ origin_netuid=stake.netuid,
470
+ origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
471
+ destination_hotkey_ss58=None,
472
+ destination_netuid=None,
473
+ destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
474
+ amount=stake_amount.rao,
437
475
  )
476
+ try:
477
+ received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
478
+ subnet_info=subnet_info, amount=stake_amount, stake_fee=stake_fee
479
+ )
480
+ except ValueError:
481
+ continue
482
+
438
483
  max_slippage = max(max_slippage, slippage_pct_float)
439
484
  total_received_value += received_amount
440
485
 
@@ -444,15 +489,21 @@ async def unstake_all(
444
489
  str(stake_amount),
445
490
  str(float(subnet_info.price))
446
491
  + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})",
492
+ str(stake_fee),
447
493
  str(received_amount),
448
494
  slippage_pct,
449
495
  )
450
496
  console.print(table)
451
- message = ""
452
497
  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"
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
+ )
456
507
  console.print(message)
457
508
 
458
509
  console.print(
@@ -466,17 +517,20 @@ async def unstake_all(
466
517
 
467
518
  if not unlock_key(wallet).success:
468
519
  return False
469
-
520
+ successes = {}
470
521
  with console.status("Unstaking all stakes...") as status:
471
522
  for hotkey_ss58 in hotkey_ss58s:
472
- await _unstake_all_extrinsic(
523
+ successes[hotkey_ss58] = await _unstake_all_extrinsic(
473
524
  wallet=wallet,
474
525
  subtensor=subtensor,
475
526
  hotkey_ss58=hotkey_ss58,
476
527
  hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
477
528
  unstake_all_alpha=unstake_all_alpha,
478
529
  status=status,
530
+ era=era,
479
531
  )
532
+ if json_output:
533
+ return json_console.print(json.dumps({"success": successes}))
480
534
 
481
535
 
482
536
  # Extrinsics
@@ -488,7 +542,8 @@ async def _unstake_extrinsic(
488
542
  current_stake: Balance,
489
543
  hotkey_ss58: str,
490
544
  status=None,
491
- ) -> None:
545
+ era: int = 3,
546
+ ) -> bool:
492
547
  """Execute a standard unstake extrinsic.
493
548
 
494
549
  Args:
@@ -499,6 +554,7 @@ async def _unstake_extrinsic(
499
554
  wallet: Wallet instance
500
555
  subtensor: Subtensor interface
501
556
  status: Optional status for console updates
557
+ era: blocks for which the transaction is valid
502
558
  """
503
559
  err_out = partial(print_error, status=status)
504
560
  failure_prelude = (
@@ -510,33 +566,32 @@ async def _unstake_extrinsic(
510
566
  f"\n:satellite: Unstaking {amount} from {hotkey_ss58} on netuid: {netuid} ..."
511
567
  )
512
568
 
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
- },
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
+ ),
522
580
  )
523
581
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
524
- call=call, keypair=wallet.coldkey
582
+ call=call, keypair=wallet.coldkey, era={"period": era}
525
583
  )
526
584
 
527
585
  try:
528
586
  response = await subtensor.substrate.submit_extrinsic(
529
587
  extrinsic, wait_for_inclusion=True, wait_for_finalization=False
530
588
  )
531
- await response.process_events()
532
-
533
589
  if not await response.is_success:
534
590
  err_out(
535
591
  f"{failure_prelude} with error: "
536
592
  f"{format_error_message(await response.error_message)}"
537
593
  )
538
- return
539
-
594
+ return False
540
595
  # Fetch latest balance and stake
541
596
  block_hash = await subtensor.substrate.get_chain_head()
542
597
  new_balance, new_stake = await asyncio.gather(
@@ -557,9 +612,11 @@ async def _unstake_extrinsic(
557
612
  f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
558
613
  f" Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
559
614
  )
615
+ return True
560
616
 
561
617
  except Exception as e:
562
618
  err_out(f"{failure_prelude} with error: {str(e)}")
619
+ return False
563
620
 
564
621
 
565
622
  async def _safe_unstake_extrinsic(
@@ -571,7 +628,8 @@ async def _safe_unstake_extrinsic(
571
628
  price_limit: Balance,
572
629
  allow_partial_stake: bool,
573
630
  status=None,
574
- ) -> None:
631
+ era: int = 3,
632
+ ) -> bool:
575
633
  """Execute a safe unstake extrinsic with price limit.
576
634
 
577
635
  Args:
@@ -596,30 +654,31 @@ async def _safe_unstake_extrinsic(
596
654
 
597
655
  block_hash = await subtensor.substrate.get_chain_head()
598
656
 
599
- current_balance, next_nonce, current_stake = await asyncio.gather(
657
+ current_balance, next_nonce, current_stake, call = await asyncio.gather(
600
658
  subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash),
601
659
  subtensor.substrate.get_account_next_index(wallet.coldkeypub.ss58_address),
602
660
  subtensor.get_stake(
603
661
  hotkey_ss58=hotkey_ss58,
604
662
  coldkey_ss58=wallet.coldkeypub.ss58_address,
605
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,
606
677
  ),
607
- )
608
-
609
- call = await subtensor.substrate.compose_call(
610
- call_module="SubtensorModule",
611
- call_function="remove_stake_limit",
612
- call_params={
613
- "hotkey": hotkey_ss58,
614
- "netuid": netuid,
615
- "amount_unstaked": amount.rao,
616
- "limit_price": price_limit,
617
- "allow_partial": allow_partial_stake,
618
- },
619
678
  )
620
679
 
621
680
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
622
- call=call, keypair=wallet.coldkey, nonce=next_nonce
681
+ call=call, keypair=wallet.coldkey, nonce=next_nonce, era={"period": era}
623
682
  )
624
683
 
625
684
  try:
@@ -634,17 +693,15 @@ async def _safe_unstake_extrinsic(
634
693
  f"Either increase price tolerance or enable partial unstaking.",
635
694
  status=status,
636
695
  )
637
- return
638
696
  else:
639
697
  err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
640
- return
698
+ return False
641
699
 
642
- await response.process_events()
643
700
  if not await response.is_success:
644
701
  err_out(
645
702
  f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
646
703
  )
647
- return
704
+ return False
648
705
 
649
706
  block_hash = await subtensor.substrate.get_chain_head()
650
707
  new_balance, new_stake = await asyncio.gather(
@@ -675,6 +732,7 @@ async def _safe_unstake_extrinsic(
675
732
  f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
676
733
  f"Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
677
734
  )
735
+ return True
678
736
 
679
737
 
680
738
  async def _unstake_all_extrinsic(
@@ -684,6 +742,7 @@ async def _unstake_all_extrinsic(
684
742
  hotkey_name: str,
685
743
  unstake_all_alpha: bool,
686
744
  status=None,
745
+ era: int = 3,
687
746
  ) -> None:
688
747
  """Execute an unstake all extrinsic.
689
748
 
@@ -734,13 +793,11 @@ async def _unstake_all_extrinsic(
734
793
  try:
735
794
  response = await subtensor.substrate.submit_extrinsic(
736
795
  extrinsic=await subtensor.substrate.create_signed_extrinsic(
737
- call=call,
738
- keypair=wallet.coldkey,
796
+ call=call, keypair=wallet.coldkey, era={"period": era}
739
797
  ),
740
798
  wait_for_inclusion=True,
741
799
  wait_for_finalization=False,
742
800
  )
743
- await response.process_events()
744
801
 
745
802
  if not await response.is_success:
746
803
  err_out(
@@ -791,28 +848,47 @@ async def _unstake_all_extrinsic(
791
848
 
792
849
 
793
850
  # Helpers
794
- def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, float]:
851
+ def _calculate_slippage(
852
+ subnet_info, amount: Balance, stake_fee: Balance
853
+ ) -> tuple[Balance, str, float]:
795
854
  """Calculate slippage and received amount for unstaking operation.
796
855
 
797
856
  Args:
798
857
  subnet_info: Subnet information containing price data
799
858
  amount: Amount being unstaked
859
+ stake_fee: Stake fee to include in slippage calculation
800
860
 
801
861
  Returns:
802
862
  tuple containing:
803
- - received_amount: Balance after slippage
863
+ - received_amount: Balance after slippage deduction
804
864
  - slippage_pct: Formatted string of slippage percentage
805
865
  - slippage_pct_float: Float value of slippage percentage
806
866
  """
807
- received_amount, _, slippage_pct_float = subnet_info.alpha_to_tao_with_slippage(
808
- amount
809
- )
867
+ received_amount, _, _ = subnet_info.alpha_to_tao_with_slippage(amount)
868
+ received_amount -= stake_fee
869
+
870
+ if received_amount < Balance.from_tao(0):
871
+ print_error("Not enough Alpha to pay the transaction fee.")
872
+ raise ValueError
810
873
 
811
874
  if subnet_info.is_dynamic:
875
+ # Ideal amount w/o slippage
876
+ ideal_amount = subnet_info.alpha_to_tao(amount)
877
+
878
+ # Total slippage including fees
879
+ total_slippage = ideal_amount - received_amount
880
+ slippage_pct_float = (
881
+ 100 * (float(total_slippage.tao) / float(ideal_amount.tao))
882
+ if ideal_amount.tao != 0
883
+ else 0
884
+ )
812
885
  slippage_pct = f"{slippage_pct_float:.4f} %"
813
886
  else:
814
- slippage_pct_float = 0
815
- slippage_pct = "[red]N/A[/red]"
887
+ # Root will only have fee-based slippage
888
+ slippage_pct_float = (
889
+ 100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
890
+ )
891
+ slippage_pct = f"{slippage_pct_float:.4f} %"
816
892
 
817
893
  return received_amount, slippage_pct, slippage_pct_float
818
894
 
@@ -1174,6 +1250,11 @@ def _create_unstake_table(
1174
1250
  justify="center",
1175
1251
  style=COLOR_PALETTE["POOLS"]["RATE"],
1176
1252
  )
1253
+ table.add_column(
1254
+ f"Fee ({Balance.get_unit(0)})",
1255
+ justify="center",
1256
+ style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
1257
+ )
1177
1258
  table.add_column(
1178
1259
  f"Received ({Balance.get_unit(0)})",
1179
1260
  justify="center",
@@ -1227,7 +1308,8 @@ The columns are as follows:
1227
1308
  - [bold white]Hotkey[/bold white]: The ss58 address or identity of the hotkey you are unstaking from.
1228
1309
  - [bold white]Amount to Unstake[/bold white]: The stake amount you are removing from this key.
1229
1310
  - [bold white]Rate[/bold white]: The rate of exchange between TAO and the subnet's stake.
1230
- - [bold white]Received[/bold white]: The amount of free balance TAO you will receive on this subnet after slippage.
1311
+ - [bold white]Fee[/bold white]: The transaction fee for this unstake operation.
1312
+ - [bold white]Received[/bold white]: The amount of free balance TAO you will receive on this subnet after slippage and fees.
1231
1313
  - [bold white]Slippage[/bold white]: The slippage percentage of the unstake operation. (0% if the subnet is not dynamic i.e. root)."""
1232
1314
 
1233
1315
  safe_staking_description = """
@@ -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",