bittensor-cli 9.0.3__py3-none-any.whl → 9.1.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.
bittensor_cli/cli.py CHANGED
@@ -32,7 +32,7 @@ from bittensor_cli.version import __version__, __version_as_int__
32
32
  from bittensor_cli.src.bittensor import utils
33
33
  from bittensor_cli.src.bittensor.balances import Balance
34
34
  from async_substrate_interface.errors import SubstrateRequestException
35
- from bittensor_cli.src.commands import sudo, wallets
35
+ from bittensor_cli.src.commands import sudo, wallets, view
36
36
  from bittensor_cli.src.commands import weights as weights_cmds
37
37
  from bittensor_cli.src.commands.subnets import price, subnets
38
38
  from bittensor_cli.src.commands.stake import (
@@ -193,8 +193,7 @@ class Options:
193
193
  )
194
194
  wait_for_finalization = typer.Option(
195
195
  True,
196
- help="If `True`, waits until the transaction is finalized "
197
- "on the blockchain.",
196
+ help="If `True`, waits until the transaction is finalized on the blockchain.",
198
197
  )
199
198
  prompt = typer.Option(
200
199
  True,
@@ -513,6 +512,7 @@ class CLIManager:
513
512
  subnets_app: typer.Typer
514
513
  weights_app: typer.Typer
515
514
  utils_app = typer.Typer(epilog=_epilog)
515
+ view_app: typer.Typer
516
516
  asyncio_runner = asyncio
517
517
 
518
518
  def __init__(self):
@@ -562,6 +562,7 @@ class CLIManager:
562
562
  self.sudo_app = typer.Typer(epilog=_epilog)
563
563
  self.subnets_app = typer.Typer(epilog=_epilog)
564
564
  self.weights_app = typer.Typer(epilog=_epilog)
565
+ self.view_app = typer.Typer(epilog=_epilog)
565
566
 
566
567
  # config alias
567
568
  self.app.add_typer(
@@ -639,6 +640,14 @@ class CLIManager:
639
640
  self.utils_app, name="utils", no_args_is_help=True, hidden=True
640
641
  )
641
642
 
643
+ # view app
644
+ self.app.add_typer(
645
+ self.view_app,
646
+ name="view",
647
+ short_help="HTML view commands",
648
+ no_args_is_help=True,
649
+ )
650
+
642
651
  # config commands
643
652
  self.config_app.command("set")(self.set_config)
644
653
  self.config_app.command("get")(self.get_config)
@@ -806,6 +815,11 @@ class CLIManager:
806
815
  "commit", rich_help_panel=HELP_PANELS["WEIGHTS"]["COMMIT_REVEAL"]
807
816
  )(self.weights_commit)
808
817
 
818
+ # view commands
819
+ self.view_app.command(
820
+ "dashboard", rich_help_panel=HELP_PANELS["VIEW"]["DASHBOARD"]
821
+ )(self.view_dashboard)
822
+
809
823
  # Sub command aliases
810
824
  # Weights
811
825
  self.wallet_app.command(
@@ -1336,7 +1350,7 @@ class CLIManager:
1336
1350
  if value in Constants.networks:
1337
1351
  value = value + f" ({Constants.network_map[value]})"
1338
1352
  if key == "rate_tolerance":
1339
- value = f"{value} ({value*100}%)" if value is not None else "None"
1353
+ value = f"{value} ({value * 100}%)" if value is not None else "None"
1340
1354
 
1341
1355
  elif key in deprecated_configs:
1342
1356
  continue
@@ -1365,19 +1379,19 @@ class CLIManager:
1365
1379
  """
1366
1380
  if rate_tolerance is not None:
1367
1381
  console.print(
1368
- f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{rate_tolerance} ({rate_tolerance*100}%)[/bold cyan]."
1382
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{rate_tolerance} ({rate_tolerance * 100}%)[/bold cyan]."
1369
1383
  )
1370
1384
  return rate_tolerance
1371
1385
  elif self.config.get("rate_tolerance") is not None:
1372
1386
  config_slippage = self.config["rate_tolerance"]
1373
1387
  console.print(
1374
- f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{config_slippage} ({config_slippage*100}%)[/bold cyan] (from config)."
1388
+ f"[dim][blue]Rate tolerance[/blue]: [bold cyan]{config_slippage} ({config_slippage * 100}%)[/bold cyan] (from config)."
1375
1389
  )
1376
1390
  return config_slippage
1377
1391
  else:
1378
1392
  console.print(
1379
1393
  "[dim][blue]Rate tolerance[/blue]: "
1380
- + f"[bold cyan]{defaults.rate_tolerance} ({defaults.rate_tolerance*100}%)[/bold cyan] "
1394
+ + f"[bold cyan]{defaults.rate_tolerance} ({defaults.rate_tolerance * 100}%)[/bold cyan] "
1381
1395
  + "by default. Set this using "
1382
1396
  + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] "
1383
1397
  + "or "
@@ -5071,6 +5085,28 @@ class CLIManager:
5071
5085
  )
5072
5086
  )
5073
5087
 
5088
+ def view_dashboard(
5089
+ self,
5090
+ network: Optional[list[str]] = Options.network,
5091
+ wallet_name: str = Options.wallet_name,
5092
+ wallet_path: str = Options.wallet_path,
5093
+ wallet_hotkey: str = Options.wallet_hotkey,
5094
+ quiet: bool = Options.quiet,
5095
+ verbose: bool = Options.verbose,
5096
+ ):
5097
+ """
5098
+ Display html dashboard with subnets list, stake, and neuron information.
5099
+ """
5100
+ self.verbosity_handler(quiet, verbose)
5101
+ if is_linux():
5102
+ print_linux_dependency_message()
5103
+ wallet = self.wallet_ask(
5104
+ wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH]
5105
+ )
5106
+ return self._run_command(
5107
+ view.display_network_dashboard(wallet, self.initialize_chain(network))
5108
+ )
5109
+
5074
5110
  @staticmethod
5075
5111
  @utils_app.command("convert")
5076
5112
  def convert(
@@ -713,6 +713,9 @@ HELP_PANELS = {
713
713
  "IDENTITY": "Subnet Identity Management",
714
714
  },
715
715
  "WEIGHTS": {"COMMIT_REVEAL": "Commit / Reveal"},
716
+ "VIEW": {
717
+ "DASHBOARD": "Network Dashboard",
718
+ },
716
719
  }
717
720
 
718
721
  COLOR_PALETTE = {
@@ -297,17 +297,16 @@ class Balance:
297
297
  return self
298
298
 
299
299
 
300
- def fixed_to_float(fixed: dict) -> float:
301
- # Currently this is stored as a U64F64
300
+ def fixed_to_float(fixed, frac_bits: int = 64, total_bits: int = 128) -> float:
301
+ # By default, this is a U64F64
302
302
  # which is 64 bits of integer and 64 bits of fractional
303
- # uint_bits = 64
304
- frac_bits = 64
305
303
 
306
304
  data: int = fixed["bits"]
307
305
 
308
- # Shift bits to extract integer part (assuming 64 bits for integer part)
309
- integer_part = data >> frac_bits
306
+ # Logical and to get the fractional part; remaining is the integer part
310
307
  fractional_part = data & (2**frac_bits - 1)
308
+ # Shift to get the integer part from the remaining bits
309
+ integer_part = data >> (total_bits - frac_bits)
311
310
 
312
311
  frac_float = fractional_part / (2**frac_bits)
313
312
 
@@ -6,11 +6,12 @@ from typing import Optional, Any, Union
6
6
  import netaddr
7
7
  from scalecodec.utils.ss58 import ss58_encode
8
8
 
9
- from bittensor_cli.src.bittensor.balances import Balance
9
+ from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float
10
10
  from bittensor_cli.src.bittensor.networking import int_to_ip
11
11
  from bittensor_cli.src.bittensor.utils import (
12
12
  SS58_FORMAT,
13
- u16_normalized_float,
13
+ u16_normalized_float as u16tf,
14
+ u64_normalized_float as u64tf,
14
15
  decode_account_id,
15
16
  )
16
17
 
@@ -57,6 +58,31 @@ def process_stake_data(stake_data, netuid):
57
58
  return decoded_stake_data
58
59
 
59
60
 
61
+ def _tbwu(val: int, netuid: Optional[int] = 0) -> Balance:
62
+ """Returns a Balance object from a value and unit."""
63
+ return Balance.from_rao(val).set_unit(netuid)
64
+
65
+
66
+ def _chr_str(codes: tuple[int]) -> str:
67
+ """Converts a tuple of integer Unicode code points into a string."""
68
+ return "".join(map(chr, codes))
69
+
70
+
71
+ def process_nested(data: Union[tuple, dict], chr_transform):
72
+ """Processes nested data structures by applying a transformation function to their elements."""
73
+ if isinstance(data, (list, tuple)):
74
+ if len(data) > 0 and isinstance(data[0], dict):
75
+ return [
76
+ {k: chr_transform(v) for k, v in item.items()}
77
+ if item is not None
78
+ else None
79
+ for item in data
80
+ ]
81
+ return {}
82
+ elif isinstance(data, dict):
83
+ return {k: chr_transform(v) for k, v in data.items()}
84
+
85
+
60
86
  @dataclass
61
87
  class AxonInfo:
62
88
  version: int
@@ -312,13 +338,13 @@ class NeuronInfo(InfoBase):
312
338
  stake=total_stake,
313
339
  stake_dict=stake_dict,
314
340
  total_stake=total_stake,
315
- rank=u16_normalized_float(decoded.get("rank")),
341
+ rank=u16tf(decoded.get("rank")),
316
342
  emission=decoded.get("emission") / 1e9,
317
- incentive=u16_normalized_float(decoded.get("incentive")),
318
- consensus=u16_normalized_float(decoded.get("consensus")),
319
- trust=u16_normalized_float(decoded.get("trust")),
320
- validator_trust=u16_normalized_float(decoded.get("validator_trust")),
321
- dividends=u16_normalized_float(decoded.get("dividends")),
343
+ incentive=u16tf(decoded.get("incentive")),
344
+ consensus=u16tf(decoded.get("consensus")),
345
+ trust=u16tf(decoded.get("trust")),
346
+ validator_trust=u16tf(decoded.get("validator_trust")),
347
+ dividends=u16tf(decoded.get("dividends")),
322
348
  last_update=decoded.get("last_update"),
323
349
  validator_permit=decoded.get("validator_permit"),
324
350
  weights=[[e[0], e[1]] for e in decoded.get("weights")],
@@ -426,22 +452,22 @@ class NeuronInfoLite(InfoBase):
426
452
  coldkey=coldkey,
427
453
  ),
428
454
  coldkey=coldkey,
429
- consensus=u16_normalized_float(consensus),
430
- dividends=u16_normalized_float(dividends),
455
+ consensus=u16tf(consensus),
456
+ dividends=u16tf(dividends),
431
457
  emission=emission / 1e9,
432
458
  hotkey=hotkey,
433
- incentive=u16_normalized_float(incentive),
459
+ incentive=u16tf(incentive),
434
460
  last_update=last_update,
435
461
  netuid=netuid,
436
462
  pruning_score=pruning_score,
437
- rank=u16_normalized_float(rank),
463
+ rank=u16tf(rank),
438
464
  stake_dict=stake_dict,
439
465
  stake=stake,
440
466
  total_stake=stake,
441
- trust=u16_normalized_float(trust),
467
+ trust=u16tf(trust),
442
468
  uid=uid,
443
469
  validator_permit=validator_permit,
444
- validator_trust=u16_normalized_float(validator_trust),
470
+ validator_trust=u16tf(validator_trust),
445
471
  )
446
472
 
447
473
  return neuron
@@ -492,7 +518,7 @@ class DelegateInfo(InfoBase):
492
518
  total_stake=total_stake,
493
519
  nominators=nominators,
494
520
  owner_ss58=owner,
495
- take=u16_normalized_float(decoded.get("take")),
521
+ take=u16tf(decoded.get("take")),
496
522
  validator_permits=decoded.get("validator_permits"),
497
523
  registrations=decoded.get("registrations"),
498
524
  return_per_1000=Balance.from_rao(decoded.get("return_per_1000")),
@@ -528,7 +554,7 @@ class DelegateInfoLite(InfoBase):
528
554
  if decoded_take == 65535:
529
555
  fixed_take = None
530
556
  else:
531
- fixed_take = u16_normalized_float(decoded_take)
557
+ fixed_take = u16tf(decoded_take)
532
558
 
533
559
  return cls(
534
560
  hotkey_ss58=ss58_encode(decoded.get("delegate_ss58"), SS58_FORMAT),
@@ -581,7 +607,7 @@ class SubnetInfo(InfoBase):
581
607
  tempo=decoded.get("tempo"),
582
608
  modality=decoded.get("network_modality"),
583
609
  connection_requirements={
584
- str(int(netuid)): u16_normalized_float(int(req))
610
+ str(int(netuid)): u16tf(int(req))
585
611
  for (netuid, req) in decoded.get("network_connect")
586
612
  },
587
613
  emission_value=decoded.get("emission_value"),
@@ -844,19 +870,17 @@ class SubnetState(InfoBase):
844
870
  coldkeys=[decode_account_id(val) for val in decoded.get("coldkeys")],
845
871
  active=decoded.get("active"),
846
872
  validator_permit=decoded.get("validator_permit"),
847
- pruning_score=[
848
- u16_normalized_float(val) for val in decoded.get("pruning_score")
849
- ],
873
+ pruning_score=[u16tf(val) for val in decoded.get("pruning_score")],
850
874
  last_update=decoded.get("last_update"),
851
875
  emission=[
852
876
  Balance.from_rao(val).set_unit(netuid)
853
877
  for val in decoded.get("emission")
854
878
  ],
855
- dividends=[u16_normalized_float(val) for val in decoded.get("dividends")],
856
- incentives=[u16_normalized_float(val) for val in decoded.get("incentives")],
857
- consensus=[u16_normalized_float(val) for val in decoded.get("consensus")],
858
- trust=[u16_normalized_float(val) for val in decoded.get("trust")],
859
- rank=[u16_normalized_float(val) for val in decoded.get("rank")],
879
+ dividends=[u16tf(val) for val in decoded.get("dividends")],
880
+ incentives=[u16tf(val) for val in decoded.get("incentives")],
881
+ consensus=[u16tf(val) for val in decoded.get("consensus")],
882
+ trust=[u16tf(val) for val in decoded.get("trust")],
883
+ rank=[u16tf(val) for val in decoded.get("rank")],
860
884
  block_at_registration=decoded.get("block_at_registration"),
861
885
  alpha_stake=[
862
886
  Balance.from_rao(val).set_unit(netuid)
@@ -871,3 +895,241 @@ class SubnetState(InfoBase):
871
895
  ],
872
896
  emission_history=decoded.get("emission_history"),
873
897
  )
898
+
899
+
900
+ @dataclass
901
+ class ChainIdentity(InfoBase):
902
+ """Dataclass for chain identity information."""
903
+
904
+ name: str
905
+ url: str
906
+ github: str
907
+ image: str
908
+ discord: str
909
+ description: str
910
+ additional: str
911
+
912
+ @classmethod
913
+ def _from_dict(cls, decoded: dict) -> "ChainIdentity":
914
+ """Returns a ChainIdentity object from decoded chain data."""
915
+ return cls(
916
+ name=decoded["name"],
917
+ url=decoded["url"],
918
+ github=decoded["github_repo"],
919
+ image=decoded["image"],
920
+ discord=decoded["discord"],
921
+ description=decoded["description"],
922
+ additional=decoded["additional"],
923
+ )
924
+
925
+
926
+ @dataclass
927
+ class MetagraphInfo(InfoBase):
928
+ # Subnet index
929
+ netuid: int
930
+
931
+ # Name and symbol
932
+ name: str
933
+ symbol: str
934
+ identity: Optional[SubnetIdentity]
935
+ network_registered_at: int
936
+
937
+ # Keys for owner.
938
+ owner_hotkey: str # hotkey
939
+ owner_coldkey: str # coldkey
940
+
941
+ # Tempo terms.
942
+ block: int # block at call.
943
+ tempo: int # epoch tempo
944
+ last_step: int
945
+ blocks_since_last_step: int
946
+
947
+ # Subnet emission terms
948
+ subnet_emission: Balance # subnet emission via tao
949
+ alpha_in: Balance # amount of alpha in reserve
950
+ alpha_out: Balance # amount of alpha outstanding
951
+ tao_in: Balance # amount of tao injected per block
952
+ alpha_out_emission: Balance # amount injected in alpha reserves per block
953
+ alpha_in_emission: Balance # amount injected outstanding per block
954
+ tao_in_emission: Balance # amount of tao injected per block
955
+ pending_alpha_emission: Balance # pending alpha to be distributed
956
+ pending_root_emission: Balance # pending tao for root divs to be distributed
957
+ subnet_volume: Balance # volume of the subnet in TAO
958
+ moving_price: Balance # subnet moving price.
959
+
960
+ # Hparams for epoch
961
+ rho: int # subnet rho param
962
+ kappa: float # subnet kappa param
963
+
964
+ # Validator params
965
+ min_allowed_weights: float # min allowed weights per val
966
+ max_weights_limit: float # max allowed weights per val
967
+ weights_version: int # allowed weights version
968
+ weights_rate_limit: int # rate limit on weights.
969
+ activity_cutoff: int # validator weights cut off period in blocks
970
+ max_validators: int # max allowed validators.
971
+
972
+ # Registration
973
+ num_uids: int
974
+ max_uids: int
975
+ burn: Balance # current burn cost.
976
+ difficulty: float # current difficulty.
977
+ registration_allowed: bool # allows registrations.
978
+ pow_registration_allowed: bool # pow registration enabled.
979
+ immunity_period: int # subnet miner immunity period
980
+ min_difficulty: float # min pow difficulty
981
+ max_difficulty: float # max pow difficulty
982
+ min_burn: Balance # min tao burn
983
+ max_burn: Balance # max tao burn
984
+ adjustment_alpha: float # adjustment speed for registration params.
985
+ adjustment_interval: int # pow and burn adjustment interval
986
+ target_regs_per_interval: int # target registrations per interval
987
+ max_regs_per_block: int # max registrations per block.
988
+ serving_rate_limit: int # axon serving rate limit
989
+
990
+ # CR
991
+ commit_reveal_weights_enabled: bool # Is CR enabled.
992
+ commit_reveal_period: int # Commit reveal interval
993
+
994
+ # Bonds
995
+ liquid_alpha_enabled: bool # Bonds liquid enabled.
996
+ alpha_high: float # Alpha param high
997
+ alpha_low: float # Alpha param low
998
+ bonds_moving_avg: float # Bonds moving avg
999
+
1000
+ # Metagraph info.
1001
+ hotkeys: list[str] # hotkey per UID
1002
+ coldkeys: list[str] # coldkey per UID
1003
+ identities: list[Optional[ChainIdentity]] # coldkeys identities
1004
+ axons: list[AxonInfo] # UID axons.
1005
+ active: list[bool] # Active per UID
1006
+ validator_permit: list[bool] # Val permit per UID
1007
+ pruning_score: list[float] # Pruning per UID
1008
+ last_update: list[int] # Last update per UID
1009
+ emission: list[Balance] # Emission per UID
1010
+ dividends: list[float] # Dividends per UID
1011
+ incentives: list[float] # Mining incentives per UID
1012
+ consensus: list[float] # Consensus per UID
1013
+ trust: list[float] # Trust per UID
1014
+ rank: list[float] # Rank per UID
1015
+ block_at_registration: list[int] # Reg block per UID
1016
+ alpha_stake: list[Balance] # Alpha staked per UID
1017
+ tao_stake: list[Balance] # TAO staked per UID
1018
+ total_stake: list[Balance] # Total stake per UID
1019
+
1020
+ # Dividend break down.
1021
+ tao_dividends_per_hotkey: list[
1022
+ tuple[str, Balance]
1023
+ ] # List of dividend payouts in tao via root.
1024
+ alpha_dividends_per_hotkey: list[
1025
+ tuple[str, Balance]
1026
+ ] # List of dividend payout in alpha via subnet.
1027
+
1028
+ @classmethod
1029
+ def _fix_decoded(cls, decoded: dict) -> "MetagraphInfo":
1030
+ """Returns a MetagraphInfo object from decoded chain data."""
1031
+ # Subnet index
1032
+ _netuid = decoded["netuid"]
1033
+
1034
+ # Name and symbol
1035
+ decoded.update({"name": bytes(decoded.get("name")).decode()})
1036
+ decoded.update({"symbol": bytes(decoded.get("symbol")).decode()})
1037
+ for key in ["identities", "identity"]:
1038
+ raw_data = decoded.get(key)
1039
+ processed = process_nested(raw_data, _chr_str)
1040
+ decoded.update({key: processed})
1041
+
1042
+ return cls(
1043
+ # Subnet index
1044
+ netuid=_netuid,
1045
+ # Name and symbol
1046
+ name=decoded["name"],
1047
+ symbol=decoded["symbol"],
1048
+ identity=decoded["identity"],
1049
+ network_registered_at=decoded["network_registered_at"],
1050
+ # Keys for owner.
1051
+ owner_hotkey=decoded["owner_hotkey"],
1052
+ owner_coldkey=decoded["owner_coldkey"],
1053
+ # Tempo terms.
1054
+ block=decoded["block"],
1055
+ tempo=decoded["tempo"],
1056
+ last_step=decoded["last_step"],
1057
+ blocks_since_last_step=decoded["blocks_since_last_step"],
1058
+ # Subnet emission terms
1059
+ subnet_emission=_tbwu(decoded["subnet_emission"]),
1060
+ alpha_in=_tbwu(decoded["alpha_in"], _netuid),
1061
+ alpha_out=_tbwu(decoded["alpha_out"], _netuid),
1062
+ tao_in=_tbwu(decoded["tao_in"]),
1063
+ alpha_out_emission=_tbwu(decoded["alpha_out_emission"], _netuid),
1064
+ alpha_in_emission=_tbwu(decoded["alpha_in_emission"], _netuid),
1065
+ tao_in_emission=_tbwu(decoded["tao_in_emission"]),
1066
+ pending_alpha_emission=_tbwu(decoded["pending_alpha_emission"], _netuid),
1067
+ pending_root_emission=_tbwu(decoded["pending_root_emission"]),
1068
+ subnet_volume=_tbwu(decoded["subnet_volume"], _netuid),
1069
+ moving_price=Balance.from_tao(
1070
+ fixed_to_float(decoded.get("moving_price"), 32)
1071
+ ),
1072
+ # Hparams for epoch
1073
+ rho=decoded["rho"],
1074
+ kappa=decoded["kappa"],
1075
+ # Validator params
1076
+ min_allowed_weights=u16tf(decoded["min_allowed_weights"]),
1077
+ max_weights_limit=u16tf(decoded["max_weights_limit"]),
1078
+ weights_version=decoded["weights_version"],
1079
+ weights_rate_limit=decoded["weights_rate_limit"],
1080
+ activity_cutoff=decoded["activity_cutoff"],
1081
+ max_validators=decoded["max_validators"],
1082
+ # Registration
1083
+ num_uids=decoded["num_uids"],
1084
+ max_uids=decoded["max_uids"],
1085
+ burn=_tbwu(decoded["burn"]),
1086
+ difficulty=u64tf(decoded["difficulty"]),
1087
+ registration_allowed=decoded["registration_allowed"],
1088
+ pow_registration_allowed=decoded["pow_registration_allowed"],
1089
+ immunity_period=decoded["immunity_period"],
1090
+ min_difficulty=u64tf(decoded["min_difficulty"]),
1091
+ max_difficulty=u64tf(decoded["max_difficulty"]),
1092
+ min_burn=_tbwu(decoded["min_burn"]),
1093
+ max_burn=_tbwu(decoded["max_burn"]),
1094
+ adjustment_alpha=u64tf(decoded["adjustment_alpha"]),
1095
+ adjustment_interval=decoded["adjustment_interval"],
1096
+ target_regs_per_interval=decoded["target_regs_per_interval"],
1097
+ max_regs_per_block=decoded["max_regs_per_block"],
1098
+ serving_rate_limit=decoded["serving_rate_limit"],
1099
+ # CR
1100
+ commit_reveal_weights_enabled=decoded["commit_reveal_weights_enabled"],
1101
+ commit_reveal_period=decoded["commit_reveal_period"],
1102
+ # Bonds
1103
+ liquid_alpha_enabled=decoded["liquid_alpha_enabled"],
1104
+ alpha_high=u16tf(decoded["alpha_high"]),
1105
+ alpha_low=u16tf(decoded["alpha_low"]),
1106
+ bonds_moving_avg=u64tf(decoded["bonds_moving_avg"]),
1107
+ # Metagraph info.
1108
+ hotkeys=[decode_account_id(ck) for ck in decoded.get("hotkeys", [])],
1109
+ coldkeys=[decode_account_id(hk) for hk in decoded.get("coldkeys", [])],
1110
+ identities=decoded["identities"],
1111
+ axons=decoded.get("axons", []),
1112
+ active=decoded["active"],
1113
+ validator_permit=decoded["validator_permit"],
1114
+ pruning_score=[u16tf(ps) for ps in decoded.get("pruning_score", [])],
1115
+ last_update=decoded["last_update"],
1116
+ emission=[_tbwu(em, _netuid) for em in decoded.get("emission", [])],
1117
+ dividends=[u16tf(dv) for dv in decoded.get("dividends", [])],
1118
+ incentives=[u16tf(ic) for ic in decoded.get("incentives", [])],
1119
+ consensus=[u16tf(cs) for cs in decoded.get("consensus", [])],
1120
+ trust=[u16tf(tr) for tr in decoded.get("trust", [])],
1121
+ rank=[u16tf(rk) for rk in decoded.get("rank", [])],
1122
+ block_at_registration=decoded["block_at_registration"],
1123
+ alpha_stake=[_tbwu(ast, _netuid) for ast in decoded["alpha_stake"]],
1124
+ tao_stake=[_tbwu(ts) for ts in decoded["tao_stake"]],
1125
+ total_stake=[_tbwu(ts, _netuid) for ts in decoded["total_stake"]],
1126
+ # Dividend break down
1127
+ tao_dividends_per_hotkey=[
1128
+ (decode_account_id(alpha[0]), _tbwu(alpha[1]))
1129
+ for alpha in decoded["tao_dividends_per_hotkey"]
1130
+ ],
1131
+ alpha_dividends_per_hotkey=[
1132
+ (decode_account_id(adphk[0]), _tbwu(adphk[1], _netuid))
1133
+ for adphk in decoded["alpha_dividends_per_hotkey"]
1134
+ ],
1135
+ )
@@ -20,6 +20,7 @@ from bittensor_cli.src.bittensor.chain_data import (
20
20
  decode_hex_identity,
21
21
  DynamicInfo,
22
22
  SubnetState,
23
+ MetagraphInfo,
23
24
  )
24
25
  from bittensor_cli.src import DelegatesDetails
25
26
  from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float
@@ -1252,6 +1253,38 @@ class SubtensorInterface:
1252
1253
  else:
1253
1254
  return Balance.from_rao(_result).set_unit(int(netuid))
1254
1255
 
1256
+ async def get_metagraph_info(
1257
+ self, netuid: int, block_hash: Optional[str] = None
1258
+ ) -> Optional[MetagraphInfo]:
1259
+ hex_bytes_result = await self.query_runtime_api(
1260
+ runtime_api="SubnetInfoRuntimeApi",
1261
+ method="get_metagraph",
1262
+ params=[netuid],
1263
+ block_hash=block_hash,
1264
+ )
1265
+
1266
+ if hex_bytes_result is None:
1267
+ return None
1268
+
1269
+ try:
1270
+ bytes_result = bytes.fromhex(hex_bytes_result[2:])
1271
+ except ValueError:
1272
+ bytes_result = bytes.fromhex(hex_bytes_result)
1273
+
1274
+ return MetagraphInfo.from_any(bytes_result)
1275
+
1276
+ async def get_all_metagraphs_info(
1277
+ self, block_hash: Optional[str] = None
1278
+ ) -> list[MetagraphInfo]:
1279
+ hex_bytes_result = await self.query_runtime_api(
1280
+ runtime_api="SubnetInfoRuntimeApi",
1281
+ method="get_all_metagraphs",
1282
+ params=[],
1283
+ block_hash=block_hash,
1284
+ )
1285
+
1286
+ return MetagraphInfo.list_from_any(hex_bytes_result)
1287
+
1255
1288
  async def multi_get_stake_for_coldkey_and_hotkey_on_netuid(
1256
1289
  self,
1257
1290
  hotkey_ss58s: list[str],
@@ -1279,14 +1279,25 @@ def print_linux_dependency_message():
1279
1279
  """Prints the WebKit dependency message for Linux systems."""
1280
1280
  console.print("[red]This command requires WebKit dependencies on Linux.[/red]")
1281
1281
  console.print(
1282
- "\nPlease install the required packages using one of the following commands based on your distribution:"
1282
+ "\nPlease make sure these packages are installed on your system for PyWry to work:"
1283
1283
  )
1284
1284
  console.print("\nArch Linux / Manjaro:")
1285
1285
  console.print("[green]sudo pacman -S webkit2gtk[/green]")
1286
1286
  console.print("\nDebian / Ubuntu:")
1287
1287
  console.print("[green]sudo apt install libwebkit2gtk-4.0-dev[/green]")
1288
+ console.print("\nNote for Ubuntu 24.04+ & Debian 13+:")
1289
+ console.print("You may need these additional steps to install libwebkit2gtk:")
1290
+ console.print(
1291
+ "\tCreate a new source file with: [green]sudo vim /etc/apt/sources.list.d/jammy-temp.list[/green]"
1292
+ )
1293
+ console.print(
1294
+ "\tAdd this into the file and save: [green]deb http://archive.ubuntu.com/ubuntu jammy main universe[/green]"
1295
+ )
1296
+ console.print(
1297
+ "\tUpdate the repository and install the webkit dependency: [green]sudo apt update && sudo apt install libwebkit2gtk-4.0-dev[/green]"
1298
+ )
1288
1299
  console.print("\nFedora / CentOS / AlmaLinux:")
1289
- console.print("[green]sudo dnf install gtk3-devel webkit2gtk3-devel[/green]")
1300
+ console.print("[green]sudo dnf install gtk3-devel webkit2gtk3-devel[/green]\n\n")
1290
1301
 
1291
1302
 
1292
1303
  def is_linux():
@@ -335,8 +335,7 @@ async def stake_move_selection(
335
335
 
336
336
 
337
337
  async def stake_transfer_selection(
338
- wallet: Wallet,
339
- subtensor: "SubtensorInterface",
338
+ wallet: Wallet, subtensor: "SubtensorInterface", origin_hotkey: str
340
339
  ):
341
340
  """Selection interface for transferring stakes."""
342
341
  (
@@ -353,7 +352,7 @@ async def stake_transfer_selection(
353
352
 
354
353
  available_stakes = {}
355
354
  for stake in stakes:
356
- if stake.stake.tao > 0 and stake.hotkey_ss58 == wallet.hotkey.ss58_address:
355
+ if stake.stake.tao > 0 and stake.hotkey_ss58 == origin_hotkey:
357
356
  available_stakes[stake.netuid] = {
358
357
  "hotkey_ss58": stake.hotkey_ss58,
359
358
  "stake": stake.stake,
@@ -718,8 +717,9 @@ async def transfer_stake(
718
717
  Returns:
719
718
  bool: True if transfer was successful, False otherwise.
720
719
  """
720
+ origin_hotkey = origin_hotkey or wallet.hotkey.ss58_address
721
721
  if interactive_selection:
722
- selection = await stake_transfer_selection(wallet, subtensor)
722
+ selection = await stake_transfer_selection(wallet, subtensor, origin_hotkey)
723
723
  origin_netuid = selection["origin_netuid"]
724
724
  amount = selection["amount"]
725
725
  dest_netuid = selection["destination_netuid"]