From 7212a46193d20a7b4a654b9b9fb2bc175f7fc756 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 25 Sep 2024 16:29:03 +0200 Subject: [PATCH 001/157] Initial commit --- bittensor_cli/src/__init__.py | 178 ++++++++++++++++++++-- bittensor_cli/src/bittensor/chain_data.py | 136 +++++++++++++++++ 2 files changed, 303 insertions(+), 11 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index a5a87e16..6df30907 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -4,16 +4,20 @@ class Constants: - networks = ["local", "finney", "test", "archive"] + networks = ["local", "finney", "test", "archive", "rao", "dev"] finney_entrypoint = "wss://entrypoint-finney.opentensor.ai:443" finney_test_entrypoint = "wss://test.finney.opentensor.ai:443" archive_entrypoint = "wss://archive.chain.opentensor.ai:443" + rao_entrypoint = "wss://rao.chain.opentensor.ai:443/" + dev_entrypoint = "wss://dev.chain.opentensor.ai:443 " local_entrypoint = "ws://127.0.0.1:9444" network_map = { "finney": finney_entrypoint, "test": finney_test_entrypoint, "archive": archive_entrypoint, "local": local_entrypoint, + "dev": dev_entrypoint, + "rao": rao_entrypoint } delegates_detail_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" @@ -206,24 +210,34 @@ class WalletValidationTypes(Enum): "StakeInfoRuntimeApi": { "methods": { "get_stake_info_for_coldkey": { + "params": [{"name": "coldkey_account_vec", "type": "Vec"}], + "type": "Vec", + }, + "get_stake_info_for_coldkeys": { "params": [ - { - "name": "coldkey_account_vec", - "type": "Vec", - }, + {"name": "coldkey_account_vecs", "type": "Vec>"} ], "type": "Vec", }, - "get_stake_info_for_coldkeys": { + "get_subnet_stake_info_for_coldkeys": { "params": [ - { - "name": "coldkey_account_vecs", - "type": "Vec>", - }, + {"name": "coldkey_account_vecs", "type": "Vec>"}, + {"name": "netuid", "type": "u16"}, ], "type": "Vec", }, - }, + "get_subnet_stake_info_for_coldkey": { + "params": [ + {"name": "coldkey_account_vec", "type": "Vec"}, + {"name": "netuid", "type": "u16"}, + ], + "type": "Vec", + }, + "get_total_subnet_stake": { + "params": [{"name": "netuid", "type": "u16"}], + "type": "Vec", + }, + } }, "ValidatorIPRuntimeApi": { "methods": { @@ -301,6 +315,148 @@ class WalletValidationTypes(Enum): }, } +UNITS = [ + "\u03c4", # τ (tau, 0) + "\u03b1", # α (alpha, 1) + "\u03b2", # β (beta, 2) + "\u03b3", # γ (gamma, 3) + "\u03b4", # δ (delta, 4) + "\u03b5", # ε (epsilon, 5) + "\u03b6", # ζ (zeta, 6) + "\u03b7", # η (eta, 7) + "\u03b8", # θ (theta, 8) + "\u03b9", # ι (iota, 9) + "\u03ba", # κ (kappa, 10) + "\u03bb", # λ (lambda, 11) + "\u03bc", # μ (mu, 12) + "\u03bd", # ν (nu, 13) + "\u03be", # ξ (xi, 14) + "\u03bf", # ο (omicron, 15) + "\u03c0", # π (pi, 16) + "\u03c1", # ρ (rho, 17) + "\u03c3", # σ (sigma, 18) + "t", # t (tau, 19) + "\u03c5", # υ (upsilon, 20) + "\u03c6", # φ (phi, 21) + "\u03c7", # χ (chi, 22) + "\u03c8", # ψ (psi, 23) + "\u03c9", # ω (omega, 24) + # Hebrew letters + "\u05d0", # א (aleph, 25) + "\u05d1", # ב (bet, 26) + "\u05d2", # ג (gimel, 27) + "\u05d3", # ד (dalet, 28) + "\u05d4", # ה (he, 29) + "\u05d5", # ו (vav, 30) + "\u05d6", # ז (zayin, 31) + "\u05d7", # ח (het, 32) + "\u05d8", # ט (tet, 33) + "\u05d9", # י (yod, 34) + "\u05da", # ך (final kaf, 35) + "\u05db", # כ (kaf, 36) + "\u05dc", # ל (lamed, 37) + "\u05dd", # ם (final mem, 38) + "\u05de", # מ (mem, 39) + "\u05df", # ן (final nun, 40) + "\u05e0", # נ (nun, 41) + "\u05e1", # ס (samekh, 42) + "\u05e2", # ע (ayin, 43) + "\u05e3", # ף (final pe, 44) + "\u05e4", # פ (pe, 45) + "\u05e5", # ץ (final tsadi, 46) + "\u05e6", # צ (tsadi, 47) + "\u05e7", # ק (qof, 48) + "\u05e8", # ר (resh, 49) + "\u05e9", # ש (shin, 50) + "\u05ea", # ת (tav, 51) + # Georgian Alphabet (Mkhedruli) + "\u10d0", # ა (Ani, 97) + "\u10d1", # ბ (Bani, 98) + "\u10d2", # გ (Gani, 99) + "\u10d3", # დ (Doni, 100) + "\u10d4", # ე (Eni, 101) + "\u10d5", # ვ (Vini, 102) + # Armenian Alphabet + "\u0531", # Ա (Ayp, 103) + "\u0532", # Բ (Ben, 104) + "\u0533", # Գ (Gim, 105) + "\u0534", # Դ (Da, 106) + "\u0535", # Ե (Ech, 107) + "\u0536", # Զ (Za, 108) + # "\u055e", # ՞ (Question mark, 109) + # Runic Alphabet + "\u16a0", # ᚠ (Fehu, wealth, 81) + "\u16a2", # ᚢ (Uruz, strength, 82) + "\u16a6", # ᚦ (Thurisaz, giant, 83) + "\u16a8", # ᚨ (Ansuz, god, 84) + "\u16b1", # ᚱ (Raidho, ride, 85) + "\u16b3", # ᚲ (Kaunan, ulcer, 86) + "\u16c7", # ᛇ (Eihwaz, yew, 87) + "\u16c9", # ᛉ (Algiz, protection, 88) + "\u16d2", # ᛒ (Berkanan, birch, 89) + # Cyrillic Alphabet + "\u0400", # Ѐ (Ie with grave, 110) + "\u0401", # Ё (Io, 111) + "\u0402", # Ђ (Dje, 112) + "\u0403", # Ѓ (Gje, 113) + "\u0404", # Є (Ukrainian Ie, 114) + "\u0405", # Ѕ (Dze, 115) + # Coptic Alphabet + "\u2c80", # Ⲁ (Alfa, 116) + "\u2c81", # ⲁ (Small Alfa, 117) + "\u2c82", # Ⲃ (Vida, 118) + "\u2c83", # ⲃ (Small Vida, 119) + "\u2c84", # Ⲅ (Gamma, 120) + "\u2c85", # ⲅ (Small Gamma, 121) + # Arabic letters + "\u0627", # ا (alef, 52) + "\u0628", # ب (ba, 53) + "\u062a", # ت (ta, 54) + "\u062b", # ث (tha, 55) + "\u062c", # ج (jeem, 56) + "\u062d", # ح (ha, 57) + "\u062e", # خ (kha, 58) + "\u062f", # د (dal, 59) + "\u0630", # ذ (dhal, 60) + "\u0631", # ر (ra, 61) + "\u0632", # ز (zay, 62) + "\u0633", # س (seen, 63) + "\u0634", # ش (sheen, 64) + "\u0635", # ص (sad, 65) + "\u0636", # ض (dad, 66) + "\u0637", # ط (ta, 67) + "\u0638", # ظ (dha, 68) + "\u0639", # ع (ain, 69) + "\u063a", # غ (ghain, 70) + "\u0641", # ف (fa, 71) + "\u0642", # ق (qaf, 72) + "\u0643", # ك (kaf, 73) + "\u0644", # ل (lam, 74) + "\u0645", # م (meem, 75) + "\u0646", # ن (noon, 76) + "\u0647", # ه (ha, 77) + "\u0648", # و (waw, 78) + "\u0649", # ى (alef maksura, 79) + "\u064a", # ي (ya, 80) + # Ogham Alphabet + "\u1680", #   (Space, 90) + "\u1681", # ᚁ (Beith, birch, 91) + "\u1682", # ᚂ (Luis, rowan, 92) + "\u1683", # ᚃ (Fearn, alder, 93) + "\u1684", # ᚄ (Sail, willow, 94) + "\u1685", # ᚅ (Nion, ash, 95) + "\u169b", # ᚛ (Forfeda, 96) + # Brahmi Script TODO verify these https://discord.com/channels/799672011265015819/1176889593136693339/1288500713625878558 + "\u11000", # 𑀀 (A, 122) + "\u11001", # 𑀁 (Aa, 123) + "\u11002", # 𑀂 (I, 124) + "\u11003", # 𑀃 (Ii, 125) + "\u11005", # 𑀅 (U, 126) + # Tifinagh Alphabet + "\u2d30", # ⴰ (Ya, 127) + "\u2d31", # ⴱ (Yab, 128) +] + NETWORK_EXPLORER_MAP = { "opentensor": { "local": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 73f41b1f..b316d8e2 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -571,6 +571,47 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfo"]: ) return result +@dataclass +class SubnetInfoV2: + """Dataclass for subnet info.""" + netuid: int + owner_ss58: str + max_allowed_validators: int + scaling_law_power: float + subnetwork_n: int + max_n: int + blocks_since_epoch: int + modality: int + emission_value: float + burn: Balance + tao_locked: Balance + hyperparameters: "SubnetHyperparameters" + dynamic_pool: "DynamicPool" + + @classmethod + def from_vec_u8(cls, vec_u8: bytes) -> Optional["SubnetInfoV2"]: + """Returns a SubnetInfoV2 object from a ``vec_u8``.""" + if len(vec_u8) == 0: + return None + decoded = bt_decode.SubnetInfoV2.decode(vec_u8) # TODO fix values + + if decoded is None: + return None + + return cls.fix_decoded_values(decoded) + + @classmethod + def list_from_vec_u8(cls, vec_u8: bytes) -> List["SubnetInfoV2"]: + """Returns a list of SubnetInfoV2 objects from a ``vec_u8``.""" + decoded = bt_decode.SubnetInfoV2.decode_vec(vec_u8) # TODO fix values + + if decoded is None: + return [] + + decoded = [cls.fix_decoded_values(d) for d in decoded] + + return decoded + custom_rpc_type_registry = { "types": { @@ -597,6 +638,35 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfo"]: ["owner", "AccountId"], ], }, + "DynamicPoolInfoV2": { + "type": "struct", + "type_mapping": [ + ["netuid", "u16"], + ["alpha_issuance", "u64"], + ["alpha_outstanding", "u64"], + ["alpha_reserve", "u64"], + ["tao_reserve", "u64"], + ["k", "u128"], + ], + }, + "SubnetInfoV2": { + "type": "struct", + "type_mapping": [ + ["netuid", "u16"], + ["owner", "AccountId"], + ["max_allowed_validators", "u16"], + ["scaling_law_power", "u16"], + ["subnetwork_n", "u16"], + ["max_allowed_uids", "u16"], + ["blocks_since_last_step", "Compact"], + ["network_modality", "u16"], + ["emission_values", "Compact"], + ["burn", "Compact"], + ["tao_locked", "Compact"], + ["hyperparameters", "SubnetHyperparameters"], + ["dynamic_pool", "Option"], + ], + }, "DelegateInfo": { "type": "struct", "type_mapping": [ @@ -610,6 +680,16 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfo"]: ["total_daily_return", "Compact"], ], }, + "DelegateInfoLight": { + "type": "struct", + "type_mapping": [ + ["delegate_ss58", "AccountId"], + ["owner_ss58", "AccountId"], + ["take", "u16"], + ["owner_stake", "Compact"], + ["total_stake", "Compact"], + ], + }, "NeuronInfo": { "type": "struct", "type_mapping": [ @@ -688,6 +768,37 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfo"]: ["ip_type_and_protocol", "Compact"], ], }, + "ScheduledColdkeySwapInfo": { + "type": "struct", + "type_mapping": [ + ["old_coldkey", "AccountId"], + ["new_coldkey", "AccountId"], + ["arbitration_block", "Compact"], + ], + }, + "SubnetState": { + "type": "struct", + "type_mapping": [ + ["netuid", "Compact"], + ["hotkeys", "Vec"], + ["coldkeys", "Vec"], + ["active", "Vec"], + ["validator_permit", "Vec"], + ["pruning_score", "Vec>"], + ["last_update", "Vec>"], + ["emission", "Vec>"], + ["dividends", "Vec>"], + ["incentives", "Vec>"], + ["consensus", "Vec>"], + ["trust", "Vec>"], + ["rank", "Vec>"], + ["block_at_registration", "Vec>"], + ["local_stake", "Vec>"], + ["global_stake", "Vec>"], + ["stake_weight", "Vec>"], + ["emission_history", "Vec>>"], + ], + }, "StakeInfo": { "type": "struct", "type_mapping": [ @@ -696,6 +807,31 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfo"]: ["stake", "Compact"], ], }, + "DynamicInfo": { + "type": "struct", + "type_mapping": [ + ["owner", "AccountId"], + ["netuid", "Compact"], + ["tempo", "Compact"], + ["last_step", "Compact"], + ["blocks_since_last_step", "Compact"], + ["emission", "Compact"], + ["alpha_in", "Compact"], + ["alpha_out", "Compact"], + ["tao_in", "Compact"], + ["total_locked", "Compact"], + ["owner_locked", "Compact"], + ], + }, + "SubstakeElements": { + "type": "struct", + "type_mapping": [ + ["hotkey", "AccountId"], + ["coldkey", "AccountId"], + ["netuid", "Compact"], + ["stake", "Compact"], + ], + }, "SubnetHyperparameters": { "type": "struct", "type_mapping": [ From e58e5a8fc9b7bab61ad913b0c2e200ed6903c36d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 25 Sep 2024 16:37:22 +0200 Subject: [PATCH 002/157] Ruff --- bittensor_cli/src/__init__.py | 2 +- bittensor_cli/src/bittensor/chain_data.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 6df30907..acd8cd90 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -17,7 +17,7 @@ class Constants: "archive": archive_entrypoint, "local": local_entrypoint, "dev": dev_entrypoint, - "rao": rao_entrypoint + "rao": rao_entrypoint, } delegates_detail_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index b316d8e2..415fb55e 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -571,9 +571,11 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfo"]: ) return result + @dataclass class SubnetInfoV2: """Dataclass for subnet info.""" + netuid: int owner_ss58: str max_allowed_validators: int @@ -776,7 +778,7 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> List["SubnetInfoV2"]: ["arbitration_block", "Compact"], ], }, - "SubnetState": { + "SubnetState": { "type": "struct", "type_mapping": [ ["netuid", "Compact"], From 683273ff21ad1c8cc9541feb6be644cf0a0b4689 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 27 Sep 2024 12:39:09 +0200 Subject: [PATCH 003/157] Data classes --- bittensor_cli/src/bittensor/chain_data.py | 437 +++++++++++++++++++++- 1 file changed, 436 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 415fb55e..61eb20e3 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -603,7 +603,7 @@ def from_vec_u8(cls, vec_u8: bytes) -> Optional["SubnetInfoV2"]: return cls.fix_decoded_values(decoded) @classmethod - def list_from_vec_u8(cls, vec_u8: bytes) -> List["SubnetInfoV2"]: + def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfoV2"]: """Returns a list of SubnetInfoV2 objects from a ``vec_u8``.""" decoded = bt_decode.SubnetInfoV2.decode_vec(vec_u8) # TODO fix values @@ -615,6 +615,441 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> List["SubnetInfoV2"]: return decoded +@dataclass +class DynamicInfo: + owner: str + netuid: int + tempo: int + last_step: int + blocks_since_last_step: int + emission: Balance + alpha_in: Balance + alpha_out: Balance + tao_in: Balance + total_locked: Balance + owner_locked: Balance + price: Balance + k: float + is_dynamic: bool + symbol: str + + @classmethod + def from_vec_u8(cls, vec_u8: list[int]) -> Optional["DynamicInfo"]: + if len(vec_u8) == 0: + return None + decoded = from_scale_encoding(vec_u8, ChainDataType.DynamicInfo, is_option=True) + if decoded is None: + return None + return DynamicInfo.fix_decoded_values(decoded) + + @classmethod + def list_from_vec_u8(cls, vec_u8: list[int]) -> list["DynamicInfo"]: + decoded = from_scale_encoding( + vec_u8, ChainDataType.DynamicInfo, is_vec=True, is_option=True + ) + if decoded is None: + return [] + decoded = [DynamicInfo.fix_decoded_values(d) for d in decoded] + return decoded + + @classmethod + def fix_decoded_values(cls, decoded: dict) -> "DynamicInfo": + netuid = int(decoded["netuid"]) + symbol = Balance.get_unit(netuid) + emission = Balance.from_rao(decoded["emission"]).set_unit(0) + alpha_out = Balance.from_rao(decoded["alpha_out"]).set_unit(netuid) + alpha_in = Balance.from_rao(decoded["alpha_in"]).set_unit(netuid) + tao_in = Balance.from_rao(decoded["tao_in"]).set_unit(0) + total_locked = Balance.from_rao(decoded["total_locked"]).set_unit( + netuid + ) + owner_locked = Balance.from_rao(decoded["owner_locked"]).set_unit( + netuid + ) + price = ( + Balance.from_tao(tao_in.tao / alpha_in.tao) + if alpha_in.tao > 0 + else Balance.from_tao(1) + ) + is_dynamic = True if decoded["alpha_in"] > 0 else False + return DynamicInfo( + owner=ss58_encode(decoded["owner"], SS58_FORMAT), + netuid=netuid, + tempo=decoded["tempo"], + last_step=decoded["last_step"], + blocks_since_last_step=decoded["blocks_since_last_step"], + emission=emission, + alpha_out=alpha_out, + alpha_in=alpha_in, + tao_in=tao_in, + total_locked=total_locked, + owner_locked=owner_locked, + price=price, + k=tao_in.rao * alpha_in.rao, + is_dynamic=is_dynamic, + symbol=symbol, + ) + + def tao_to_alpha(self, tao: Balance) -> Balance: + if self.price.tao != 0: + return Balance.from_tao(tao.tao / self.price.tao).set_unit(self.netuid) + else: + return Balance.from_tao(0) + + def alpha_to_tao(self, alpha: Balance) -> Balance: + return Balance.from_tao(alpha.tao * self.price.tao) + + def tao_to_alpha_with_slippage(self, tao: Balance) -> tuple[Balance, Balance]: + """ + Returns an estimate of how much Alpha would a staker receive if they stake their tao using the current pool state. + Args: + tao: Amount of TAO to stake. + Returns: + Tuple of balances where the first part is the amount of Alpha received, and the + second part (slippage) is the difference between the estimated amount and ideal + amount as if there was no slippage + """ + if self.is_dynamic: + new_tao_in = self.tao_in + tao + if new_tao_in == 0: + return tao, Balance.from_rao(0) + new_alpha_in = self.k / new_tao_in + + # Amount of alpha given to the staker + alpha_returned = Balance.from_rao( + self.alpha_in.rao - new_alpha_in.rao + ).set_unit(self.netuid) + + # Ideal conversion as if there is no slippage, just price + alpha_ideal = self.tao_to_alpha(tao) + + if alpha_ideal.tao > alpha_returned.tao: + slippage = Balance.from_tao( + alpha_ideal.tao - alpha_returned.tao + ).set_unit(self.netuid) + else: + slippage = Balance.from_tao(0) + else: + alpha_returned = tao.set_unit(self.netuid) + slippage = Balance.from_tao(0) + return alpha_returned, slippage + + def alpha_to_tao_with_slippage(self, alpha: Balance) -> tuple[Balance, Balance]: + """ + Returns an estimate of how much TAO would a staker receive if they unstake their alpha using the current pool state. + Args: + alpha: Amount of Alpha to stake. + Returns: + Tuple of balances where the first part is the amount of TAO received, and the + second part (slippage) is the difference between the estimated amount and ideal + amount as if there was no slippage + """ + if self.is_dynamic: + new_alpha_in = self.alpha_in + alpha + new_tao_reserve = self.k / new_alpha_in + # Amount of TAO given to the unstaker + tao_returned = Balance.from_rao(self.tao_in - new_tao_reserve) + + # Ideal conversion as if there is no slippage, just price + tao_ideal = self.alpha_to_tao(alpha) + + if tao_ideal > tao_returned: + slippage = Balance.from_tao(tao_ideal.tao - tao_returned.tao) + else: + slippage = Balance.from_tao(0) + else: + tao_returned = alpha.set_unit(0) + slippage = Balance.from_tao(0) + return tao_returned, slippage + + +@dataclass +class DynamicPoolInfoV2: + """Dataclass for dynamic pool info.""" + netuid: int + alpha_issuance: int + alpha_outstanding: int + alpha_reserve: int + tao_reserve: int + k: int + + @classmethod + def from_vec_u8(cls, vec_u8: list[int]) -> Optional["DynamicPoolInfoV2"]: + """Returns a DynamicPoolInfoV2 object from a ``vec_u8``.""" + if len(vec_u8) == 0: + return None + return from_scale_encoding(vec_u8, ChainDataType.DynamicPoolInfoV2) + + +@dataclass +class DynamicPool: + is_dynamic: bool + alpha_issuance: Balance + alpha_outstanding: Balance + alpha_reserve: Balance + tao_reserve: Balance + k: int + price: Balance + netuid: int + + def __init__( + self, + is_dynamic: bool, + netuid: int, + alpha_issuance: Union[int, Balance], + alpha_outstanding: Union[int, Balance], + alpha_reserve: Union[int, Balance], + tao_reserve: Union[int, Balance], + k: int, + ): + self.is_dynamic = is_dynamic + self.netuid = netuid + self.alpha_issuance = ( + alpha_issuance + if isinstance(alpha_issuance, Balance) + else Balance.from_rao(alpha_issuance).set_unit(netuid) + ) + self.alpha_outstanding = ( + alpha_outstanding + if isinstance(alpha_outstanding, Balance) + else Balance.from_rao(alpha_outstanding).set_unit(netuid) + ) + self.alpha_reserve = ( + alpha_reserve + if isinstance(alpha_reserve, Balance) + else Balance.from_rao(alpha_reserve).set_unit(netuid) + ) + self.tao_reserve = ( + tao_reserve + if isinstance(tao_reserve, Balance) + else Balance.from_rao(tao_reserve).set_unit(0) + ) + self.k = k + if is_dynamic: + if self.alpha_reserve.tao > 0: + self.price = Balance.from_tao( + self.tao_reserve.tao / self.alpha_reserve.tao + ) + else: + self.price = Balance.from_tao(0.0) + else: + self.price = Balance.from_tao(1.0) + + def __str__(self) -> str: + return (f"DynamicPool( alpha_issuance={self.alpha_issuance}, " + f"alpha_outstanding={self.alpha_outstanding}, " + f"alpha_reserve={self.alpha_reserve}, " + f"tao_reserve={self.tao_reserve}, k={self.k}, price={self.price} )") + + def __repr__(self) -> str: + return self.__str__() + + def tao_to_alpha(self, tao: Balance) -> Balance: + if self.price.tao != 0: + return Balance.from_tao(tao.tao / self.price.tao).set_unit(self.netuid) + else: + return Balance.from_tao(0) + + def alpha_to_tao(self, alpha: Balance) -> Balance: + return Balance.from_tao(alpha.tao * self.price.tao) + + def tao_to_alpha_with_slippage(self, tao: Balance) -> Tuple[Balance, Balance]: + """ + Returns an estimate of how much Alpha would a staker receive if they stake their tao + using the current pool state + Args: + tao: Amount of TAO to stake. + Returns: + Tuple of balances where the first part is the amount of Alpha received, and the + second part (slippage) is the difference between the estimated amount and ideal + amount as if there was no slippage + """ + if self.is_dynamic: + new_tao_in = self.tao_reserve + tao + if new_tao_in == 0: + return tao, Balance.from_rao(0) + new_alpha_in = self.k / new_tao_in + + # Amount of alpha given to the staker + alpha_returned = Balance.from_rao( + self.alpha_reserve.rao - new_alpha_in.rao + ).set_unit(self.netuid) + + # Ideal conversion as if there is no slippage, just price + alpha_ideal = self.tao_to_alpha(tao) + + if alpha_ideal.tao > alpha_returned.tao: + slippage = Balance.from_tao( + alpha_ideal.tao - alpha_returned.tao + ).set_unit(self.netuid) + else: + slippage = Balance.from_tao(0) + else: + alpha_returned = tao.set_unit(self.netuid) + slippage = Balance.from_tao(0) + return alpha_returned, slippage + + def alpha_to_tao_with_slippage(self, alpha: Balance) -> Tuple[Balance, Balance]: + """ + Returns an estimate of how much TAO would a staker receive if they unstake their + alpha using the current pool state + Args: + alpha: Amount of Alpha to stake. + Returns: + Tuple of balances where the first part is the amount of TAO received, and the + second part (slippage) is the difference between the estimated amount and ideal + amount as if there was no slippage + """ + if self.is_dynamic: + new_alpha_in = self.alpha_reserve + alpha + new_tao_reserve = self.k / new_alpha_in + # Amount of TAO given to the unstaker + tao_returned = Balance.from_rao(self.tao_reserve - new_tao_reserve) + + # Ideal conversion as if there is no slippage, just price + tao_ideal = self.alpha_to_tao(alpha) + + if tao_ideal > tao_returned: + slippage = Balance.from_tao(tao_ideal.tao - tao_returned.tao) + else: + slippage = Balance.from_tao(0) + else: + tao_returned = alpha.set_unit(0) + slippage = Balance.from_tao(0) + return tao_returned, slippage + + +@dataclass +class ScheduledColdkeySwapInfo: + """Dataclass for scheduled coldkey swap information.""" + old_coldkey: str + new_coldkey: str + arbitration_block: int + + @classmethod + def fix_decoded_values(cls, decoded: Any) -> "ScheduledColdkeySwapInfo": + """Fixes the decoded values.""" + return cls( + old_coldkey=ss58_encode(decoded["old_coldkey"], SS58_FORMAT), + new_coldkey=ss58_encode(decoded["new_coldkey"], SS58_FORMAT), + arbitration_block=decoded["arbitration_block"], + ) + + @classmethod + def from_vec_u8(cls, vec_u8: list[int]) -> Optional["ScheduledColdkeySwapInfo"]: + """Returns a ScheduledColdkeySwapInfo object from a ``vec_u8``.""" + if len(vec_u8) == 0: + return None + + decoded = from_scale_encoding(vec_u8, ChainDataType.ScheduledColdkeySwapInfo) + if decoded is None: + return None + + return ScheduledColdkeySwapInfo.fix_decoded_values(decoded) + + @classmethod + def list_from_vec_u8(cls, vec_u8: list[int]) -> list["ScheduledColdkeySwapInfo"]: + """Returns a list of ScheduledColdkeySwapInfo objects from a ``vec_u8``.""" + decoded = from_scale_encoding( + vec_u8, ChainDataType.ScheduledColdkeySwapInfo, is_vec=True + ) + if decoded is None: + return [] + + return [ScheduledColdkeySwapInfo.fix_decoded_values(d) for d in decoded] + + @classmethod + def decode_account_id_list(cls, vec_u8: list[int]) -> Optional[list[str]]: + """Decodes a list of AccountIds from vec_u8.""" + decoded = from_scale_encoding( + vec_u8, ChainDataType.ScheduledColdkeySwapInfo.AccountId, is_vec=True + ) + if decoded is None: + return None + return [ + ss58_encode(account_id, SS58_FORMAT) for account_id in decoded + ] + + +@dataclass +class SubnetState: + netuid: int + hotkeys: list[str] + coldkeys: list[str] + active: list[bool] + validator_permit: list[bool] + pruning_score: list[float] + last_update: list[int] + emission: list[Balance] + dividends: list[float] + incentives: list[float] + consensus: list[float] + trust: list[float] + rank: list[float] + block_at_registration: list[int] + local_stake: list[Balance] + global_stake: list[Balance] + stake_weight: list[float] + emission_history: list[list[int]] + + @classmethod + def from_vec_u8(cls, vec_u8: list[int]) -> Optional["SubnetState"]: + if len(vec_u8) == 0: return None + decoded = from_scale_encoding(vec_u8, ChainDataType.SubnetState, is_option=True) + if decoded is None: return None + return SubnetState.fix_decoded_values(decoded) + + @classmethod + def list_from_vec_u8(cls, vec_u8: list[int]) -> list["SubnetState"]: + decoded = from_scale_encoding( vec_u8, ChainDataType.SubnetState, is_vec=True, is_option=True ) + if decoded is None:return [] + decoded = [SubnetState.fix_decoded_values(d) for d in decoded] + return decoded + + @classmethod + def fix_decoded_values(cls, decoded: dict) -> "SubnetState": + netuid = decoded["netuid"] + return SubnetState( + netuid = netuid, + hotkeys = [ss58_encode(val, SS58_FORMAT) for val in decoded["hotkeys"]], + coldkeys = [ss58_encode(val, SS58_FORMAT) for val in decoded["coldkeys"]], + active = decoded["active"], + validator_permit = decoded["validator_permit"], + pruning_score = [u16_normalized_float(val) for val in decoded["pruning_score"]], + last_update = decoded["last_update"], + emission = [Balance.from_rao( val ).set_unit(netuid) for val in decoded["emission"]], + dividends = [u16_normalized_float(val) for val in decoded["dividends"]], + incentives = [u16_normalized_float(val) for val in decoded["incentives"]], + consensus = [u16_normalized_float(val) for val in decoded["consensus"]], + trust = [u16_normalized_float(val) for val in decoded["trust"]], + rank = [u16_normalized_float(val) for val in decoded["rank"]], + block_at_registration = decoded["block_at_registration"], + local_stake = [Balance.from_rao( val ).set_unit(netuid) for val in decoded["local_stake"]], + global_stake = [Balance.from_rao( val ).set_unit(0) for val in decoded["global_stake"]], + stake_weight = [u16_normalized_float(val) for val in decoded["stake_weight"]], + emission_history = decoded["emission_history"] + ) + +class SubstakeElements: + @staticmethod + def decode(result: list[int]) -> list[dict]: + descaled = from_scale_encoding( + input_=result, type_name=ChainDataType.SubstakeElements, is_vec=True + ) + result = [] + for item in descaled: + result.append( + { + "hotkey": ss58_encode(item["hotkey"], SS58_FORMAT), + "coldkey": ss58_encode(item["coldkey"], SS58_FORMAT), + "netuid": item["netuid"], + "stake": Balance.from_rao(item["stake"]), + } + ) + return result + + + custom_rpc_type_registry = { "types": { "SubnetInfo": { From 545759c70ea32b2469151c00bee0e85f0751bca7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 27 Sep 2024 12:46:14 +0200 Subject: [PATCH 004/157] Balance update. --- bittensor_cli/src/bittensor/balances.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bittensor_cli/src/bittensor/balances.py b/bittensor_cli/src/bittensor/balances.py index 1e678c9d..4943edd7 100644 --- a/bittensor_cli/src/bittensor/balances.py +++ b/bittensor_cli/src/bittensor/balances.py @@ -18,6 +18,7 @@ # DEALINGS IN THE SOFTWARE. from typing import Union +from bittensor_cli.src import UNITS class Balance: @@ -279,3 +280,21 @@ def from_rao(amount: int): :return: A Balance object representing the given amount. """ return Balance(amount) + + @staticmethod + def get_unit(netuid: int): + units = UNITS + base = len(units) + if netuid < base: + return units[netuid] + else: + result = "" + while netuid > 0: + result = units[netuid % base] + result + netuid //= base + return result + + def set_unit(self, netuid: int): + self.unit = Balance.get_unit(netuid) + self.rao_unit = Balance.get_unit(netuid) + return self From 58c623bfb89ba5b3f231f9a22a342d5fc69492c7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 27 Sep 2024 12:50:10 +0200 Subject: [PATCH 005/157] Ruff --- bittensor_cli/src/bittensor/chain_data.py | 83 +++++++++++++---------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 61eb20e3..b6bb985c 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -660,12 +660,8 @@ def fix_decoded_values(cls, decoded: dict) -> "DynamicInfo": alpha_out = Balance.from_rao(decoded["alpha_out"]).set_unit(netuid) alpha_in = Balance.from_rao(decoded["alpha_in"]).set_unit(netuid) tao_in = Balance.from_rao(decoded["tao_in"]).set_unit(0) - total_locked = Balance.from_rao(decoded["total_locked"]).set_unit( - netuid - ) - owner_locked = Balance.from_rao(decoded["owner_locked"]).set_unit( - netuid - ) + total_locked = Balance.from_rao(decoded["total_locked"]).set_unit(netuid) + owner_locked = Balance.from_rao(decoded["owner_locked"]).set_unit(netuid) price = ( Balance.from_tao(tao_in.tao / alpha_in.tao) if alpha_in.tao > 0 @@ -766,6 +762,7 @@ def alpha_to_tao_with_slippage(self, alpha: Balance) -> tuple[Balance, Balance]: @dataclass class DynamicPoolInfoV2: """Dataclass for dynamic pool info.""" + netuid: int alpha_issuance: int alpha_outstanding: int @@ -836,10 +833,12 @@ def __init__( self.price = Balance.from_tao(1.0) def __str__(self) -> str: - return (f"DynamicPool( alpha_issuance={self.alpha_issuance}, " - f"alpha_outstanding={self.alpha_outstanding}, " - f"alpha_reserve={self.alpha_reserve}, " - f"tao_reserve={self.tao_reserve}, k={self.k}, price={self.price} )") + return ( + f"DynamicPool( alpha_issuance={self.alpha_issuance}, " + f"alpha_outstanding={self.alpha_outstanding}, " + f"alpha_reserve={self.alpha_reserve}, " + f"tao_reserve={self.tao_reserve}, k={self.k}, price={self.price} )" + ) def __repr__(self) -> str: return self.__str__() @@ -922,6 +921,7 @@ def alpha_to_tao_with_slippage(self, alpha: Balance) -> Tuple[Balance, Balance]: @dataclass class ScheduledColdkeySwapInfo: """Dataclass for scheduled coldkey swap information.""" + old_coldkey: str new_coldkey: str arbitration_block: int @@ -966,9 +966,7 @@ def decode_account_id_list(cls, vec_u8: list[int]) -> Optional[list[str]]: ) if decoded is None: return None - return [ - ss58_encode(account_id, SS58_FORMAT) for account_id in decoded - ] + return [ss58_encode(account_id, SS58_FORMAT) for account_id in decoded] @dataclass @@ -994,15 +992,20 @@ class SubnetState: @classmethod def from_vec_u8(cls, vec_u8: list[int]) -> Optional["SubnetState"]: - if len(vec_u8) == 0: return None + if len(vec_u8) == 0: + return None decoded = from_scale_encoding(vec_u8, ChainDataType.SubnetState, is_option=True) - if decoded is None: return None + if decoded is None: + return None return SubnetState.fix_decoded_values(decoded) @classmethod def list_from_vec_u8(cls, vec_u8: list[int]) -> list["SubnetState"]: - decoded = from_scale_encoding( vec_u8, ChainDataType.SubnetState, is_vec=True, is_option=True ) - if decoded is None:return [] + decoded = from_scale_encoding( + vec_u8, ChainDataType.SubnetState, is_vec=True, is_option=True + ) + if decoded is None: + return [] decoded = [SubnetState.fix_decoded_values(d) for d in decoded] return decoded @@ -1010,26 +1013,35 @@ def list_from_vec_u8(cls, vec_u8: list[int]) -> list["SubnetState"]: def fix_decoded_values(cls, decoded: dict) -> "SubnetState": netuid = decoded["netuid"] return SubnetState( - netuid = netuid, - hotkeys = [ss58_encode(val, SS58_FORMAT) for val in decoded["hotkeys"]], - coldkeys = [ss58_encode(val, SS58_FORMAT) for val in decoded["coldkeys"]], - active = decoded["active"], - validator_permit = decoded["validator_permit"], - pruning_score = [u16_normalized_float(val) for val in decoded["pruning_score"]], - last_update = decoded["last_update"], - emission = [Balance.from_rao( val ).set_unit(netuid) for val in decoded["emission"]], - dividends = [u16_normalized_float(val) for val in decoded["dividends"]], - incentives = [u16_normalized_float(val) for val in decoded["incentives"]], - consensus = [u16_normalized_float(val) for val in decoded["consensus"]], - trust = [u16_normalized_float(val) for val in decoded["trust"]], - rank = [u16_normalized_float(val) for val in decoded["rank"]], - block_at_registration = decoded["block_at_registration"], - local_stake = [Balance.from_rao( val ).set_unit(netuid) for val in decoded["local_stake"]], - global_stake = [Balance.from_rao( val ).set_unit(0) for val in decoded["global_stake"]], - stake_weight = [u16_normalized_float(val) for val in decoded["stake_weight"]], - emission_history = decoded["emission_history"] + netuid=netuid, + hotkeys=[ss58_encode(val, SS58_FORMAT) for val in decoded["hotkeys"]], + coldkeys=[ss58_encode(val, SS58_FORMAT) for val in decoded["coldkeys"]], + active=decoded["active"], + validator_permit=decoded["validator_permit"], + pruning_score=[ + u16_normalized_float(val) for val in decoded["pruning_score"] + ], + last_update=decoded["last_update"], + emission=[ + Balance.from_rao(val).set_unit(netuid) for val in decoded["emission"] + ], + dividends=[u16_normalized_float(val) for val in decoded["dividends"]], + incentives=[u16_normalized_float(val) for val in decoded["incentives"]], + consensus=[u16_normalized_float(val) for val in decoded["consensus"]], + trust=[u16_normalized_float(val) for val in decoded["trust"]], + rank=[u16_normalized_float(val) for val in decoded["rank"]], + block_at_registration=decoded["block_at_registration"], + local_stake=[ + Balance.from_rao(val).set_unit(netuid) for val in decoded["local_stake"] + ], + global_stake=[ + Balance.from_rao(val).set_unit(0) for val in decoded["global_stake"] + ], + stake_weight=[u16_normalized_float(val) for val in decoded["stake_weight"]], + emission_history=decoded["emission_history"], ) + class SubstakeElements: @staticmethod def decode(result: list[int]) -> list[dict]: @@ -1049,7 +1061,6 @@ def decode(result: list[int]) -> list[dict]: return result - custom_rpc_type_registry = { "types": { "SubnetInfo": { From f61a165ba2db7383753468d1d69c3afb0a611956 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 27 Sep 2024 17:37:08 +0200 Subject: [PATCH 006/157] Names --- bittensor_cli/src/bittensor/chain_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index b6bb985c..2cd4e580 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional +from typing import Optional, Any, Union import bt_decode import netaddr @@ -852,7 +852,7 @@ def tao_to_alpha(self, tao: Balance) -> Balance: def alpha_to_tao(self, alpha: Balance) -> Balance: return Balance.from_tao(alpha.tao * self.price.tao) - def tao_to_alpha_with_slippage(self, tao: Balance) -> Tuple[Balance, Balance]: + def tao_to_alpha_with_slippage(self, tao: Balance) -> tuple[Balance, Balance]: """ Returns an estimate of how much Alpha would a staker receive if they stake their tao using the current pool state @@ -888,7 +888,7 @@ def tao_to_alpha_with_slippage(self, tao: Balance) -> Tuple[Balance, Balance]: slippage = Balance.from_tao(0) return alpha_returned, slippage - def alpha_to_tao_with_slippage(self, alpha: Balance) -> Tuple[Balance, Balance]: + def alpha_to_tao_with_slippage(self, alpha: Balance) -> tuple[Balance, Balance]: """ Returns an estimate of how much TAO would a staker receive if they unstake their alpha using the current pool state From 330c382c2f53b6ece5a879318bfea93c30ecc014 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 4 Oct 2024 13:48:51 +0200 Subject: [PATCH 007/157] dumpster fire --- bittensor_cli/src/bittensor/chain_data.py | 70 +++++++++ bittensor_cli/src/commands/stake/__init__.py | 146 +++++++++++++++++++ 2 files changed, 216 insertions(+) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 2cd4e580..c839fe43 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -515,6 +515,76 @@ def delegated_list_from_vec_u8( results.append((delegate, Balance.from_rao(b))) return results +@dataclass +class DelegateInfoLite: + """ + Dataclass for light delegate information. + + Args: + hotkey_ss58 (str): Hotkey of the delegate for which the information is being fetched. + owner_ss58 (str): Coldkey of the owner. + total_stake (int): Total stake of the delegate. + owner_stake (int): Own stake of the delegate. + take (float): Take of the delegate as a percentage. None if custom + """ + + hotkey_ss58: str # Hotkey of delegate + owner_ss58: str # Coldkey of owner + take: Optional[float] + total_stake: Balance # Total stake of the delegate + previous_total_stake: Optional[Balance] # Total stake of the delegate + owner_stake: Balance # Own stake of the delegate + + @classmethod + def fix_decoded_values(cls, decoded: Any) -> "DelegateInfoLite": + """Fixes the decoded values.""" + decoded_take = decoded["take"] + + if decoded_take == 65535: + fixed_take = None + else: + fixed_take = u16_normalized_float(decoded_take) + + return cls( + hotkey_ss58=ss58_encode( + decoded["delegate_ss58"], SS58_FORMAT + ), + owner_ss58=ss58_encode(decoded["owner_ss58"], SS58_FORMAT), + take=fixed_take, + total_stake=Balance.from_rao(decoded["total_stake"]), + owner_stake=Balance.from_rao(decoded["owner_stake"]), + previous_total_stake=None, + ) + + @classmethod + def from_vec_u8(cls, vec_u8: List[int]) -> Optional["DelegateInfoLite"]: + """Returns a DelegateInfoLight object from a ``vec_u8``.""" + if len(vec_u8) == 0: + return None + + decoded = from_scale_encoding(vec_u8, ChainDataType.DelegateInfoLight) + + if decoded is None: + return None + + decoded = DelegateInfoLite.fix_decoded_values(decoded) + + return decoded + + @classmethod + def list_from_vec_u8(cls, vec_u8: list[int]) -> list["DelegateInfoLite"]: + """Returns a list of DelegateInfoLight objects from a ``vec_u8``.""" + decoded = from_scale_encoding( + vec_u8, ChainDataType.DelegateInfoLight, is_vec=True + ) + + if decoded is None: + return [] + + decoded = [DelegateInfoLite.fix_decoded_values(d) for d in decoded] + + return decoded + @dataclass class SubnetInfo: diff --git a/bittensor_cli/src/commands/stake/__init__.py b/bittensor_cli/src/commands/stake/__init__.py index e69de29b..c0c650e8 100644 --- a/bittensor_cli/src/commands/stake/__init__.py +++ b/bittensor_cli/src/commands/stake/__init__.py @@ -0,0 +1,146 @@ + +from typing import Optional, TYPE_CHECKING + +import rich.prompt +from rich.table import Table + +from bittensor_cli.src import DelegatesDetails +from bittensor_cli.src.bittensor.chain_data import DelegateInfo, DelegateInfoLite +from bittensor_cli.src.bittensor.utils import console, err_console + +if TYPE_CHECKING: + from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface + + +async def select_delegate(subtensor: "SubtensorInterface", netuid: int): + # Get a list of delegates and sort them by total stake in descending order + delegates: list[DelegateInfoLite] = ( + subtensor.get_delegates_by_netuid_light(netuid) + ) + delegates.sort(key=lambda x: x.total_stake, reverse=True) + + # Get registered delegates details. + registered_delegate_info: Optional[DelegatesDetails] = await subtensor.get_delegate_identities() + + # Create a table to display delegate information + table = Table( + show_header=True, + header_style="bold", + border_style="rgb(7,54,66)", + style="rgb(0,43,54)", + ) + + # Add columns to the table with specific styles + table.add_column("Index", style="rgb(253,246,227)", no_wrap=True) + table.add_column("Delegate Name", no_wrap=True) + table.add_column("Hotkey SS58", style="rgb(211,54,130)", no_wrap=True) + table.add_column("Owner SS58", style="rgb(133,153,0)", no_wrap=True) + table.add_column("Take", style="rgb(181,137,0)", no_wrap=True) + table.add_column( + "Total Stake", style="rgb(38,139,210)", no_wrap=True, justify="right" + ) + table.add_column( + "Owner Stake", style="rgb(220,50,47)", no_wrap=True, justify="right" + ) + # table.add_column("Return per 1000", style="rgb(108,113,196)", no_wrap=True, justify="right") + # table.add_column("Total Daily Return", style="rgb(42,161,152)", no_wrap=True, justify="right") + + # List to store visible delegates + visible_delegates = [] + + # TODO: Add pagination to handle large number of delegates more efficiently + # Iterate through delegates and display their information + idx = 0 + selected_idx = None + while True: + if idx < len(delegates): + delegate = delegates[idx] + + # Add delegate to visible list + visible_delegates.append(delegate) + + # Add a row to the table with delegate information + table.add_row( + str(idx), + registered_delegate_info[delegate.hotkey_ss58].name + if delegate.hotkey_ss58 in registered_delegate_info + else "", + delegate.hotkey_ss58[:5] + + "..." + + delegate.hotkey_ss58[-5:], # Show truncated hotkey + delegate.owner_ss58[:5] + + "..." + + delegate.owner_ss58[-5:], # Show truncated owner address + f"{delegate.take:.6f}", + f"τ{delegate.total_stake.tao:,.4f}", + f"τ{delegate.owner_stake.tao:,.4f}", + # f"τ{delegate.return_per_1000.tao:,.4f}", + # f"τ{delegate.total_daily_return.tao:,.4f}", + ) + + # Clear console and print updated table + console.clear() + console.print(table) + + # Prompt user for input + user_input: str = rich.prompt.Prompt.ask( + 'Press Enter to scroll, enter a number (1-N) to select, or type "h" for help: ', + choices=["h"] + [str(x) for x in range(1, len(delegates) - 1)], + show_choices=True + ) + + # Add a help option to display information about each column + if user_input == "h": + console.print("\nColumn Information:") + console.print( + "[rgb(253,246,227)]Index:[/rgb(253,246,227)] Position in the list of delegates" + ) + console.print( + "[rgb(211,54,130)]Hotkey SS58:[/rgb(211,54,130)] Truncated public key of the delegate's hotkey" + ) + console.print( + "[rgb(133,153,0)]Owner SS58:[/rgb(133,153,0)] Truncated public key of the delegate's owner" + ) + console.print( + "[rgb(181,137,0)]Take:[/rgb(181,137,0)] Percentage of rewards the delegate takes" + ) + console.print( + "[rgb(38,139,210)]Total Stake:[/rgb(38,139,210)] Total amount staked to this delegate" + ) + console.print( + "[rgb(220,50,47)]Owner Stake:[/rgb(220,50,47)] Amount staked by the delegate owner" + ) + console.print( + "[rgb(108,113,196)]Return per 1000:[/rgb(108,113,196)] Estimated return for 1000 Tao staked" + ) + console.print( + "[rgb(42,161,152)]Total Daily Return:[/rgb(42,161,152)] Estimated total daily return for all stake" + ) + console.print("\nPress Enter to continue...") + input() + continue + + # If user presses Enter, continue to next delegate + elif user_input: + selected_idx = int(user_input) + break + + if idx < len(delegates): + idx += 1 + + # TODO( const ): uncomment for check + # Add a confirmation step before returning the selected delegate + # console.print(f"\nSelected delegate: [rgb(211,54,130)]{visible_delegates[selected_idx].hotkey_ss58}[/rgb(211,54,130)]") + # console.print(f"Take: [rgb(181,137,0)]{visible_delegates[selected_idx].take:.6f}[/rgb(181,137,0)]") + # console.print(f"Total Stake: [rgb(38,139,210)]{visible_delegates[selected_idx].total_stake}[/rgb(38,139,210)]") + + # confirmation = Prompt.ask("Do you want to proceed with this delegate? (y/n)") + # if confirmation.lower() != 'yes' and confirmation.lower() != 'y': + # return select_delegate( subtensor, netuid ) + + # Return the selected delegate + + if selected_idx is None: + raise IndexError("You must make a selection.") + else: + return delegates[selected_idx] From 881722df678684fef0f8a9295f5bc8a64bd03e5d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 4 Oct 2024 14:10:23 +0200 Subject: [PATCH 008/157] Slightly better --- bittensor_cli/src/bittensor/chain_data.py | 5 +- .../src/bittensor/subtensor_interface.py | 31 +++ bittensor_cli/src/commands/stake/__init__.py | 179 +++++++++--------- 3 files changed, 127 insertions(+), 88 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index c839fe43..61e2718b 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -515,6 +515,7 @@ def delegated_list_from_vec_u8( results.append((delegate, Balance.from_rao(b))) return results + @dataclass class DelegateInfoLite: """ @@ -546,9 +547,7 @@ def fix_decoded_values(cls, decoded: Any) -> "DelegateInfoLite": fixed_take = u16_normalized_float(decoded_take) return cls( - hotkey_ss58=ss58_encode( - decoded["delegate_ss58"], SS58_FORMAT - ), + hotkey_ss58=ss58_encode(decoded["delegate_ss58"], SS58_FORMAT), owner_ss58=ss58_encode(decoded["owner_ss58"], SS58_FORMAT), take=fixed_take, total_stake=Balance.from_rao(decoded["total_stake"]), diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 62063c20..d4ada27d 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -24,6 +24,7 @@ NeuronInfo, SubnetHyperparameters, decode_account_id, + DelegateInfoLite, ) from bittensor_cli.src import DelegatesDetails from bittensor_cli.src.bittensor.balances import Balance @@ -1087,3 +1088,33 @@ async def get_delegate_identities( ) return all_delegates_details + + async def get_delegates_by_netuid_light( + self, netuid: int, block_hash: Optional[str] = None + ) -> list[DelegateInfoLite]: + """ + Retrieves a list of all delegate neurons within the Bittensor network. This function provides an overview of the neurons that are actively involved in the network's delegation system. + + Analyzing the delegate population offers insights into the network's governance dynamics and the distribution of trust and responsibility among participating neurons. + + Args: + netuid: the netuid to query + block_hash: The hash of the blockchain block number for the query. + + Returns: + A list of DelegateInfo objects detailing each delegate's characteristics. + + """ + + params = [netuid] if not block_hash else [netuid, block_hash] + json_body = await self.substrate.rpc_request( + method="delegateInfo_getDelegatesLight", # custom rpc method + params=params, + ) + + result = json_body["result"] + + if result in (None, []): + return [] + + return DelegateInfoLite.list_from_vec_u8(result) # TODO this won't work yet diff --git a/bittensor_cli/src/commands/stake/__init__.py b/bittensor_cli/src/commands/stake/__init__.py index c0c650e8..8e28c089 100644 --- a/bittensor_cli/src/commands/stake/__init__.py +++ b/bittensor_cli/src/commands/stake/__init__.py @@ -1,4 +1,3 @@ - from typing import Optional, TYPE_CHECKING import rich.prompt @@ -15,12 +14,11 @@ async def select_delegate(subtensor: "SubtensorInterface", netuid: int): # Get a list of delegates and sort them by total stake in descending order delegates: list[DelegateInfoLite] = ( - subtensor.get_delegates_by_netuid_light(netuid) - ) - delegates.sort(key=lambda x: x.total_stake, reverse=True) + await subtensor.get_delegates_by_netuid_light(netuid) + ).sort(key=lambda x: x.total_stake, reverse=True) # Get registered delegates details. - registered_delegate_info: Optional[DelegatesDetails] = await subtensor.get_delegate_identities() + registered_delegate_info = await subtensor.get_delegate_identities() # Create a table to display delegate information table = Table( @@ -48,85 +46,90 @@ async def select_delegate(subtensor: "SubtensorInterface", netuid: int): # List to store visible delegates visible_delegates = [] - # TODO: Add pagination to handle large number of delegates more efficiently - # Iterate through delegates and display their information - idx = 0 - selected_idx = None - while True: - if idx < len(delegates): - delegate = delegates[idx] - - # Add delegate to visible list - visible_delegates.append(delegate) - - # Add a row to the table with delegate information - table.add_row( - str(idx), - registered_delegate_info[delegate.hotkey_ss58].name - if delegate.hotkey_ss58 in registered_delegate_info - else "", - delegate.hotkey_ss58[:5] - + "..." - + delegate.hotkey_ss58[-5:], # Show truncated hotkey - delegate.owner_ss58[:5] - + "..." - + delegate.owner_ss58[-5:], # Show truncated owner address - f"{delegate.take:.6f}", - f"τ{delegate.total_stake.tao:,.4f}", - f"τ{delegate.owner_stake.tao:,.4f}", - # f"τ{delegate.return_per_1000.tao:,.4f}", - # f"τ{delegate.total_daily_return.tao:,.4f}", - ) - - # Clear console and print updated table - console.clear() - console.print(table) - - # Prompt user for input - user_input: str = rich.prompt.Prompt.ask( + def get_user_input(): + return rich.prompt.Prompt.ask( 'Press Enter to scroll, enter a number (1-N) to select, or type "h" for help: ', choices=["h"] + [str(x) for x in range(1, len(delegates) - 1)], - show_choices=True + show_choices=True, ) - # Add a help option to display information about each column - if user_input == "h": - console.print("\nColumn Information:") - console.print( - "[rgb(253,246,227)]Index:[/rgb(253,246,227)] Position in the list of delegates" - ) - console.print( - "[rgb(211,54,130)]Hotkey SS58:[/rgb(211,54,130)] Truncated public key of the delegate's hotkey" - ) - console.print( - "[rgb(133,153,0)]Owner SS58:[/rgb(133,153,0)] Truncated public key of the delegate's owner" - ) - console.print( - "[rgb(181,137,0)]Take:[/rgb(181,137,0)] Percentage of rewards the delegate takes" - ) - console.print( - "[rgb(38,139,210)]Total Stake:[/rgb(38,139,210)] Total amount staked to this delegate" - ) - console.print( - "[rgb(220,50,47)]Owner Stake:[/rgb(220,50,47)] Amount staked by the delegate owner" - ) - console.print( - "[rgb(108,113,196)]Return per 1000:[/rgb(108,113,196)] Estimated return for 1000 Tao staked" - ) - console.print( - "[rgb(42,161,152)]Total Daily Return:[/rgb(42,161,152)] Estimated total daily return for all stake" - ) - console.print("\nPress Enter to continue...") - input() - continue - - # If user presses Enter, continue to next delegate - elif user_input: - selected_idx = int(user_input) - break - - if idx < len(delegates): - idx += 1 + # TODO: Add pagination to handle large number of delegates more efficiently + # Iterate through delegates and display their information + + def loop_selections(): + idx = 0 + selected_idx = None + while True: + if idx < len(delegates): + delegate = delegates[idx] + + # Add delegate to visible list + visible_delegates.append(delegate) + + # Add a row to the table with delegate information + table.add_row( + str(idx), + registered_delegate_info[delegate.hotkey_ss58].name + if delegate.hotkey_ss58 in registered_delegate_info + else "", + delegate.hotkey_ss58[:5] + + "..." + + delegate.hotkey_ss58[-5:], # Show truncated hotkey + delegate.owner_ss58[:5] + + "..." + + delegate.owner_ss58[-5:], # Show truncated owner address + f"{delegate.take:.6f}", + f"τ{delegate.total_stake.tao:,.4f}", + f"τ{delegate.owner_stake.tao:,.4f}", + # f"τ{delegate.return_per_1000.tao:,.4f}", + # f"τ{delegate.total_daily_return.tao:,.4f}", + ) + + # Clear console and print updated table + console.clear() + console.print(table) + + # Prompt user for input + user_input: str = get_user_input() + + # Add a help option to display information about each column + if user_input == "h": + console.print("\nColumn Information:") + console.print( + "[rgb(253,246,227)]Index:[/rgb(253,246,227)] Position in the list of delegates" + ) + console.print( + "[rgb(211,54,130)]Hotkey SS58:[/rgb(211,54,130)] Truncated public key of the delegate's hotkey" + ) + console.print( + "[rgb(133,153,0)]Owner SS58:[/rgb(133,153,0)] Truncated public key of the delegate's owner" + ) + console.print( + "[rgb(181,137,0)]Take:[/rgb(181,137,0)] Percentage of rewards the delegate takes" + ) + console.print( + "[rgb(38,139,210)]Total Stake:[/rgb(38,139,210)] Total amount staked to this delegate" + ) + console.print( + "[rgb(220,50,47)]Owner Stake:[/rgb(220,50,47)] Amount staked by the delegate owner" + ) + console.print( + "[rgb(108,113,196)]Return per 1000:[/rgb(108,113,196)] Estimated return for 1000 Tao staked" + ) + console.print( + "[rgb(42,161,152)]Total Daily Return:[/rgb(42,161,152)] Estimated total daily return for all stake" + ) + user_input = get_user_input() + + # If user presses Enter, continue to next delegate + if user_input and user_input != "h": + selected_idx = int(user_input) + break + + if idx < len(delegates): + idx += 1 + + return selected_idx # TODO( const ): uncomment for check # Add a confirmation step before returning the selected delegate @@ -139,8 +142,14 @@ async def select_delegate(subtensor: "SubtensorInterface", netuid: int): # return select_delegate( subtensor, netuid ) # Return the selected delegate - - if selected_idx is None: - raise IndexError("You must make a selection.") - else: - return delegates[selected_idx] + while True: + selected_idx_ = loop_selections() + if selected_idx_ is None: + if not rich.prompt.Confirm.ask( + "You must make a selection. Loop through again?" + ): + raise IndexError + else: + continue + else: + return delegates[selected_idx_] From c72ab2120cc5e66a6fd5640a315fff78510a67c7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 4 Oct 2024 15:41:32 +0200 Subject: [PATCH 009/157] Renamed DelegateInfoLight to DelegateInfoLite, as Samuel states this is correct. --- bittensor_cli/src/bittensor/chain_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 61e2718b..713d8da0 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -557,11 +557,11 @@ def fix_decoded_values(cls, decoded: Any) -> "DelegateInfoLite": @classmethod def from_vec_u8(cls, vec_u8: List[int]) -> Optional["DelegateInfoLite"]: - """Returns a DelegateInfoLight object from a ``vec_u8``.""" + """Returns a DelegateInfoLite object from a ``vec_u8``.""" if len(vec_u8) == 0: return None - decoded = from_scale_encoding(vec_u8, ChainDataType.DelegateInfoLight) + decoded = from_scale_encoding(vec_u8, ChainDataType.DelegateInfoLite) if decoded is None: return None @@ -572,9 +572,9 @@ def from_vec_u8(cls, vec_u8: List[int]) -> Optional["DelegateInfoLite"]: @classmethod def list_from_vec_u8(cls, vec_u8: list[int]) -> list["DelegateInfoLite"]: - """Returns a list of DelegateInfoLight objects from a ``vec_u8``.""" + """Returns a list of DelegateInfoLite objects from a ``vec_u8``.""" decoded = from_scale_encoding( - vec_u8, ChainDataType.DelegateInfoLight, is_vec=True + vec_u8, ChainDataType.DelegateInfoLite, is_vec=True ) if decoded is None: @@ -1197,7 +1197,7 @@ def decode(result: list[int]) -> list[dict]: ["total_daily_return", "Compact"], ], }, - "DelegateInfoLight": { + "DelegateInfoLite": { "type": "struct", "type_mapping": [ ["delegate_ss58", "AccountId"], From d421ba92a5ce36e23217bcf3fd229d6949faab06 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 4 Oct 2024 16:13:22 +0200 Subject: [PATCH 010/157] Stake Add ported mostly --- bittensor_cli/src/bittensor/chain_data.py | 2 +- bittensor_cli/src/commands/stake/__init__.py | 10 +- bittensor_cli/src/commands/stake/stake.py | 149 ++++++++++++++++++- 3 files changed, 154 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 713d8da0..cd75389b 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -556,7 +556,7 @@ def fix_decoded_values(cls, decoded: Any) -> "DelegateInfoLite": ) @classmethod - def from_vec_u8(cls, vec_u8: List[int]) -> Optional["DelegateInfoLite"]: + def from_vec_u8(cls, vec_u8: list[int]) -> Optional["DelegateInfoLite"]: """Returns a DelegateInfoLite object from a ``vec_u8``.""" if len(vec_u8) == 0: return None diff --git a/bittensor_cli/src/commands/stake/__init__.py b/bittensor_cli/src/commands/stake/__init__.py index 8e28c089..6b0b0fc9 100644 --- a/bittensor_cli/src/commands/stake/__init__.py +++ b/bittensor_cli/src/commands/stake/__init__.py @@ -46,20 +46,20 @@ async def select_delegate(subtensor: "SubtensorInterface", netuid: int): # List to store visible delegates visible_delegates = [] - def get_user_input(): + def get_user_input() -> str: return rich.prompt.Prompt.ask( 'Press Enter to scroll, enter a number (1-N) to select, or type "h" for help: ', - choices=["h"] + [str(x) for x in range(1, len(delegates) - 1)], + choices=["", "h"] + [str(x) for x in range(1, len(delegates) - 1)], show_choices=True, ) # TODO: Add pagination to handle large number of delegates more efficiently # Iterate through delegates and display their information - def loop_selections(): + def loop_selections() -> Optional[int]: idx = 0 selected_idx = None - while True: + while idx < len(delegates): if idx < len(delegates): delegate = delegates[idx] @@ -146,7 +146,7 @@ def loop_selections(): selected_idx_ = loop_selections() if selected_idx_ is None: if not rich.prompt.Confirm.ask( - "You must make a selection. Loop through again?" + "You've reached the end of the list. You must make a selection. Loop through again?" ): raise IndexError else: diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 819f02cc..e59eed6a 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -8,7 +8,7 @@ from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError -from rich.prompt import Confirm +from rich.prompt import Confirm, FloatPrompt from rich.table import Table, Column import typer @@ -1147,6 +1147,153 @@ async def get_all_wallet_accounts( ) +async def stake_add_new( + wallet: Wallet, + subtensor: "SubtensorInterface", + netuid: Optional[int], + stake_all: bool, + amount: float, + staking_address_ss58: str, + delegate: bool, + prompt: bool, +): + """ + + Args: + wallet: wallet object + subtensor: SubtensorInterface object + netuid: the netuid to stake to (None indicates all subnets) + stake_all: whether to stake all available balance + amount: specified amount of balance to stake + staking_address_ss58: the hotkey to use for the stake + delegate: whether to delegate stake + prompt: whether to prompt the user + + Returns: + + """ + netuids = ( + [netuid] if netuid is not None else await subtensor.get_all_subnet_netuids() + ) + # Init the table. + table = Table( + title=f"[white]Staking operation from Coldkey SS58[/white]: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", + width=console.width - 5, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + + # Determine the amount we are staking. + rows = [] + stake_amount_balance = [] + current_stake_balances = [] + current_wallet_balance_ = await subtensor.get_balance( + wallet.coldkeypub.ss58_address + ) + current_wallet_balance = current_wallet_balance_[ + wallet.coldkeypub.ss58_address + ].set_unit(0) + remaining_wallet_balance = current_wallet_balance + max_slippage = 0 + + for netuid in netuids: + # Check that the subnet exists. + # TODO gather this + dynamic_info = subtensor.get_subnet_dynamic_info(netuid) # TODO add + if not dynamic_info: + err_console.print(f"Subnet with netuid: {netuid} does not exist.") + continue + + # Get old staking balance. + # TODO gather this + current_stake_balance: Balance = ( + subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( # TODO add/await + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=staking_address_ss58, + netuid=netuid, + ).set_unit(netuid) + ) + current_stake_balances.append(current_stake_balance) + + # Get the amount. + amount_to_stake_as_balance = None + if amount: + amount_to_stake_as_balance = Balance.from_tao(amount) + elif stake_all: + amount_to_stake_as_balance = current_wallet_balance / len(netuids) + elif ( + not amount and not max_stake + ): # TODO should this be an Option? Asked in Cortex channel https://discord.com/channels/799672011265015819/1176889593136693339/1291756735316365456 + if Confirm.ask(f"Stake all: [bold]{remaining_wallet_balance}[/bold]?"): + amount_to_stake_as_balance = remaining_wallet_balance + else: + try: + amount = FloatPrompt.ask( + f"Enter amount to stake in {Balance.get_unit(0)} to subnet: {netuid}" + ) + amount_to_stake_as_balance = Balance.from_tao(amount) + except ValueError: + err_console.print( + f":cross_mark:[red]Invalid amount: {amount}[/red]" + ) + return False + stake_amount_balance.append(amount_to_stake_as_balance) + + # Check enough to stake. + amount_to_stake_as_balance.set_unit(0) + if amount_to_stake_as_balance > remaining_wallet_balance: + err_console.print( + f"[red]Not enough stake[/red]:[bold white]\n wallet balance:{remaining_wallet_balance} < " + f"staking amount: {amount_to_stake_as_balance}[/bold white]" + ) + return False + remaining_wallet_balance -= amount_to_stake_as_balance + + # Slippage warning + received_amount, slippage = dynamic_info.tao_to_alpha_with_slippage( + amount_to_stake_as_balance + ) + if dynamic_info.is_dynamic: + slippage_pct_float = ( + 100 * float(slippage) / float(slippage + received_amount) + if slippage + received_amount != 0 + else 0 + ) + slippage_pct = f"{slippage_pct_float:.4f} %" + else: + slippage_pct_float = 0 + slippage_pct = "N/A" + max_slippage = max(slippage_pct_float, max_slippage) + rows.append( + ( + str(netuid), + # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", + f"{staking_address_ss58}", + str(amount_to_stake_as_balance), + str(1 / float(dynamic_info.price)) + + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", + str(received_amount.set_unit(netuid)), + str(slippage_pct), + ) + ) + + async def stake_add( wallet: Wallet, subtensor: "SubtensorInterface", From 722836d6d179931c25c1eac89f62bf3aeded9ff1 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 4 Oct 2024 17:28:04 +0200 Subject: [PATCH 011/157] Stake Add additions --- bittensor_cli/src/commands/stake/stake.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index e59eed6a..0d26c7ea 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1177,7 +1177,8 @@ async def stake_add_new( ) # Init the table. table = Table( - title=f"[white]Staking operation from Coldkey SS58[/white]: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", + title="[white]Staking operation from Coldkey SS58[/white]: " + f"[bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", width=console.width - 5, safe_box=True, padding=(0, 1), From 5930f64b1fea852544d2c100eab845ba785e2258 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 7 Oct 2024 16:43:53 +0200 Subject: [PATCH 012/157] New set children --- .../src/commands/stake/children_hotkeys.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index 07472240..c47c71d1 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -270,6 +270,7 @@ def prepare_child_proportions(children_with_proportions): async def get_children( wallet: Wallet, subtensor: "SubtensorInterface", netuid: Optional[int] = None ): + # TODO rao asks separately for the hotkey from the user, should we do this, or the way we do it now? """ Retrieves the child hotkeys for the specified wallet. @@ -486,6 +487,79 @@ async def _render_table( return children +async def set_children_new( + wallet: Wallet, + subtensor: "SubtensorInterface", + children: list[str], + proportions: list[float], + hotkey: str, + netuid: int, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + prompt: bool = True +): + """Set children hotkeys.""" + # Validate children SS58 addresses + for child in children: + if not is_valid_ss58_address(child): + err_console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") + return + if child == wallet.hotkey.ss58_address: + err_console.print(":cross_mark:[red] Cannot set yourself as a child.[/red]") + return + + total_proposed = sum(proportions) + if total_proposed > 1: + raise ValueError( + f"Invalid proportion: The sum of all proportions cannot be greater than 1. " + f"Proposed sum of proportions is {total_proposed}." + ) + children_with_proportions = list(zip(proportions, children)) + if netuid: + success, message = await set_children_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey=hotkey, + children_with_proportions=children_with_proportions, + prompt=prompt, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + # Result + if success: + if wait_for_inclusion and wait_for_finalization: + console.print("New Status:") + await get_children(wallet, subtensor, netuid) + console.print( + ":white_heavy_check_mark: [green]Set children hotkeys.[/green]" + ) + else: + console.print( + f":cross_mark:[red] Unable to set children hotkeys.[/red] {message}" + ) + else: + # set children on all subnets that parent is registered on + netuids = await subtensor.get_all_subnet_netuids() + for netuid in netuids: + if netuid == 0: # dont include root network + continue + console.print(f"Setting children on netuid {netuid}.") + await set_children_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey=hotkey, + children_with_proportions=children_with_proportions, + prompt=prompt, + wait_for_inclusion=True, + wait_for_finalization=False, + ) + console.print( + ":white_heavy_check_mark: [green]Sent set children request for all subnets.[/green]" + ) + + async def set_children( wallet: Wallet, subtensor: "SubtensorInterface", @@ -565,6 +639,7 @@ async def revoke_children( wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ): + # TODO seek clarification on use of asking hotkey vs how we do it now """ Revokes the children hotkeys associated with a given network identifier (netuid). """ From 253fc66df399e97a4ed99b668c86c09913c8ff74 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 7 Oct 2024 17:50:24 +0200 Subject: [PATCH 013/157] Stake list initial --- bittensor_cli/src/commands/stake/stake.py | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 0d26c7ea..43ab5621 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -14,6 +14,7 @@ from bittensor_cli.src.bittensor.balances import Balance +from bittensor_cli.src.bittensor.chain_data import StakeInfo from bittensor_cli.src.bittensor.utils import ( console, create_table, @@ -1604,3 +1605,31 @@ async def unstake( wait_for_inclusion=True, prompt=prompt, ) + + +async def stake_list( + wallet: Wallet, + subtensor: "SubtensorInterface" +): + substakes = subtensor.get_stake_info_for_coldkeys( + coldkey_ss58_list=[wallet.coldkeypub.ss58_address] + )[wallet.coldkeypub.ss58_address] + + # Get registered delegates details. + registered_delegate_info = await subtensor.get_delegate_identities() + + # Token pricing info. + dynamic_info = await subtensor.get_all_subnet_dynamic_info() + emission_drain_tempo = int(await subtensor.substrate.query("SubtensorModule", "HotkeyEmissionTempo")) + balance = (await subtensor.get_balance(wallet.coldkeypub.ss58_address))[wallet.coldkeypub.ss58_address] + + # Iterate over substakes and aggregate them by hotkey. + hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} + + for substake in substakes: + hotkey = substake.hotkey_ss58 + if substake.stake.rao == 0: + continue + if hotkey not in hotkeys_to_substakes: + hotkeys_to_substakes[hotkey] = [] + hotkeys_to_substakes[hotkey].append(substake) From 028483070616d97a8ce40c76fea71574ef49d0f0 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 9 Oct 2024 21:55:02 +0200 Subject: [PATCH 014/157] Progress --- .../src/bittensor/subtensor_interface.py | 24 ++++ .../src/commands/stake/children_hotkeys.py | 2 +- bittensor_cli/src/commands/stake/stake.py | 128 ++++++++++++++++-- 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index d4ada27d..fdabba62 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -25,6 +25,7 @@ SubnetHyperparameters, decode_account_id, DelegateInfoLite, + DynamicInfo, ) from bittensor_cli.src import DelegatesDetails from bittensor_cli.src.bittensor.balances import Balance @@ -1118,3 +1119,26 @@ async def get_delegates_by_netuid_light( return [] return DelegateInfoLite.list_from_vec_u8(result) # TODO this won't work yet + + async def get_subnet_dynamic_info(self, netuid: int) -> Optional["DynamicInfo"]: + json = await self.substrate.rpc_request( + method="subnetInfo_getDynamicInfo", params=[netuid, None] + ) + subnets = DynamicInfo.from_vec_u8(json["result"]) + return subnets + + async def get_stake_for_coldkey_and_hotkey_on_netuid( + self, + hotkey_ss58: str, + coldkey_ss58: str, + netuid: int, + block_hash: Optional[str] = None, + ) -> Optional["Balance"]: + """Returns the stake under a coldkey - hotkey - netuid pairing""" + _result = await self.substrate.query( + "SubtensorModule", "Alpha", [hotkey_ss58, coldkey_ss58, netuid], block_hash + ) + if _result is None: + return None + else: + return Balance.from_rao(_result).set_unit(int(netuid)) diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index c47c71d1..c472bcb1 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -496,7 +496,7 @@ async def set_children_new( netuid: int, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - prompt: bool = True + prompt: bool = True, ): """Set children hotkeys.""" # Validate children SS58 addresses diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 43ab5621..ee1cf0cc 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1157,6 +1157,10 @@ async def stake_add_new( staking_address_ss58: str, delegate: bool, prompt: bool, + max_stake: float, + include_hotkeys: list[str], + exclude_hotkeys: list[str], + hotkey_ss58: Optional[str] = None, ): """ @@ -1179,7 +1183,7 @@ async def stake_add_new( # Init the table. table = Table( title="[white]Staking operation from Coldkey SS58[/white]: " - f"[bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", + f"[bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", width=console.width - 5, safe_box=True, padding=(0, 1), @@ -1214,10 +1218,12 @@ async def stake_add_new( remaining_wallet_balance = current_wallet_balance max_slippage = 0 - for netuid in netuids: + all_dynamic_info = await asyncio.gather( + *[subtensor.get_subnet_dynamic_info(x) for x in netuids] + ) + + for netuid, dynamic_info in zip(netuids, all_dynamic_info): # Check that the subnet exists. - # TODO gather this - dynamic_info = subtensor.get_subnet_dynamic_info(netuid) # TODO add if not dynamic_info: err_console.print(f"Subnet with netuid: {netuid} does not exist.") continue @@ -1239,9 +1245,7 @@ async def stake_add_new( amount_to_stake_as_balance = Balance.from_tao(amount) elif stake_all: amount_to_stake_as_balance = current_wallet_balance / len(netuids) - elif ( - not amount and not max_stake - ): # TODO should this be an Option? Asked in Cortex channel https://discord.com/channels/799672011265015819/1176889593136693339/1291756735316365456 + elif not amount and not max_stake: if Confirm.ask(f"Stake all: [bold]{remaining_wallet_balance}[/bold]?"): amount_to_stake_as_balance = remaining_wallet_balance else: @@ -1294,6 +1298,103 @@ async def stake_add_new( str(slippage_pct), ) ) + table.add_column("Netuid", justify="center", style="grey89") + table.add_column("Hotkey", justify="center", style="light_salmon3") + table.add_column( + f"Amount ({Balance.get_unit(0)})", justify="center", style="dark_sea_green" + ) + table.add_column( + f"Rate ({Balance.get_unit(netuid)}/{Balance.get_unit(0)})", + justify="center", + style="light_goldenrod2", + ) + table.add_column( + f"Recieved ({Balance.get_unit(netuid)})", + justify="center", + style="light_slate_blue", + ) + table.add_column("Slippage", justify="center", style="rgb(220,50,47)") + for row in rows: + table.add_row(*row) + console.print(table) + message = "" + if max_slippage > 5: + message += "-------------------------------------------------------------------------------------------------------------------\n" + message += f"[bold][yellow]WARNING:[/yellow]\tThe slippage on one of your operations is high: [bold red]{max_slippage} %[/bold red], this may result in a loss of funds.[/bold] \n" + message += "-------------------------------------------------------------------------------------------------------------------\n" + console.print(message) + console.print( + """ +[bold white]Description[/bold white]: +The table displays information about the stake operation you are about to perform. +The columns are as follows: + - [bold white]Netuid[/bold white]: The netuid of the subnet you are staking to. + - [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are staking to. + - [bold white]Amount[/bold white]: The TAO you are staking into this subnet onto this hotkey. + - [bold white]Rate[/bold white]: The rate of exchange between your TAO and the subnet's stake. + - [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage. + - [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root). +""" + ) + if prompt: + if not Confirm.ask("Would you like to continue?"): + return False + + async def send_extrinsic(netuid_i, amount, current): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="add_stake", + call_params={ + "hotkey": staking_address_ss58, + "netuid": netuid_i, + "amount_staked": amount.rao, + }, + ) + extrinsic = await subtensor.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = await subtensor.substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=True, wait_for_finalization=False + ) + if not prompt: # TODO verbose? + console.print( + f":white_heavy_check_mark: [green]Submitted {amount} to {netuid_i}[/green]" + ) + else: + await response.process_events() + if not await response.is_success: + err_console.print( + f":cross_mark: [red]Failed[/red] with error: {response.error_message}" + ) + else: + new_balance_, new_stake_ = await asyncio.gather( + subtensor.get_balance(wallet.coldkeypub.ss58_address), + subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=staking_address_ss58, + netuid=netuid_i, + ), + ) + new_balance = new_balance_[wallet.coldkeypub.ss58_address] + new_stake = new_stake_.set_unit(netuid_i) + console.print( + f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + console.print( + f"Subnet: {netuid_i} Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + + # Perform staking operation. + wallet.unlock_coldkey() + with console.status(f"\n:satellite: Staking on netuid: {netuids} ..."): + await asyncio.gather( + *[ + send_extrinsic(ni, am, curr) + for (ni, am, curr) in zip( + netuids, stake_amount_balance, current_stake_balances + ) + ] + ) async def stake_add( @@ -1607,10 +1708,7 @@ async def unstake( ) -async def stake_list( - wallet: Wallet, - subtensor: "SubtensorInterface" -): +async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): substakes = subtensor.get_stake_info_for_coldkeys( coldkey_ss58_list=[wallet.coldkeypub.ss58_address] )[wallet.coldkeypub.ss58_address] @@ -1620,8 +1718,12 @@ async def stake_list( # Token pricing info. dynamic_info = await subtensor.get_all_subnet_dynamic_info() - emission_drain_tempo = int(await subtensor.substrate.query("SubtensorModule", "HotkeyEmissionTempo")) - balance = (await subtensor.get_balance(wallet.coldkeypub.ss58_address))[wallet.coldkeypub.ss58_address] + emission_drain_tempo = int( + await subtensor.substrate.query("SubtensorModule", "HotkeyEmissionTempo") + ) + balance = (await subtensor.get_balance(wallet.coldkeypub.ss58_address))[ + wallet.coldkeypub.ss58_address + ] # Iterate over substakes and aggregate them by hotkey. hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} From 6f013d2f952fdd3695b54e6dfb52964eb4721035 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 9 Oct 2024 22:20:56 +0200 Subject: [PATCH 015/157] Progress --- .../src/bittensor/subtensor_interface.py | 4 +-- bittensor_cli/src/commands/stake/stake.py | 33 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index fdabba62..65160777 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1133,12 +1133,12 @@ async def get_stake_for_coldkey_and_hotkey_on_netuid( coldkey_ss58: str, netuid: int, block_hash: Optional[str] = None, - ) -> Optional["Balance"]: + ) -> "Balance": """Returns the stake under a coldkey - hotkey - netuid pairing""" _result = await self.substrate.query( "SubtensorModule", "Alpha", [hotkey_ss58, coldkey_ss58, netuid], block_hash ) if _result is None: - return None + return Balance(0).set_unit(netuid) else: return Balance.from_rao(_result).set_unit(int(netuid)) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index ee1cf0cc..eec8e6a4 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1216,31 +1216,34 @@ async def stake_add_new( wallet.coldkeypub.ss58_address ].set_unit(0) remaining_wallet_balance = current_wallet_balance - max_slippage = 0 + max_slippage = 0.0 - all_dynamic_info = await asyncio.gather( - *[subtensor.get_subnet_dynamic_info(x) for x in netuids] + all_dynamic_info, initial_stake_balances = await asyncio.gather( + asyncio.gather(*[subtensor.get_subnet_dynamic_info(x) for x in netuids]), + asyncio.gather( + *[ + subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=staking_address_ss58, + netuid=x, + ) + for x in netuids + ] + ), ) - for netuid, dynamic_info in zip(netuids, all_dynamic_info): + for netuid, dynamic_info, current_stake_balance in zip( + netuids, all_dynamic_info, initial_stake_balances + ): # Check that the subnet exists. if not dynamic_info: err_console.print(f"Subnet with netuid: {netuid} does not exist.") continue - # Get old staking balance. - # TODO gather this - current_stake_balance: Balance = ( - subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( # TODO add/await - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=staking_address_ss58, - netuid=netuid, - ).set_unit(netuid) - ) current_stake_balances.append(current_stake_balance) # Get the amount. - amount_to_stake_as_balance = None + amount_to_stake_as_balance = Balance(0) if amount: amount_to_stake_as_balance = Balance.from_tao(amount) elif stake_all: @@ -1309,7 +1312,7 @@ async def stake_add_new( style="light_goldenrod2", ) table.add_column( - f"Recieved ({Balance.get_unit(netuid)})", + f"Received ({Balance.get_unit(netuid)})", justify="center", style="light_slate_blue", ) From c2223cca64f9bd8cbea71ab7d2695919630a1e2c Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 9 Oct 2024 22:23:22 +0200 Subject: [PATCH 016/157] Name shadowing. --- bittensor_cli/src/commands/stake/stake.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index eec8e6a4..6cfc32ca 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1343,14 +1343,14 @@ async def stake_add_new( if not Confirm.ask("Would you like to continue?"): return False - async def send_extrinsic(netuid_i, amount, current): + async def send_extrinsic(netuid_i, amount_, current): call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="add_stake", call_params={ "hotkey": staking_address_ss58, "netuid": netuid_i, - "amount_staked": amount.rao, + "amount_staked": amount_.rao, }, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( @@ -1361,7 +1361,7 @@ async def send_extrinsic(netuid_i, amount, current): ) if not prompt: # TODO verbose? console.print( - f":white_heavy_check_mark: [green]Submitted {amount} to {netuid_i}[/green]" + f":white_heavy_check_mark: [green]Submitted {amount_} to {netuid_i}[/green]" ) else: await response.process_events() From b53137bd307c972cfa058746f48e4f6e3adadafe Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 9 Oct 2024 23:05:25 +0200 Subject: [PATCH 017/157] Ensure balance from rao is handled correctly --- bittensor_cli/src/bittensor/balances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/balances.py b/bittensor_cli/src/bittensor/balances.py index 4943edd7..bc935f01 100644 --- a/bittensor_cli/src/bittensor/balances.py +++ b/bittensor_cli/src/bittensor/balances.py @@ -279,7 +279,7 @@ def from_rao(amount: int): :return: A Balance object representing the given amount. """ - return Balance(amount) + return Balance(int(amount)) @staticmethod def get_unit(netuid: int): From c092c8a1f032097d0db1243d015c9a480d90e39f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 11 Oct 2024 15:09:45 +0200 Subject: [PATCH 018/157] Stake add fully ported. --- bittensor_cli/cli.py | 38 +-- .../src/bittensor/subtensor_interface.py | 58 +++++ bittensor_cli/src/commands/stake/stake.py | 239 +++++++++++------- 3 files changed, 224 insertions(+), 111 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index b012c989..00842f77 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3225,14 +3225,11 @@ def stake_add( "-m", help="Stake is sent to a hotkey only until the hotkey's total stake is less than or equal to this maximum staked TAO. If a hotkey already has stake greater than this amount, then stake is not added to this hotkey.", ), - hotkey_ss58_address: str = typer.Option( - "", - help="The ss58 address of the hotkey to stake to.", - ), include_hotkeys: str = typer.Option( "", "--include-hotkeys", "-in", + "--hotkey-ss58-address", help="Specifies hotkeys by name or ss58 address to stake to. For example, `-in hk1,hk2`", ), exclude_hotkeys: str = typer.Option( @@ -3247,6 +3244,7 @@ def stake_add( help="When set, this command stakes to all hotkeys associated with the wallet. Do not use if specifying " "hotkeys in `--include-hotkeys`.", ), + netuid: Optional[int] = Options.netuid, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -3294,12 +3292,7 @@ def stake_add( ) raise typer.Exit() - if ( - not wallet_hotkey - and not all_hotkeys - and not include_hotkeys - and not hotkey_ss58_address - ): + if not wallet_hotkey and not all_hotkeys and not include_hotkeys: hotkey_or_ss58 = Prompt.ask( "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", ) @@ -3308,6 +3301,7 @@ def stake_add( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) + include_hotkeys = hotkey_or_ss58 else: wallet_hotkey = hotkey_or_ss58 wallet = self.wallet_ask( @@ -3317,8 +3311,9 @@ def stake_add( ask_for=[WO.NAME, WO.HOTKEY, WO.PATH], validate=WV.WALLET_AND_HOTKEY, ) + include_hotkeys = wallet.hotkey.ss58_address - elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address: + elif all_hotkeys or include_hotkeys or exclude_hotkeys: wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) @@ -3332,33 +3327,38 @@ def stake_add( ) if include_hotkeys: - include_hotkeys = parse_to_list( + included_hotkeys = parse_to_list( include_hotkeys, str, "Hotkeys must be a comma-separated list of ss58s, e.g., `--include-hotkeys 5Grw....,5Grw....`.", is_ss58=True, ) + else: + included_hotkeys = [] if exclude_hotkeys: - exclude_hotkeys = parse_to_list( + excluded_hotkeys = parse_to_list( exclude_hotkeys, str, "Hotkeys must be a comma-separated list of ss58s, e.g., `--exclude-hotkeys 5Grw....,5Grw....`.", is_ss58=True, ) + else: + excluded_hotkeys = [] return self._run_command( stake.stake_add( wallet, self.initialize_chain(network), - amount, + netuid, stake_all, + amount, + False, + prompt, max_stake, - include_hotkeys, - exclude_hotkeys, all_hotkeys, - prompt, - hotkey_ss58_address, + included_hotkeys, + excluded_hotkeys, ) ) @@ -3372,7 +3372,7 @@ def stake_remove( False, "--unstake-all", "--all", - help="When set, this commmand unstakes all staked TAO from the specified hotkeys.", + help="When set, this command unstakes all staked TAO from the specified hotkeys.", ), amount: float = typer.Option( 0.0, "--amount", "-a", help="The amount of TAO to unstake." diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 65160777..dec3aa4a 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1142,3 +1142,61 @@ async def get_stake_for_coldkey_and_hotkey_on_netuid( return Balance(0).set_unit(netuid) else: return Balance.from_rao(_result).set_unit(int(netuid)) + + async def multi_get_stake_for_coldkey_and_hotkey_on_netuid( + self, + hotkey_ss58s: list[str], + coldkey_ss58: str, + netuids: list[int], + block_hash: Optional[str] = None, + ) -> dict[str, dict[str, "Balance"]]: + """ + Queries the stake for multiple hotkey - coldkey - netuid pairings. + + Args: + hotkey_ss58s: list of hotkey ss58 addresses + coldkey_ss58: a single coldkey ss58 address + netuids: list of netuids + block_hash: hash of the blockchain block, if any + + Returns: + { + hotkey_ss58_1: { + netuid_1: netuid1_stake, + netuid_2: netuid2_stake, + ... + }, + hotkey_ss58_2: { + netuid_1: netuid1_stake, + netuid_2: netuid2_stake, + ... + }, + ... + } + + """ + calls = [ + ( + await self.substrate.create_storage_key( + "SubtensorModule", + "Alpha", + [hk_ss58, coldkey_ss58, netuid], + block_hash=block_hash, + ) + ) + for hk_ss58 in hotkey_ss58s + for netuid in netuids + ] + batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) + results = {} + for idx, item in enumerate(batch_call): + if hotkey_ss58s[idx] not in results: + results[hotkey_ss58s[idx]] = {} + for netuid in netuids: + value = ( + Balance.from_rao(item).set_unit(netuid) + if item is not None + else Balance(0).set_unit(netuid) + ) + results[hotkey_ss58s[idx]] = value + return results diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 6cfc32ca..63653efc 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -12,7 +12,6 @@ from rich.table import Table, Column import typer - from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import StakeInfo from bittensor_cli.src.bittensor.utils import ( @@ -1148,19 +1147,18 @@ async def get_all_wallet_accounts( ) -async def stake_add_new( +async def stake_add( wallet: Wallet, subtensor: "SubtensorInterface", netuid: Optional[int], stake_all: bool, amount: float, - staking_address_ss58: str, delegate: bool, prompt: bool, max_stake: float, + all_hotkeys: bool, include_hotkeys: list[str], exclude_hotkeys: list[str], - hotkey_ss58: Optional[str] = None, ): """ @@ -1170,9 +1168,12 @@ async def stake_add_new( netuid: the netuid to stake to (None indicates all subnets) stake_all: whether to stake all available balance amount: specified amount of balance to stake - staking_address_ss58: the hotkey to use for the stake - delegate: whether to delegate stake + delegate: whether to delegate stake, currently unused prompt: whether to prompt the user + max_stake: maximum amount to stake (used in combination with stake_all), currently unused + all_hotkeys: whether to stake all hotkeys + include_hotkeys: list of hotkeys to include in staking process (if not specifying `--all`) + exclude_hotkeys: list of hotkeys to exclude in staking (if specifying `--all`) Returns: @@ -1218,101 +1219,139 @@ async def stake_add_new( remaining_wallet_balance = current_wallet_balance max_slippage = 0.0 + hotkeys_to_stake_to: list[tuple[Optional[str], str]] = [] + if all_hotkeys: + # Stake to all hotkeys. + all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) + # Get the hotkeys to exclude. (d)efault to no exclusions. + # Exclude hotkeys that are specified. + hotkeys_to_stake_to = [ + (wallet.hotkey_str, wallet.hotkey.ss58_address) + for wallet in all_hotkeys_ + if wallet.hotkey_str not in exclude_hotkeys + ] # definitely wallets + + elif include_hotkeys: + print_verbose("Staking to only included hotkeys") + # Stake to specific hotkeys. + for hotkey_ss58_or_hotkey_name in include_hotkeys: + if is_valid_ss58_address(hotkey_ss58_or_hotkey_name): + # If the hotkey is a valid ss58 address, we add it to the list. + hotkeys_to_stake_to.append((None, hotkey_ss58_or_hotkey_name)) + else: + # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. + # We then get the hotkey from the wallet and add it to the list. + wallet_ = Wallet( + path=wallet.path, + name=wallet.name, + hotkey=hotkey_ss58_or_hotkey_name, + ) + hotkeys_to_stake_to.append( + (wallet_.hotkey_str, wallet_.hotkey.ss58_address) + ) + else: + # Only config.wallet.hotkey is specified. + # so we stake to that single hotkey. + print_verbose( + f"Staking to hotkey: ({wallet.hotkey_str}) in wallet: ({wallet.name})" + ) + assert wallet.hotkey is not None + hotkey_ss58_or_name = wallet.hotkey.ss58_address + hotkeys_to_stake_to = [(None, hotkey_ss58_or_name)] + all_dynamic_info, initial_stake_balances = await asyncio.gather( asyncio.gather(*[subtensor.get_subnet_dynamic_info(x) for x in netuids]), - asyncio.gather( - *[ - subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=staking_address_ss58, - netuid=x, - ) - for x in netuids - ] + subtensor.multi_get_stake_for_coldkey_and_hotkey_on_netuid( + hotkey_ss58s=[x[1] for x in hotkeys_to_stake_to], + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuids=netuids, ), ) - - for netuid, dynamic_info, current_stake_balance in zip( - netuids, all_dynamic_info, initial_stake_balances - ): - # Check that the subnet exists. - if not dynamic_info: - err_console.print(f"Subnet with netuid: {netuid} does not exist.") - continue - - current_stake_balances.append(current_stake_balance) - - # Get the amount. - amount_to_stake_as_balance = Balance(0) - if amount: - amount_to_stake_as_balance = Balance.from_tao(amount) - elif stake_all: - amount_to_stake_as_balance = current_wallet_balance / len(netuids) - elif not amount and not max_stake: - if Confirm.ask(f"Stake all: [bold]{remaining_wallet_balance}[/bold]?"): - amount_to_stake_as_balance = remaining_wallet_balance - else: - try: - amount = FloatPrompt.ask( - f"Enter amount to stake in {Balance.get_unit(0)} to subnet: {netuid}" - ) - amount_to_stake_as_balance = Balance.from_tao(amount) - except ValueError: - err_console.print( - f":cross_mark:[red]Invalid amount: {amount}[/red]" - ) - return False - stake_amount_balance.append(amount_to_stake_as_balance) - - # Check enough to stake. - amount_to_stake_as_balance.set_unit(0) - if amount_to_stake_as_balance > remaining_wallet_balance: - err_console.print( - f"[red]Not enough stake[/red]:[bold white]\n wallet balance:{remaining_wallet_balance} < " - f"staking amount: {amount_to_stake_as_balance}[/bold white]" + for hk_name, hk_ss58 in hotkeys_to_stake_to: + if not is_valid_ss58_address(hk_ss58): + print_error( + f"The entered hotkey ss58 address is incorrect: {hk_name} | {hk_ss58}" ) return False - remaining_wallet_balance -= amount_to_stake_as_balance + for hotkey in hotkeys_to_stake_to: + for netuid, dynamic_info in zip(netuids, all_dynamic_info): + # Check that the subnet exists. + if not dynamic_info: + err_console.print(f"Subnet with netuid: {netuid} does not exist.") + continue + current_stake_balances.append(initial_stake_balances[hotkey[1]][netuid]) + + # Get the amount. + amount_to_stake_as_balance = Balance(0) + if amount: + amount_to_stake_as_balance = Balance.from_tao(amount) + elif stake_all: + amount_to_stake_as_balance = current_wallet_balance / len(netuids) + elif not amount and not max_stake: + if Confirm.ask(f"Stake all: [bold]{remaining_wallet_balance}[/bold]?"): + amount_to_stake_as_balance = remaining_wallet_balance + else: + try: + amount = FloatPrompt.ask( + f"Enter amount to stake in {Balance.get_unit(0)} to subnet: {netuid}" + ) + amount_to_stake_as_balance = Balance.from_tao(amount) + except ValueError: + err_console.print( + f":cross_mark:[red]Invalid amount: {amount}[/red]" + ) + return False + stake_amount_balance.append(amount_to_stake_as_balance) + + # Check enough to stake. + amount_to_stake_as_balance.set_unit(0) + if amount_to_stake_as_balance > remaining_wallet_balance: + err_console.print( + f"[red]Not enough stake[/red]:[bold white]\n wallet balance:{remaining_wallet_balance} < " + f"staking amount: {amount_to_stake_as_balance}[/bold white]" + ) + return False + remaining_wallet_balance -= amount_to_stake_as_balance - # Slippage warning - received_amount, slippage = dynamic_info.tao_to_alpha_with_slippage( - amount_to_stake_as_balance - ) - if dynamic_info.is_dynamic: - slippage_pct_float = ( - 100 * float(slippage) / float(slippage + received_amount) - if slippage + received_amount != 0 - else 0 + # Slippage warning + received_amount, slippage = dynamic_info.tao_to_alpha_with_slippage( + amount_to_stake_as_balance ) - slippage_pct = f"{slippage_pct_float:.4f} %" - else: - slippage_pct_float = 0 - slippage_pct = "N/A" - max_slippage = max(slippage_pct_float, max_slippage) - rows.append( - ( - str(netuid), - # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", - f"{staking_address_ss58}", - str(amount_to_stake_as_balance), - str(1 / float(dynamic_info.price)) - + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", - str(received_amount.set_unit(netuid)), - str(slippage_pct), + if dynamic_info.is_dynamic: + slippage_pct_float = ( + 100 * float(slippage) / float(slippage + received_amount) + if slippage + received_amount != 0 + else 0 + ) + slippage_pct = f"{slippage_pct_float:.4f} %" + else: + slippage_pct_float = 0 + slippage_pct = "N/A" + max_slippage = max(slippage_pct_float, max_slippage) + rows.append( + ( + str(netuid), + # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", + f"{hotkey}", + str(amount_to_stake_as_balance), + str(1 / float(dynamic_info.price)) + + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", + str(received_amount.set_unit(netuid)), + str(slippage_pct), + ) ) - ) table.add_column("Netuid", justify="center", style="grey89") table.add_column("Hotkey", justify="center", style="light_salmon3") table.add_column( f"Amount ({Balance.get_unit(0)})", justify="center", style="dark_sea_green" ) table.add_column( - f"Rate ({Balance.get_unit(netuid)}/{Balance.get_unit(0)})", + f"Rate (per {Balance.get_unit(0)})", justify="center", style="light_goldenrod2", ) table.add_column( - f"Received ({Balance.get_unit(netuid)})", + "Received", justify="center", style="light_slate_blue", ) @@ -1389,18 +1428,34 @@ async def send_extrinsic(netuid_i, amount_, current): # Perform staking operation. wallet.unlock_coldkey() - with console.status(f"\n:satellite: Staking on netuid: {netuids} ..."): - await asyncio.gather( - *[ - send_extrinsic(ni, am, curr) - for (ni, am, curr) in zip( - netuids, stake_amount_balance, current_stake_balances - ) - ] - ) + with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ..."): + extrinsics_coroutines = [ + send_extrinsic(ni, am, curr) + for (ni, am, curr) in zip( + netuids, stake_amount_balance, current_stake_balances + ) + ] + if len(extrinsics_coroutines) == 1: + await asyncio.gather(*extrinsics_coroutines) + else: + tx_rate_limit_blocks = await subtensor.substrate.query( + module="SubtensorModule", storage_function="TxRateLimit" + ) + if tx_rate_limit_blocks > 0: + for item in extrinsics_coroutines: + await item + with console.status( + f":hourglass: [yellow]Waiting for tx rate limit:" + f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]" + ): + await asyncio.sleep( + tx_rate_limit_blocks * 12 + ) # 12 sec per block + else: + await asyncio.gather(*extrinsics_coroutines) -async def stake_add( +async def stake_add_old( wallet: Wallet, subtensor: "SubtensorInterface", amount: float, From f7fc94aa07d44795dd66368c6385607817ba0132 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 11 Oct 2024 15:10:21 +0200 Subject: [PATCH 019/157] Old stake add removed. --- bittensor_cli/src/commands/stake/stake.py | 173 ---------------------- 1 file changed, 173 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 63653efc..1f94b84a 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1455,179 +1455,6 @@ async def send_extrinsic(netuid_i, amount_, current): await asyncio.gather(*extrinsics_coroutines) -async def stake_add_old( - wallet: Wallet, - subtensor: "SubtensorInterface", - amount: float, - stake_all: bool, - max_stake: float, - include_hotkeys: list[str], - exclude_hotkeys: list[str], - all_hotkeys: bool, - prompt: bool, - hotkey_ss58: Optional[str] = None, -) -> None: - """Stake token of amount to hotkey(s).""" - - async def is_hotkey_registered_any(hk: str, bh: str) -> bool: - return len(await subtensor.get_netuids_for_hotkey(hk, bh)) > 0 - - # Get the hotkey_names (if any) and the hotkey_ss58s. - hotkeys_to_stake_to: list[tuple[Optional[str], str]] = [] - if hotkey_ss58: - if not is_valid_ss58_address(hotkey_ss58): - print_error("The entered ss58 address is incorrect") - typer.Exit() - - # Stake to specific hotkey. - hotkeys_to_stake_to = [(None, hotkey_ss58)] - elif all_hotkeys: - # Stake to all hotkeys. - all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) - # Get the hotkeys to exclude. (d)efault to no exclusions. - # Exclude hotkeys that are specified. - hotkeys_to_stake_to = [ - (wallet.hotkey_str, wallet.hotkey.ss58_address) - for wallet in all_hotkeys_ - if wallet.hotkey_str not in exclude_hotkeys - ] # definitely wallets - - elif include_hotkeys: - print_verbose("Staking to only included hotkeys") - # Stake to specific hotkeys. - for hotkey_ss58_or_hotkey_name in include_hotkeys: - if is_valid_ss58_address(hotkey_ss58_or_hotkey_name): - # If the hotkey is a valid ss58 address, we add it to the list. - hotkeys_to_stake_to.append((None, hotkey_ss58_or_hotkey_name)) - else: - # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. - # We then get the hotkey from the wallet and add it to the list. - wallet_ = Wallet( - path=wallet.path, - name=wallet.name, - hotkey=hotkey_ss58_or_hotkey_name, - ) - hotkeys_to_stake_to.append( - (wallet_.hotkey_str, wallet_.hotkey.ss58_address) - ) - else: - # Only config.wallet.hotkey is specified. - # so we stake to that single hotkey. - print_verbose( - f"Staking to hotkey: ({wallet.hotkey_str}) in wallet: ({wallet.name})" - ) - assert wallet.hotkey is not None - hotkey_ss58_or_name = wallet.hotkey.ss58_address - hotkeys_to_stake_to = [(None, hotkey_ss58_or_name)] - - try: - # Get coldkey balance - print_verbose("Fetching coldkey balance") - wallet_balance_: dict[str, Balance] = await subtensor.get_balance( - wallet.coldkeypub.ss58_address - ) - block_hash = subtensor.substrate.last_block_hash - wallet_balance: Balance = wallet_balance_[wallet.coldkeypub.ss58_address] - old_balance = copy.copy(wallet_balance) - final_hotkeys: list[tuple[Optional[str], str]] = [] - final_amounts: list[Union[float, Balance]] = [] - hotkey: tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58) - - print_verbose("Checking if hotkeys are registered") - registered_ = asyncio.gather( - *[is_hotkey_registered_any(h[1], block_hash) for h in hotkeys_to_stake_to] - ) - if max_stake: - hotkey_stakes_ = asyncio.gather( - *[ - subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=h[1], - coldkey_ss58=wallet.coldkeypub.ss58_address, - block_hash=block_hash, - ) - for h in hotkeys_to_stake_to - ] - ) - else: - - async def null(): - return [None] * len(hotkeys_to_stake_to) - - hotkey_stakes_ = null() - registered: list[bool] - hotkey_stakes: list[Optional[Balance]] - registered, hotkey_stakes = await asyncio.gather(registered_, hotkey_stakes_) - - for hotkey, reg, hotkey_stake in zip( - hotkeys_to_stake_to, registered, hotkey_stakes - ): - if not reg: - # Hotkey is not registered. - if len(hotkeys_to_stake_to) == 1: - # Only one hotkey, error - err_console.print( - f"[red]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Aborting.[/red]" - ) - raise ValueError - else: - # Otherwise, print warning and skip - console.print( - f"[yellow]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Skipping.[/yellow]" - ) - continue - - stake_amount_tao: float = amount - if max_stake: - stake_amount_tao = max_stake - hotkey_stake.tao - - # If the max_stake is greater than the current wallet balance, stake the entire balance. - stake_amount_tao = min(stake_amount_tao, wallet_balance.tao) - if ( - stake_amount_tao <= 0.00001 - ): # Threshold because of fees, might create a loop otherwise - # Skip hotkey if max_stake is less than current stake. - continue - wallet_balance = Balance.from_tao(wallet_balance.tao - stake_amount_tao) - - if wallet_balance.tao < 0: - # No more balance to stake. - break - - final_amounts.append(stake_amount_tao) - final_hotkeys.append(hotkey) # add both the name and the ss58 address. - - if len(final_hotkeys) == 0: - # No hotkeys to stake to. - err_console.print( - "Not enough balance to stake to any hotkeys or max_stake is less than current stake." - ) - raise ValueError - - if len(final_hotkeys) == 1: - # do regular stake - await add_stake_extrinsic( - subtensor, - wallet=wallet, - old_balance=old_balance, - hotkey_ss58=final_hotkeys[0][1], - amount=None if stake_all else final_amounts[0], - wait_for_inclusion=True, - prompt=prompt, - ) - else: - await add_stake_multiple_extrinsic( - subtensor, - wallet=wallet, - old_balance=old_balance, - hotkey_ss58s=[hotkey_ss58 for _, hotkey_ss58 in final_hotkeys], - amounts=None if stake_all else final_amounts, - wait_for_inclusion=True, - prompt=prompt, - ) - except ValueError: - pass - - async def unstake( wallet: Wallet, subtensor: "SubtensorInterface", From db3e8bb4b4ef102862002a18c4c9807c4b16d64d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 11 Oct 2024 15:30:07 +0200 Subject: [PATCH 020/157] Updated multi fetch method. --- .../src/bittensor/subtensor_interface.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index dec3aa4a..bf54946a 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1149,7 +1149,7 @@ async def multi_get_stake_for_coldkey_and_hotkey_on_netuid( coldkey_ss58: str, netuids: list[int], block_hash: Optional[str] = None, - ) -> dict[str, dict[str, "Balance"]]: + ) -> dict[str, dict[int, "Balance"]]: """ Queries the stake for multiple hotkey - coldkey - netuid pairings. @@ -1188,15 +1188,16 @@ async def multi_get_stake_for_coldkey_and_hotkey_on_netuid( for netuid in netuids ] batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) - results = {} + results: dict[str, dict[int, "Balance"]] = {hk_ss58: {} for hk_ss58 in hotkey_ss58s} for idx, item in enumerate(batch_call): - if hotkey_ss58s[idx] not in results: - results[hotkey_ss58s[idx]] = {} - for netuid in netuids: - value = ( - Balance.from_rao(item).set_unit(netuid) - if item is not None - else Balance(0).set_unit(netuid) - ) - results[hotkey_ss58s[idx]] = value + hotkey_idx = idx // len(netuids) + netuid_idx = idx % len(netuids) + hotkey_ss58 = hotkey_ss58s[hotkey_idx] + netuid = netuids[netuid_idx] + value = ( + Balance.from_rao(item).set_unit(netuid) + if item is not None + else Balance(0).set_unit(netuid) + ) + results[hotkey_ss58][netuid] = value return results From 82d013ed1b782204705eb3c482e08329ecafcfbf Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 11 Oct 2024 15:37:47 +0200 Subject: [PATCH 021/157] Added block_hash specification --- bittensor_cli/src/bittensor/subtensor_interface.py | 10 +++++++--- bittensor_cli/src/commands/stake/stake.py | 9 ++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index bf54946a..5d6f55af 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1120,9 +1120,11 @@ async def get_delegates_by_netuid_light( return DelegateInfoLite.list_from_vec_u8(result) # TODO this won't work yet - async def get_subnet_dynamic_info(self, netuid: int) -> Optional["DynamicInfo"]: + async def get_subnet_dynamic_info( + self, netuid: int, block_hash: Optional[str] = None + ) -> Optional["DynamicInfo"]: json = await self.substrate.rpc_request( - method="subnetInfo_getDynamicInfo", params=[netuid, None] + method="subnetInfo_getDynamicInfo", params=[netuid, block_hash] ) subnets = DynamicInfo.from_vec_u8(json["result"]) return subnets @@ -1188,7 +1190,9 @@ async def multi_get_stake_for_coldkey_and_hotkey_on_netuid( for netuid in netuids ] batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) - results: dict[str, dict[int, "Balance"]] = {hk_ss58: {} for hk_ss58 in hotkey_ss58s} + results: dict[str, dict[int, "Balance"]] = { + hk_ss58: {} for hk_ss58 in hotkey_ss58s + } for idx, item in enumerate(batch_call): hotkey_idx = idx // len(netuids) netuid_idx = idx % len(netuids) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 1f94b84a..57ad26ea 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1259,12 +1259,19 @@ async def stake_add( hotkey_ss58_or_name = wallet.hotkey.ss58_address hotkeys_to_stake_to = [(None, hotkey_ss58_or_name)] + starting_chain_head = await subtensor.substrate.get_chain_head() all_dynamic_info, initial_stake_balances = await asyncio.gather( - asyncio.gather(*[subtensor.get_subnet_dynamic_info(x) for x in netuids]), + asyncio.gather( + *[ + subtensor.get_subnet_dynamic_info(x, starting_chain_head) + for x in netuids + ] + ), subtensor.multi_get_stake_for_coldkey_and_hotkey_on_netuid( hotkey_ss58s=[x[1] for x in hotkeys_to_stake_to], coldkey_ss58=wallet.coldkeypub.ss58_address, netuids=netuids, + block_hash=starting_chain_head, ), ) for hk_name, hk_ss58 in hotkeys_to_stake_to: From f3581acdee507f9443f505a437642e1e201686b0 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 15 Oct 2024 16:54:18 +0200 Subject: [PATCH 022/157] Check-in --- .../src/bittensor/subtensor_interface.py | 38 +++++ bittensor_cli/src/commands/stake/stake.py | 138 +++++++++++++++++- 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 5d6f55af..9b93478a 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1205,3 +1205,41 @@ async def multi_get_stake_for_coldkey_and_hotkey_on_netuid( ) results[hotkey_ss58][netuid] = value return results + + async def get_stake_info_for_coldkeys( + self, coldkey_ss58_list: list[str], block_hash: Optional[str] = None + ) -> Optional[dict[str, list[StakeInfo]]]: + """ + Retrieves stake information for a list of coldkeys. This function aggregates stake data for multiple + accounts, providing a collective view of their stakes and delegations. + + Args: + coldkey_ss58_list: A list of SS58 addresses of the accounts' coldkeys. + block_hash: The blockchain block number for the query. + + Returns: + A dictionary mapping each coldkey to a list of its StakeInfo objects. + + This function is useful for analyzing the stake distribution and delegation patterns of multiple + accounts simultaneously, offering a broader perspective on network participation and investment strategies. + """ + encoded_coldkeys = [ + ss58_to_vec_u8(coldkey_ss58) for coldkey_ss58 in coldkey_ss58_list + ] + + hex_bytes_result = await self.query_runtime_api( + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_coldkeys", + params=encoded_coldkeys, + block_hash=block_hash, + ) + + if hex_bytes_result is None: + return None + + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + return StakeInfo.list_of_tuple_from_vec_u8(bytes_result) # type: ignore diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 57ad26ea..0966ba78 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1601,9 +1601,9 @@ async def unstake( async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): - substakes = subtensor.get_stake_info_for_coldkeys( + sub_stakes = (await subtensor.get_stake_info_for_coldkeys( coldkey_ss58_list=[wallet.coldkeypub.ss58_address] - )[wallet.coldkeypub.ss58_address] + ))[wallet.coldkeypub.ss58_address] # Get registered delegates details. registered_delegate_info = await subtensor.get_delegate_identities() @@ -1620,10 +1620,142 @@ async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): # Iterate over substakes and aggregate them by hotkey. hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} - for substake in substakes: + for substake in sub_stakes: hotkey = substake.hotkey_ss58 if substake.stake.rao == 0: continue if hotkey not in hotkeys_to_substakes: hotkeys_to_substakes[hotkey] = [] hotkeys_to_substakes[hotkey].append(substake) + + def table_substakes(hotkey: str, substakes: list[StakeInfo]): + # Create table structure. + name = registered_delegate_info[ + hotkey].name + f" ({hotkey})" if hotkey in registered_delegate_info else hotkey + rows = [] + total_global_tao = Balance(0) + total_tao_value = Balance(0) + for substake in substakes: + netuid = substake.netuid + pool = dynamic_info[netuid] + symbol = f"{bittensor.Balance.get_unit(netuid)}" + price = "{:.4f}{}".format(pool.price.__float__(), + f" τ/{Balance.get_unit(netuid)}\u200E") if pool.is_dynamic else ( + f" 1.0000 τ/{symbol} ") + alpha_value = Balance.from_rao(int(substake.stake.rao)).set_unit(netuid) + locked_value = Balance.from_rao(int(substake.locked.rao)).set_unit(netuid) + tao_value = pool.alpha_to_tao(alpha_value) + total_tao_value += tao_value + swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage(substake.stake) + if pool.is_dynamic: + slippage_percentage = 100 * float(slippage) / float( + slippage + swapped_tao_value) if slippage + swapped_tao_value != 0 else 0 + slippage_percentage = f"[dark_red]{slippage_percentage:.3f}%[/dark_red]" + else: + slippage_percentage = '0.000%' + tao_locked = pool.tao_in + issuance = pool.alpha_out if pool.is_dynamic else tao_locked + per_block_emission = substake.emission.tao / ((emission_drain_tempo / pool.tempo) * pool.tempo) + if alpha_value.tao > 0.00009: + if issuance.tao != 0: + alpha_ownership = "{:.4f}".format((alpha_value.tao / issuance.tao) * 100) + tao_ownership = bittensor.Balance.from_tao((alpha_value.tao / issuance.tao) * tao_locked.tao) + total_global_tao += tao_ownership + else: + alpha_ownership = "0.0000" + tao_ownership = "0.0000" + rows.append([ + str(netuid), # Number + symbol, # Symbol + # f"[medium_purple]{tao_ownership}[/medium_purple] ([light_salmon3]{ alpha_ownership }[/light_salmon3][white]%[/white])", # Tao ownership. + f"[medium_purple]{tao_ownership}[/medium_purple]", # Tao ownership. + # f"[dark_sea_green]{ alpha_value }", # Alpha value + f"{substake.stake.tao:,.4f} {symbol}", + f"{pool.price.tao:.4f} τ/{symbol}", + f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Tao equiv + f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap amount. + # f"[light_salmon3]{ alpha_ownership }%[/light_salmon3]", # Ownership. + f"[bold cadet_blue]YES[/bold cadet_blue]" if substake.is_registered else f"[dark_red]NO[/dark_red]", + # Registered. + str(bittensor.Balance.from_tao(per_block_emission).set_unit( + netuid)) if substake.is_registered else "[dark_red]N/A[/dark_red]", # emission per block. + f"[light_slate_blue]{locked_value}[/light_slate_blue]", # Locked value + ]) + # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") + table = Table( + title=f"[white]hotkey:[/white] [light_salmon3]{name}[/light_salmon3]\n", + width=bittensor.__console__.width - 5, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + table.add_column("[white]Netuid", footer_style="overline white", style="grey89") + table.add_column("[white]Symbol", footer_style="white", style="light_goldenrod1", justify="right", + width=5, + no_wrap=True) + table.add_column(f"[white]TAO({Balance.unit})", style="aquamarine3", justify="right", + footer=f"{total_global_tao}") + table.add_column(f"[white]Stake({Balance.get_unit(1)})", footer_style="overline white", + style="green", justify="right") + table.add_column(f"[white]Rate({Balance.unit}/{Balance.get_unit(1)})", + footer_style="white", style="light_goldenrod2", justify="center") + table.add_column( + f"[white]Value({bittensor.Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", + footer_style="overline white", style="blue", justify="right", footer=f"{total_tao_value}") + table.add_column(f"[white]Swap({Balance.get_unit(1)}) -> {Balance.unit}", + footer_style="overline white", style="white", justify="right") + # table.add_column(f"[white]Control({bittensor.Balance.get_unit(1)})", style="aquamarine3", justify="right") + table.add_column("[white]Registered", style="red", justify="right") + table.add_column(f"[white]Emission({Balance.get_unit(1)}/block)", style="aquamarine3", + justify="right") + table.add_column(f"[white]Locked({Balance.get_unit(1)})", footer_style="overline white", + style="green", justify="right") + for row in rows: + table.add_row(*row) + console.print(table) + return total_global_tao, total_tao_value + + # Iterate over each hotkey and make a table + all_hotkeys_total_global_tao = Balance(0) + all_hotkeys_total_tao_value = Balance(0) + for hotkey in hotkeys_to_substakes.keys(): + stake, value = table_substakes(hotkey, hotkeys_to_substakes[hotkey]) + all_hotkeys_total_global_tao += stake + all_hotkeys_total_tao_value += value + + console.print("\n\n") + console.print( + f"Wallet:\n Coldkey SS58: [bold dark_green]{cli.config.coldkey_address}[/bold dark_green]\n Free Balance: [aquamarine3]{balance}[/aquamarine3]\n Total TAO ({bittensor.Balance.unit}): [aquamarine3]{all_hotkeys_total_global_tao}[/aquamarine3]\n Total Value ({bittensor.Balance.unit}): [aquamarine3]{all_hotkeys_total_tao_value}[/aquamarine3]") + console.print( + """ +[bold white]Description[/bold white]: + Each table displays information about your coldkey's staking accounts with a hotkey. + The header of the table displays the hotkey and the footer displays the total stake and total value of all your staking accounts. + The columns of the table are as follows: + - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). + - [bold white]Symbol[/bold white]: The symbol representing the subnet stake's unit. + - [bold white]TAO[/bold white]: The hotkey's TAO balance on this subnet. This is this hotkey's proportion of total TAO staked into the subnet divided by the hotkey's share of outstanding stake. + - [bold white]Stake[/bold white]: The hotkey's stake balance in subnets staking unit. + - [bold white]Rate[/bold white]: The rate of exchange between the subnet's staking unit and the subnet's TAO. + - [bold white]Value[/bold white]: The price of the hotkey's stake in TAO computed via the exchange rate. + - [bold white]Swap[/bold white]: The amount of TAO received when unstaking all of the hotkey's stake (with slippage). + - [bold white]Registered[/bold white]: Whether the hotkey is registered on this subnet. + - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. + - [bold white]Locked[/bold white]: The total amount of stake locked (not able to be unstaked). +""" + ) \ No newline at end of file From 21d9b4451a8faf8d0d2198786006c9da836c8e05 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 16 Oct 2024 22:16:42 +0200 Subject: [PATCH 023/157] Stake List --- bittensor_cli/cli.py | 75 +-- .../bittensor/async_substrate_interface.py | 4 +- bittensor_cli/src/bittensor/chain_data.py | 70 +- .../src/bittensor/subtensor_interface.py | 7 + bittensor_cli/src/commands/stake/stake.py | 597 +++++------------- 5 files changed, 244 insertions(+), 509 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 00842f77..11ba38e0 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -663,9 +663,6 @@ def __init__(self): )(self.root_nominate) # stake commands - self.stake_app.command( - "show", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] - )(self.stake_show) self.stake_app.command( "add", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] )(self.stake_add) @@ -3130,81 +3127,23 @@ def root_nominate( root.nominate(wallet, self.initialize_chain(network), prompt) ) - def stake_show( + def stake_list( self, - all_wallets: bool = typer.Option( - False, - "--all", - "--all-wallets", - "-a", - help="When set, the command checks all the coldkey wallets of the user instead of just the specified wallet.", - ), network: Optional[list[str]] = Options.network, wallet_name: Optional[str] = Options.wallet_name, wallet_hotkey: Optional[str] = Options.wallet_hotkey, wallet_path: Optional[str] = Options.wallet_path, - reuse_last: bool = Options.reuse_last, - html_output: bool = Options.html_output, quiet: bool = Options.quiet, verbose: bool = Options.verbose, + # TODO add: all-wallets, reuse_last, html_output ): - """ - Lists all the stake accounts associated with a user's wallet. - - This command provides a comprehensive view of the stakes associated with the user's coldkeys. It shows both the user's own hotkeys and also the hotkeys of the delegates to which this user has staked. - - The command lists all the stake accounts for a specified wallet or all wallets in the user's configuration directory. It displays the coldkey, balance, hotkey details (own hotkey and delegate hotkey), stake amount, and the rate of return. - - The command shows a table with the below columns: - - - Coldkey: The coldkey associated with the wallet. - - - Balance: The balance of the coldkey. - - - Hotkey: The names of the coldkey's own hotkeys and the delegate hotkeys to which this coldkey has staked. - - - Stake: The amount of TAO staked to all the hotkeys. - - - Rate: The rate of return on the stake, shown in TAO per day. - - EXAMPLE - - [green]$[/green] btcli stake show --all - """ + """List all stake accounts for wallet.""" self.verbosity_handler(quiet, verbose) - if (reuse_last or html_output) and self.config.get("use_cache") is False: - err_console.print( - "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. " - "Please change the config to 'False' using `btcli config set`" - ) - raise typer.Exit() - if not reuse_last: - subtensor = self.initialize_chain(network) - else: - subtensor = None - - if all_wallets: - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.PATH], - validate=WV.NONE, - ) - else: - wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] - ) - + wallet = self.wallet_ask( + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] + ) return self._run_command( - stake.show( - wallet, - subtensor, - all_wallets, - reuse_last, - html_output, - not self.config.get("use_cache", True), - ) + stake.stake_list(wallet, self.initialize_chain(network)) ) def stake_add( diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index 0c40830d..60ec9dce 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -1707,9 +1707,7 @@ async def rpc_request( ) result = await self._make_rpc_request(payloads, runtime=runtime) if "error" in result[payload_id][0]: - raise SubstrateRequestException( - result[payload_id][0]["error"]["message"] - ) + raise SubstrateRequestException(result[payload_id][0]["error"]["message"]) if "result" in result[payload_id][0]: return result[payload_id][0] else: diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index cd75389b..9f359e92 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -133,22 +133,68 @@ class StakeInfo: hotkey_ss58: str # Hotkey address coldkey_ss58: str # Coldkey address + netuid: int stake: Balance # Stake for the hotkey-coldkey pair + locked: Balance # Stake which is locked. + emission: Balance # Emission for the hotkey-coldkey pair + drain: int + is_registered: bool @classmethod - def list_from_vec_u8(cls, vec_u8: bytes) -> list["StakeInfo"]: - """ - Returns a list of StakeInfo objects from a `vec_u8`. - """ - decoded = bt_decode.StakeInfo.decode_vec(vec_u8) - results = [] - for d in decoded: - hotkey = decode_account_id(d.hotkey) - coldkey = decode_account_id(d.coldkey) - stake = Balance.from_rao(d.stake) - results.append(StakeInfo(hotkey, coldkey, stake)) + def fix_decoded_values(cls, decoded: Any) -> "StakeInfo": + """Fixes the decoded values.""" + return cls( + hotkey_ss58=ss58_encode(decoded["hotkey"], SS58_FORMAT), + coldkey_ss58=ss58_encode(decoded["coldkey"], SS58_FORMAT), + netuid=int(decoded["netuid"]), + stake=Balance.from_rao(decoded["stake"]).set_unit(decoded["netuid"]), + locked=Balance.from_rao(decoded["locked"]).set_unit(decoded["netuid"]), + emission=Balance.from_rao(decoded["emission"]).set_unit(decoded["netuid"]), + drain=int(decoded["drain"]), + is_registered=bool(decoded["is_registered"]), + ) - return results + @classmethod + def from_vec_u8(cls, vec_u8: list[int]) -> Optional["StakeInfo"]: + """Returns a StakeInfo object from a ``vec_u8``.""" + if len(vec_u8) == 0: + return None + + decoded = from_scale_encoding(vec_u8, ChainDataType.StakeInfo) + if decoded is None: + return None + + return StakeInfo.fix_decoded_values(decoded) + + @classmethod + def list_of_tuple_from_vec_u8( + cls, vec_u8: list[int] + ) -> dict[str, list["StakeInfo"]]: + """Returns a list of StakeInfo objects from a ``vec_u8``.""" + decoded: Optional[list[tuple[str, list[object]]]] = ( + from_scale_encoding_using_type_string( + vec_u8, type_string="Vec<(AccountId, Vec)>" + ) + ) + + if decoded is None: + return {} + + return { + ss58_encode(address=account_id, ss58_format=SS58_FORMAT): [ + StakeInfo.fix_decoded_values(d) for d in stake_info + ] + for account_id, stake_info in decoded + } + + @classmethod + def list_from_vec_u8(cls, vec_u8: list[int]) -> list["StakeInfo"]: + """Returns a list of StakeInfo objects from a ``vec_u8``.""" + decoded = from_scale_encoding(vec_u8, ChainDataType.StakeInfo, is_vec=True) + if decoded is None: + return [] + + return [StakeInfo.fix_decoded_values(d) for d in decoded] @dataclass diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 9b93478a..8e76171b 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1243,3 +1243,10 @@ async def get_stake_info_for_coldkeys( bytes_result = bytes.fromhex(hex_bytes_result) return StakeInfo.list_of_tuple_from_vec_u8(bytes_result) # type: ignore + + async def get_all_subnet_dynamic_info(self) -> list["DynamicInfo"]: + json = await self.substrate.rpc_request( + method="subnetInfo_getAllDynamicInfo", params=[None] + ) + subnets = DynamicInfo.list_from_vec_u8(json["result"]) + return subnets diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 0966ba78..07c9891d 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -825,328 +825,6 @@ async def unstake_multiple_extrinsic( # Commands - - -async def show( - wallet: Wallet, - subtensor: Optional["SubtensorInterface"], - all_wallets: bool, - reuse_last: bool, - html_output: bool, - no_cache: bool, -): - """Show all stake accounts.""" - - async def get_stake_accounts( - wallet_, block_hash: str - ) -> dict[str, Union[str, Balance, dict[str, Union[str, Balance]]]]: - """Get stake account details for the given wallet. - - :param wallet_: The wallet object to fetch the stake account details for. - - :return: A dictionary mapping SS58 addresses to their respective stake account details. - """ - - wallet_stake_accounts = {} - - # Get this wallet's coldkey balance. - cold_balance_, stakes_from_hk, stakes_from_d = await asyncio.gather( - subtensor.get_balance( - wallet_.coldkeypub.ss58_address, block_hash=block_hash - ), - get_stakes_from_hotkeys(wallet_, block_hash=block_hash), - get_stakes_from_delegates(wallet_), - ) - - cold_balance = cold_balance_[wallet_.coldkeypub.ss58_address] - - # Populate the stake accounts with local hotkeys data. - wallet_stake_accounts.update(stakes_from_hk) - - # Populate the stake accounts with delegations data. - wallet_stake_accounts.update(stakes_from_d) - - return { - "name": wallet_.name, - "balance": cold_balance, - "accounts": wallet_stake_accounts, - } - - async def get_stakes_from_hotkeys( - wallet_, block_hash: str - ) -> dict[str, dict[str, Union[str, Balance]]]: - """Fetch stakes from hotkeys for the provided wallet. - - :param wallet_: The wallet object to fetch the stakes for. - - :return: A dictionary of stakes related to hotkeys. - """ - - async def get_all_neurons_for_pubkey(hk): - netuids = await subtensor.get_netuids_for_hotkey(hk, block_hash=block_hash) - uid_query = await asyncio.gather( - *[ - subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, hk], - block_hash=block_hash, - ) - for netuid in netuids - ] - ) - uids = [_result for _result in uid_query] - neurons = await asyncio.gather( - *[ - subtensor.neuron_for_uid(uid, net) - for (uid, net) in zip(uids, netuids) - ] - ) - return neurons - - async def get_emissions_and_stake(hk: str): - neurons, stake = await asyncio.gather( - get_all_neurons_for_pubkey(hk), - subtensor.substrate.query( - module="SubtensorModule", - storage_function="Stake", - params=[hk, wallet_.coldkeypub.ss58_address], - block_hash=block_hash, - ), - ) - emission_ = sum([n.emission for n in neurons]) if neurons else 0.0 - return emission_, Balance.from_rao(stake) if stake else Balance(0) - - hotkeys = cast(list[Wallet], get_hotkey_wallets_for_wallet(wallet_)) - stakes = {} - query = await asyncio.gather( - *[get_emissions_and_stake(hot.hotkey.ss58_address) for hot in hotkeys] - ) - for hot, (emission, hotkey_stake) in zip(hotkeys, query): - stakes[hot.hotkey.ss58_address] = { - "name": hot.hotkey_str, - "stake": hotkey_stake, - "rate": emission, - } - return stakes - - async def get_stakes_from_delegates( - wallet_, - ) -> dict[str, dict[str, Union[str, Balance]]]: - """Fetch stakes from delegates for the provided wallet. - - :param wallet_: The wallet object to fetch the stakes for. - - :return: A dictionary of stakes related to delegates. - """ - delegates = await subtensor.get_delegated( - coldkey_ss58=wallet_.coldkeypub.ss58_address, block_hash=None - ) - stakes = {} - for dele, staked in delegates: - for nom in dele.nominators: - if nom[0] == wallet_.coldkeypub.ss58_address: - delegate_name = ( - registered_delegate_info[dele.hotkey_ss58].display - if dele.hotkey_ss58 in registered_delegate_info - else None - ) - stakes[dele.hotkey_ss58] = { - "name": delegate_name if delegate_name else dele.hotkey_ss58, - "stake": nom[1], - "rate": dele.total_daily_return.tao - * (nom[1] / dele.total_stake.tao), - } - return stakes - - async def get_all_wallet_accounts( - block_hash: str, - ) -> list[dict[str, Union[str, Balance, dict[str, Union[str, Balance]]]]]: - """Fetch stake accounts for all provided wallets using a ThreadPool. - - :param block_hash: The block hash to fetch the stake accounts for. - - :return: A list of dictionaries, each dictionary containing stake account details for each wallet. - """ - - accounts_ = await asyncio.gather( - *[get_stake_accounts(w, block_hash=block_hash) for w in wallets] - ) - return accounts_ - - if not reuse_last: - cast("SubtensorInterface", subtensor) - if all_wallets: - wallets = get_coldkey_wallets_for_path(wallet.path) - valid_wallets, invalid_wallets = validate_coldkey_presence(wallets) - wallets = valid_wallets - for invalid_wallet in invalid_wallets: - print_error(f"No coldkeypub found for wallet: ({invalid_wallet.name})") - else: - wallets = [wallet] - - with console.status( - ":satellite: Retrieving account data...", spinner="aesthetic" - ): - block_hash_ = await subtensor.substrate.get_chain_head() - registered_delegate_info = await subtensor.get_delegate_identities( - block_hash=block_hash_ - ) - accounts = await get_all_wallet_accounts(block_hash=block_hash_) - - total_stake: float = 0.0 - total_balance: float = 0.0 - total_rate: float = 0.0 - rows = [] - db_rows = [] - for acc in accounts: - cast(str, acc["name"]) - cast(Balance, acc["balance"]) - rows.append([acc["name"], str(acc["balance"]), "", "", ""]) - db_rows.append( - [acc["name"], float(acc["balance"]), None, None, None, None, 0] - ) - total_balance += cast(Balance, acc["balance"]).tao - for key, value in cast(dict, acc["accounts"]).items(): - if value["name"] and value["name"] != key: - account_display_name = f"{value['name']}" - else: - account_display_name = "(~)" - rows.append( - [ - "", - "", - account_display_name, - key, - str(value["stake"]), - str(value["rate"]), - ] - ) - db_rows.append( - [ - acc["name"], - None, - value["name"], - float(value["stake"]), - float(value["rate"]), - key, - 1, - ] - ) - total_stake += cast(Balance, value["stake"]).tao - total_rate += float(value["rate"]) - metadata = { - "total_stake": "\u03c4{:.5f}".format(total_stake), - "total_balance": "\u03c4{:.5f}".format(total_balance), - "total_rate": "\u03c4{:.5f}/d".format(total_rate), - "rows": json.dumps(rows), - } - if not no_cache: - create_table( - "stakeshow", - [ - ("COLDKEY", "TEXT"), - ("BALANCE", "REAL"), - ("ACCOUNT", "TEXT"), - ("STAKE", "REAL"), - ("RATE", "REAL"), - ("HOTKEY", "TEXT"), - ("CHILD", "INTEGER"), - ], - db_rows, - ) - update_metadata_table("stakeshow", metadata) - else: - try: - metadata = get_metadata_table("stakeshow") - rows = json.loads(metadata["rows"]) - except sqlite3.OperationalError: - err_console.print( - "[red]Error[/red] Unable to retrieve table data. This is usually caused by attempting to use " - "`--reuse-last` before running the command a first time. In rare cases, this could also be due to " - "a corrupted database. Re-run the command (do not use `--reuse-last`) and see if that resolves your " - "issue." - ) - return - if not html_output: - table = Table( - Column("[bold white]Coldkey", style="dark_orange", ratio=1), - Column( - "[bold white]Balance", - metadata["total_balance"], - style="dark_sea_green", - ratio=1, - ), - Column("[bold white]Account", style="bright_cyan", ratio=3), - Column("[bold white]Hotkey", ratio=7, no_wrap=True, style="bright_magenta"), - Column( - "[bold white]Stake", - metadata["total_stake"], - style="light_goldenrod2", - ratio=1, - ), - Column( - "[bold white]Rate /d", - metadata["total_rate"], - style="rgb(42,161,152)", - ratio=1, - ), - title=f"[underline dark_orange]Stake Show[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n", - show_footer=True, - show_edge=False, - expand=False, - border_style="bright_black", - ) - - for i, row in enumerate(rows): - is_last_row = i + 1 == len(rows) - table.add_row(*row) - - # If last row or new coldkey starting next - if is_last_row or (rows[i + 1][0] != ""): - table.add_row(end_section=True) - console.print(table) - - else: - render_tree( - "stakeshow", - f"Stakes | Total Balance: {metadata['total_balance']} - Total Stake: {metadata['total_stake']} " - f"Total Rate: {metadata['total_rate']}", - [ - {"title": "Coldkey", "field": "COLDKEY"}, - { - "title": "Balance", - "field": "BALANCE", - "formatter": "money", - "formatterParams": {"symbol": "τ", "precision": 5}, - }, - { - "title": "Account", - "field": "ACCOUNT", - "width": 425, - }, - { - "title": "Stake", - "field": "STAKE", - "formatter": "money", - "formatterParams": {"symbol": "τ", "precision": 5}, - }, - { - "title": "Daily Rate", - "field": "RATE", - "formatter": "money", - "formatterParams": {"symbol": "τ", "precision": 5}, - }, - { - "title": "Hotkey", - "field": "HOTKEY", - "width": 425, - }, - ], - 0, - ) - - async def stake_add( wallet: Wallet, subtensor: "SubtensorInterface", @@ -1389,7 +1067,7 @@ async def stake_add( if not Confirm.ask("Would you like to continue?"): return False - async def send_extrinsic(netuid_i, amount_, current): + async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="add_stake", @@ -1437,10 +1115,11 @@ async def send_extrinsic(netuid_i, amount_, current): wallet.unlock_coldkey() with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ..."): extrinsics_coroutines = [ - send_extrinsic(ni, am, curr) + send_extrinsic(ni, am, curr, staking_address) for (ni, am, curr) in zip( netuids, stake_amount_balance, current_stake_balances ) + for _, staking_address in hotkeys_to_stake_to ] if len(extrinsics_coroutines) == 1: await asyncio.gather(*extrinsics_coroutines) @@ -1601,9 +1280,11 @@ async def unstake( async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): - sub_stakes = (await subtensor.get_stake_info_for_coldkeys( - coldkey_ss58_list=[wallet.coldkeypub.ss58_address] - ))[wallet.coldkeypub.ss58_address] + sub_stakes = ( + await subtensor.get_stake_info_for_coldkeys( + coldkey_ss58_list=[wallet.coldkeypub.ss58_address] + ) + )[wallet.coldkeypub.ss58_address] # Get registered delegates details. registered_delegate_info = await subtensor.get_delegate_identities() @@ -1620,115 +1301,174 @@ async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): # Iterate over substakes and aggregate them by hotkey. hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} - for substake in sub_stakes: - hotkey = substake.hotkey_ss58 - if substake.stake.rao == 0: - continue - if hotkey not in hotkeys_to_substakes: - hotkeys_to_substakes[hotkey] = [] - hotkeys_to_substakes[hotkey].append(substake) - - def table_substakes(hotkey: str, substakes: list[StakeInfo]): - # Create table structure. - name = registered_delegate_info[ - hotkey].name + f" ({hotkey})" if hotkey in registered_delegate_info else hotkey - rows = [] - total_global_tao = Balance(0) - total_tao_value = Balance(0) - for substake in substakes: - netuid = substake.netuid - pool = dynamic_info[netuid] - symbol = f"{bittensor.Balance.get_unit(netuid)}" - price = "{:.4f}{}".format(pool.price.__float__(), - f" τ/{Balance.get_unit(netuid)}\u200E") if pool.is_dynamic else ( - f" 1.0000 τ/{symbol} ") - alpha_value = Balance.from_rao(int(substake.stake.rao)).set_unit(netuid) - locked_value = Balance.from_rao(int(substake.locked.rao)).set_unit(netuid) - tao_value = pool.alpha_to_tao(alpha_value) - total_tao_value += tao_value - swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage(substake.stake) - if pool.is_dynamic: - slippage_percentage = 100 * float(slippage) / float( - slippage + swapped_tao_value) if slippage + swapped_tao_value != 0 else 0 - slippage_percentage = f"[dark_red]{slippage_percentage:.3f}%[/dark_red]" + def table_substakes(hotkey_: str, substakes: list[StakeInfo]): + # Create table structure. + name = ( + f"{registered_delegate_info[hotkey_].display} ({hotkey_})" + if hotkey_ in registered_delegate_info + else hotkey_ + ) + rows = [] + total_global_tao = Balance(0) + total_tao_value = Balance(0) + for substake_ in substakes: + netuid = substake_.netuid + pool = dynamic_info[netuid] + symbol = f"{Balance.get_unit(netuid)}" + # TODO: what is this price var for? + price = ( + "{:.4f}{}".format( + pool.price.__float__(), f" τ/{Balance.get_unit(netuid)}\u200e" + ) + if pool.is_dynamic + else (f" 1.0000 τ/{symbol} ") + ) + alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid) + locked_value = Balance.from_rao(int(substake_.locked.rao)).set_unit(netuid) + tao_value = pool.alpha_to_tao(alpha_value) + total_tao_value += tao_value + swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage( + substake_.stake + ) + if pool.is_dynamic: + slippage_percentage_ = ( + 100 * float(slippage) / float(slippage + swapped_tao_value) + if slippage + swapped_tao_value != 0 + else 0 + ) + slippage_percentage = ( + f"[dark_red]{slippage_percentage_:.3f}%[/dark_red]" + ) + else: + slippage_percentage = "0.000%" + tao_locked = pool.tao_in + issuance = pool.alpha_out if pool.is_dynamic else tao_locked + per_block_emission = substake_.emission.tao / ( + (emission_drain_tempo / pool.tempo) * pool.tempo + ) + if alpha_value.tao > 0.00009: + if issuance.tao != 0: + alpha_ownership = "{:.4f}".format( + (alpha_value.tao / issuance.tao) * 100 + ) + tao_ownership = Balance.from_tao( + (alpha_value.tao / issuance.tao) * tao_locked.tao + ) + total_global_tao += tao_ownership else: - slippage_percentage = '0.000%' - tao_locked = pool.tao_in - issuance = pool.alpha_out if pool.is_dynamic else tao_locked - per_block_emission = substake.emission.tao / ((emission_drain_tempo / pool.tempo) * pool.tempo) - if alpha_value.tao > 0.00009: - if issuance.tao != 0: - alpha_ownership = "{:.4f}".format((alpha_value.tao / issuance.tao) * 100) - tao_ownership = bittensor.Balance.from_tao((alpha_value.tao / issuance.tao) * tao_locked.tao) - total_global_tao += tao_ownership - else: - alpha_ownership = "0.0000" - tao_ownership = "0.0000" - rows.append([ + # TODO what's this var for? + alpha_ownership = "0.0000" + tao_ownership = "0.0000" + rows.append( + [ str(netuid), # Number symbol, # Symbol # f"[medium_purple]{tao_ownership}[/medium_purple] ([light_salmon3]{ alpha_ownership }[/light_salmon3][white]%[/white])", # Tao ownership. f"[medium_purple]{tao_ownership}[/medium_purple]", # Tao ownership. # f"[dark_sea_green]{ alpha_value }", # Alpha value - f"{substake.stake.tao:,.4f} {symbol}", + f"{substake_.stake.tao:,.4f} {symbol}", f"{pool.price.tao:.4f} τ/{symbol}", f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Tao equiv f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap amount. # f"[light_salmon3]{ alpha_ownership }%[/light_salmon3]", # Ownership. - f"[bold cadet_blue]YES[/bold cadet_blue]" if substake.is_registered else f"[dark_red]NO[/dark_red]", + "[bold cadet_blue]YES[/bold cadet_blue]" + if substake_.is_registered + else "[dark_red]NO[/dark_red]", # Registered. - str(bittensor.Balance.from_tao(per_block_emission).set_unit( - netuid)) if substake.is_registered else "[dark_red]N/A[/dark_red]", # emission per block. + str(Balance.from_tao(per_block_emission).set_unit(netuid)) + if substake_.is_registered + else "[dark_red]N/A[/dark_red]", # emission per block. f"[light_slate_blue]{locked_value}[/light_slate_blue]", # Locked value - ]) - # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") - table = Table( - title=f"[white]hotkey:[/white] [light_salmon3]{name}[/light_salmon3]\n", - width=bittensor.__console__.width - 5, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, - show_footer=True, - show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", - title_justify="center", - highlight=False, - ) - table.add_column("[white]Netuid", footer_style="overline white", style="grey89") - table.add_column("[white]Symbol", footer_style="white", style="light_goldenrod1", justify="right", - width=5, - no_wrap=True) - table.add_column(f"[white]TAO({Balance.unit})", style="aquamarine3", justify="right", - footer=f"{total_global_tao}") - table.add_column(f"[white]Stake({Balance.get_unit(1)})", footer_style="overline white", - style="green", justify="right") - table.add_column(f"[white]Rate({Balance.unit}/{Balance.get_unit(1)})", - footer_style="white", style="light_goldenrod2", justify="center") - table.add_column( - f"[white]Value({bittensor.Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", - footer_style="overline white", style="blue", justify="right", footer=f"{total_tao_value}") - table.add_column(f"[white]Swap({Balance.get_unit(1)}) -> {Balance.unit}", - footer_style="overline white", style="white", justify="right") - # table.add_column(f"[white]Control({bittensor.Balance.get_unit(1)})", style="aquamarine3", justify="right") - table.add_column("[white]Registered", style="red", justify="right") - table.add_column(f"[white]Emission({Balance.get_unit(1)}/block)", style="aquamarine3", - justify="right") - table.add_column(f"[white]Locked({Balance.get_unit(1)})", footer_style="overline white", - style="green", justify="right") - for row in rows: - table.add_row(*row) - console.print(table) - return total_global_tao, total_tao_value + ] + ) + # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") + table = Table( + title=f"[white]hotkey:[/white] [light_salmon3]{name}[/light_salmon3]\n", + width=console.width - 5, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + table.add_column("[white]Netuid", footer_style="overline white", style="grey89") + table.add_column( + "[white]Symbol", + footer_style="white", + style="light_goldenrod1", + justify="right", + width=5, + no_wrap=True, + ) + table.add_column( + f"[white]TAO({Balance.unit})", + style="aquamarine3", + justify="right", + footer=f"{total_global_tao}", + ) + table.add_column( + f"[white]Stake({Balance.get_unit(1)})", + footer_style="overline white", + style="green", + justify="right", + ) + table.add_column( + f"[white]Rate({Balance.unit}/{Balance.get_unit(1)})", + footer_style="white", + style="light_goldenrod2", + justify="center", + ) + table.add_column( + f"[white]Value({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", + footer_style="overline white", + style="blue", + justify="right", + footer=f"{total_tao_value}", + ) + table.add_column( + f"[white]Swap({Balance.get_unit(1)}) -> {Balance.unit}", + footer_style="overline white", + style="white", + justify="right", + ) + # table.add_column(f"[white]Control({bittensor.Balance.get_unit(1)})", style="aquamarine3", justify="right") + table.add_column("[white]Registered", style="red", justify="right") + table.add_column( + f"[white]Emission({Balance.get_unit(1)}/block)", + style="aquamarine3", + justify="right", + ) + table.add_column( + f"[white]Locked({Balance.get_unit(1)})", + footer_style="overline white", + style="green", + justify="right", + ) + for row in rows: + table.add_row(*row) + console.print(table) + return total_global_tao, total_tao_value + + for substake in sub_stakes: + hotkey = substake.hotkey_ss58 + if substake.stake.rao == 0: + continue + if hotkey not in hotkeys_to_substakes: + hotkeys_to_substakes[hotkey] = [] + hotkeys_to_substakes[hotkey].append(substake) # Iterate over each hotkey and make a table all_hotkeys_total_global_tao = Balance(0) @@ -1740,7 +1480,12 @@ def table_substakes(hotkey: str, substakes: list[StakeInfo]): console.print("\n\n") console.print( - f"Wallet:\n Coldkey SS58: [bold dark_green]{cli.config.coldkey_address}[/bold dark_green]\n Free Balance: [aquamarine3]{balance}[/aquamarine3]\n Total TAO ({bittensor.Balance.unit}): [aquamarine3]{all_hotkeys_total_global_tao}[/aquamarine3]\n Total Value ({bittensor.Balance.unit}): [aquamarine3]{all_hotkeys_total_tao_value}[/aquamarine3]") + f"Wallet:\n" + f" Coldkey SS58: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n" + f" Free Balance: [aquamarine3]{balance}[/aquamarine3]\n" + f" Total TAO ({Balance.unit}): [aquamarine3]{all_hotkeys_total_global_tao}[/aquamarine3]\n" + f" Total Value ({Balance.unit}): [aquamarine3]{all_hotkeys_total_tao_value}[/aquamarine3]" + ) console.print( """ [bold white]Description[/bold white]: @@ -1758,4 +1503,4 @@ def table_substakes(hotkey: str, substakes: list[StakeInfo]): - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. - [bold white]Locked[/bold white]: The total amount of stake locked (not able to be unstaked). """ - ) \ No newline at end of file + ) From 0c0c2df3452538da03123b0c38a0ff05d403babf Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 17 Oct 2024 17:10:52 +0200 Subject: [PATCH 024/157] Removed root app/commands. --- bittensor_cli/cli.py | 801 +------------ bittensor_cli/src/commands/root.py | 1770 ---------------------------- 2 files changed, 1 insertion(+), 2570 deletions(-) delete mode 100644 bittensor_cli/src/commands/root.py diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 11ba38e0..8566a180 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -30,7 +30,7 @@ from bittensor_cli.src.bittensor.async_substrate_interface import ( SubstrateRequestException, ) -from bittensor_cli.src.commands import root, subnets, sudo, wallets +from bittensor_cli.src.commands import subnets, sudo, wallets from bittensor_cli.src.commands import weights as weights_cmds from bittensor_cli.src.commands.stake import children_hotkeys, stake from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface @@ -418,7 +418,6 @@ class CLIManager: :var app: the main CLI Typer app :var config_app: the Typer app as it relates to config commands :var wallet_app: the Typer app as it relates to wallet commands - :var root_app: the Typer app as it relates to root commands :var stake_app: the Typer app as it relates to stake commands :var sudo_app: the Typer app as it relates to sudo commands :var subnets_app: the Typer app as it relates to subnets commands @@ -429,7 +428,6 @@ class CLIManager: app: typer.Typer config_app: typer.Typer wallet_app: typer.Typer - root_app: typer.Typer subnets_app: typer.Typer weights_app: typer.Typer utils_app = typer.Typer(epilog=_epilog) @@ -471,7 +469,6 @@ def __init__(self): ) self.config_app = typer.Typer(epilog=_epilog) self.wallet_app = typer.Typer(epilog=_epilog) - self.root_app = typer.Typer(epilog=_epilog) self.stake_app = typer.Typer(epilog=_epilog) self.sudo_app = typer.Typer(epilog=_epilog) self.subnets_app = typer.Typer(epilog=_epilog) @@ -501,15 +498,6 @@ def __init__(self): self.wallet_app, name="wallets", hidden=True, no_args_is_help=True ) - # root aliases - self.app.add_typer( - self.root_app, - name="root", - short_help="Root commands, alias: `r`", - no_args_is_help=True, - ) - self.app.add_typer(self.root_app, name="r", hidden=True, no_args_is_help=True) - # stake aliases self.app.add_typer( self.stake_app, @@ -619,49 +607,6 @@ def __init__(self): "sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] )(self.wallet_sign) - # root commands - self.root_app.command("list")(self.root_list) - self.root_app.command( - "set-weights", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"] - )(self.root_set_weights) - self.root_app.command( - "get-weights", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"] - )(self.root_get_weights) - self.root_app.command( - "boost", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"] - )(self.root_boost) - self.root_app.command( - "slash", rich_help_panel=HELP_PANELS["ROOT"]["WEIGHT_MGMT"] - )(self.root_slash) - self.root_app.command( - "senate", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"] - )(self.root_senate) - self.root_app.command( - "senate-vote", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"] - )(self.root_senate_vote) - self.root_app.command("register")(self.root_register) - self.root_app.command( - "proposals", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"] - )(self.root_proposals) - self.root_app.command( - "set-take", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"] - )(self.root_set_take) - self.root_app.command( - "delegate-stake", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"] - )(self.root_delegate_stake) - self.root_app.command( - "undelegate-stake", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"] - )(self.root_undelegate_stake) - self.root_app.command( - "my-delegates", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"] - )(self.root_my_delegates) - self.root_app.command( - "list-delegates", rich_help_panel=HELP_PANELS["ROOT"]["DELEGATION"] - )(self.root_list_delegates) - self.root_app.command( - "nominate", rich_help_panel=HELP_PANELS["ROOT"]["GOVERNANCE"] - )(self.root_nominate) - # stake commands self.stake_app.command( "add", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] @@ -761,18 +706,6 @@ def __init__(self): hidden=True, )(self.wallet_get_id) - # Root - self.root_app.command("set_weights", hidden=True)(self.root_set_weights) - self.root_app.command("get_weights", hidden=True)(self.root_get_weights) - self.root_app.command("senate_vote", hidden=True)(self.root_senate_vote) - self.root_app.command("set_take", hidden=True)(self.root_set_take) - self.root_app.command("delegate_stake", hidden=True)(self.root_delegate_stake) - self.root_app.command("undelegate_stake", hidden=True)( - self.root_undelegate_stake - ) - self.root_app.command("my_delegates", hidden=True)(self.root_my_delegates) - self.root_app.command("list_delegates", hidden=True)(self.root_list_delegates) - # Subnets self.subnets_app.command("lock_cost", hidden=True)(self.subnets_lock_cost) self.subnets_app.command("pow_register", hidden=True)(self.subnets_pow_register) @@ -2395,738 +2328,6 @@ def wallet_sign( return self._run_command(wallets.sign(wallet, message, use_hotkey)) - def root_list( - self, - network: Optional[list[str]] = Options.network, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Show the neurons (root network validators) in the root network (netuid = 0). - - USAGE - - The command fetches and lists the neurons (root network validators) in the root network, showing their unique identifiers (UIDs), names, addresses, stakes, and whether they are part of the senate (network governance body). - - This command is useful for understanding the composition and governance structure of the Bittensor network's root network. It provides insights into which neurons hold significant influence and responsibility within the Bittensor network. - - EXAMPLE - - [green]$[/green] btcli root list - """ - self.verbosity_handler(quiet, verbose) - return self._run_command( - root.root_list(subtensor=self.initialize_chain(network)) - ) - - def root_set_weights( - self, - network: Optional[list[str]] = Options.network, - wallet_name: str = Options.wallet_name, - wallet_path: str = Options.wallet_path, - wallet_hotkey: str = Options.wallet_hotkey, - netuids=typer.Option( - None, - "--netuids", - "--netuid", - "-n", - help="Set the netuid(s) to set weights to. Separate multiple netuids with a comma, for example: `-n 0,1,2`.", - ), - weights: str = Options.weights, - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Set the weights for different subnets, by setting them in the root network. - - To use this command, you should specify the netuids and corresponding weights you wish to assign. This command is used by validators registered to the root subnet to influence the distribution of subnet rewards and responsibilities. - - You must have a comprehensive understanding of the dynamics of the subnets to use this command. It is a powerful tool that directly impacts the subnet's operational mechanics and reward distribution. - - EXAMPLE - - With no spaces between the passed values: - - [green]$[/green] btcli root set-weights --netuids 1,2 --weights 0.2,0.3 - - or - - Include double quotes to include spaces between the passed values: - - [green]$[/green] btcli root set-weights --netuids "1, 2" --weights "0.2, 0.3" - """ - self.verbosity_handler(quiet, verbose) - - if netuids: - netuids = parse_to_list( - netuids, - int, - "Netuids must be a comma-separated list of ints, e.g., `--netuid 1,2,3,4`.", - ) - else: - netuids = list_prompt(netuids, int, "Enter netuids (e.g: 1, 4, 6)") - - if weights: - weights = parse_to_list( - weights, - float, - "Weights must be a comma-separated list of floats, e.g., `--weights 0.3,0.4,0.3`.", - ) - else: - weights = list_prompt( - weights, float, "Enter weights (e.g. 0.02, 0.03, 0.01)" - ) - - if len(netuids) != len(weights): - raise typer.BadParameter( - "The number of netuids and weights must be the same." - ) - - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.HOTKEY, WO.PATH, WO.NAME], - validate=WV.WALLET_AND_HOTKEY, - ) - self._run_command( - root.set_weights( - wallet, self.initialize_chain(network), netuids, weights, prompt - ) - ) - - def root_get_weights( - self, - network: Optional[list[str]] = Options.network, - limit_min_col: Optional[int] = typer.Option( - None, - "--limit-min-col", - "--min", - help="Limit the left display of the table to this column.", - ), - limit_max_col: Optional[int] = typer.Option( - None, - "--limit-max-col", - "--max", - help="Limit the right display of the table to this column.", - ), - reuse_last: bool = Options.reuse_last, - html_output: bool = Options.html_output, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Shows a table listing the weights assigned to each subnet in the root network. - - This command provides visibility into how network responsibilities and rewards are distributed among various subnets. This information is crucial for understanding the current influence and reward distribution across different subnets. Use this command if you are interested in the governance and operational dynamics of the Bittensor network. - - EXAMPLE - - [green]$[/green] btcli root get_weights - """ - self.verbosity_handler(quiet, verbose) - if (reuse_last or html_output) and self.config.get("use_cache") is False: - err_console.print( - "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'." - "Change it to 'False' using `btcli config set`." - ) - raise typer.Exit() - if not reuse_last: - subtensor = self.initialize_chain(network) - else: - subtensor = None - return self._run_command( - root.get_weights( - subtensor, - limit_min_col, - limit_max_col, - reuse_last, - html_output, - not self.config.get("use_cache", True), - ) - ) - - def root_boost( - self, - network: Optional[list[str]] = Options.network, - wallet_name: str = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - netuid: int = Options.netuid, - amount: float = typer.Option( - None, - "--amount", - "--increase", - "-a", - prompt="Enter the boost amount (added to existing weight)", - help="Amount (float) to boost (added to the existing weight), (e.g. 0.01)", - ), - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Increase (boost) the weights for a specific subnet in the root network. Any amount provided will be added to the subnet's existing weight. - - EXAMPLE - - [green]$[/green] btcli root boost --netuid 1 --increase 0.01 - """ - self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, - ) - return self._run_command( - root.set_boost( - wallet, self.initialize_chain(network), netuid, amount, prompt - ) - ) - - def root_slash( - self, - network: Optional[list[str]] = Options.network, - wallet_name: str = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - netuid: int = Options.netuid, - amount: float = typer.Option( - None, - "--amount", - "--decrease", - "-a", - prompt="Enter the slash amount (subtracted from the existing weight)", - help="Amount (float) to slash (subtract from the existing weight), (e.g. 0.01)", - ), - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Decrease (slash) the weights for a specific subnet in the root network. Any amount provided will be subtracted from the subnet's existing weight. - - EXAMPLE - - [green]$[/green] btcli root slash --netuid 1 --decrease 0.01 - - """ - self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, - ) - return self._run_command( - root.set_slash( - wallet, self.initialize_chain(network), netuid, amount, prompt - ) - ) - - def root_senate_vote( - self, - network: Optional[list[str]] = Options.network, - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - proposal: str = typer.Option( - None, - "--proposal", - "--proposal-hash", - prompt="Enter the proposal hash", - help="The hash of the proposal to vote on.", - ), - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - vote: bool = typer.Option( - None, - "--vote-aye/--vote-nay", - prompt="Enter y to vote Aye, or enter n to vote Nay", - help="The vote casted on the proposal", - ), - ): - """ - Cast a vote on an active proposal in Bittensor's governance protocol. - - This command is used by Senate members to vote on various proposals that shape the network's future. Use `btcli root proposals` to see the active proposals and their hashes. - - USAGE - - The user must specify the hash of the proposal they want to vote on. The command then allows the Senate member to cast a 'Yes' or 'No' vote, contributing to the decision-making process on the proposal. This command is crucial for Senate members to exercise their voting rights on key proposals. It plays a vital role in the governance and evolution of the Bittensor network. - - EXAMPLE - - [green]$[/green] btcli root senate_vote --proposal - """ - self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, - ) - return self._run_command( - root.senate_vote( - wallet, self.initialize_chain(network), proposal, vote, prompt - ) - ) - - def root_senate( - self, - network: Optional[list[str]] = Options.network, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Shows the Senate members of the Bittensor's governance protocol. - - This command lists the delegates involved in the decision-making process of the Bittensor network, showing their names and wallet addresses. This information is crucial for understanding who holds governance roles within the network. - - EXAMPLE - - [green]$[/green] btcli root senate - """ - self.verbosity_handler(quiet, verbose) - return self._run_command(root.get_senate(self.initialize_chain(network))) - - def root_register( - self, - network: Optional[list[str]] = Options.network, - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Register a neuron to the root subnet by recycling some TAO to cover for the registration cost. - - This command adds a new neuron as a validator on the root network. This will allow the neuron owner to set subnet weights. - - # Usage: - - Before registering, the command checks if the specified subnet exists and whether the TAO balance in the user's wallet is sufficient to cover the registration cost. The registration cost is determined by the current recycle amount for the specified subnet. If the balance is insufficient or the subnet does not exist, the command will exit with an appropriate error message. - - # Example usage: - - [green]$[/green] btcli subnets register --netuid 1 - """ - self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, - ) - return self._run_command( - root.register(wallet, self.initialize_chain(network), prompt) - ) - - def root_proposals( - self, - network: Optional[list[str]] = Options.network, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - View active proposals for the senate in the Bittensor's governance protocol. - - This command displays the details of ongoing proposals, including proposal hashes, votes, thresholds, and proposal data. - - EXAMPLE - - [green]$[/green] btcli root proposals - """ - self.verbosity_handler(quiet, verbose) - return self._run_command(root.proposals(self.initialize_chain(network))) - - def root_set_take( - self, - network: Optional[list[str]] = Options.network, - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - take: float = typer.Option(None, help="The new take value."), - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Allows users to change their delegate take percentage. - - This command can be used to update the delegate takes individually for every subnet. To run the command, the user must have a configured wallet with both hotkey and coldkey. The command performs the below checks: - - 1. The provided hotkey is already a delegate. - 2. The new take value is within 0-18% range. - - EXAMPLE - - [green]$[/green] btcli root set_take --wallet-name my_wallet --wallet-hotkey my_hotkey - """ - max_value = 0.18 - min_value = 0.00 - self.verbosity_handler(quiet, verbose) - - if not take: - max_value_style = typer.style(f"Max: {max_value}", fg="magenta") - min_value_style = typer.style(f"Min: {min_value}", fg="magenta") - prompt_text = typer.style( - "Enter take value (0.18 for 18%)", fg="bright_cyan", bold=True - ) - take = FloatPrompt.ask(f"{prompt_text} {min_value_style} {max_value_style}") - - if not (min_value <= take <= max_value): - print_error( - f"Take value must be between {min_value} and {max_value}. Provided value: {take}" - ) - raise typer.Exit() - - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, - ) - - return self._run_command( - root.set_take(wallet, self.initialize_chain(network), take) - ) - - def root_delegate_stake( - self, - delegate_ss58key: str = typer.Option( - None, - help="The ss58 address of the delegate hotkey to stake TAO to.", - prompt="Enter the hotkey ss58 address you want to delegate TAO to.", - ), - amount: Optional[float] = typer.Option( - None, help="The amount of TAO to stake. Do no specify if using `--all`" - ), - stake_all: Optional[bool] = typer.Option( - False, - "--all", - "-a", - help="If specified, the command stakes all available TAO. Do not specify if using" - " `--amount`", - ), - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - network: Optional[list[str]] = Options.network, - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Stakes TAO to a specified delegate hotkey. - - This command allocates the user's TAO to the specified hotkey of a delegate, potentially earning staking rewards in return. If the - `--all` flag is used, it delegates the entire TAO balance available in the user's wallet. - - This command should be run by a TAO holder. Compare this command with "btcli stake add" that is typically run by a subnet validator to add stake to their own delegate hotkey. - - EXAMPLE - - [green]$[/green] btcli root delegate-stake --delegate_ss58key --amount - - [green]$[/green] btcli root delegate-stake --delegate_ss58key --all - - [blue bold]Note[/blue bold]: This command modifies the blockchain state and may incur transaction fees. The user should ensure the delegate's address and the amount to be staked are correct before executing the command. - """ - self.verbosity_handler(quiet, verbose) - if amount and stake_all: - err_console.print( - "Both `--amount` and `--all` are specified. Choose one or the other." - ) - if not stake_all and not amount: - while True: - amount = FloatPrompt.ask( - "[blue bold]Amount to stake (TAO τ)[/blue bold]", console=console - ) - confirmation = FloatPrompt.ask( - "[blue bold]Confirm the amount to stake (TAO τ)[/blue bold]", - console=console, - ) - if amount == confirmation: - break - else: - err_console.print( - "[red]The amounts do not match. Please try again.[/red]" - ) - - wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] - ) - return self._run_command( - root.delegate_stake( - wallet, - self.initialize_chain(network), - amount, - delegate_ss58key, - prompt, - ) - ) - - def root_undelegate_stake( - self, - delegate_ss58key: str = typer.Option( - None, - help="The ss58 address of the delegate to undelegate from.", - prompt="Enter the hotkey ss58 address you want to undelegate from", - ), - amount: Optional[float] = typer.Option( - None, help="The amount of TAO to unstake. Do no specify if using `--all`" - ), - unstake_all: Optional[bool] = typer.Option( - False, - "--all", - "-a", - help="If specified, the command undelegates all staked TAO from the delegate. Do not specify if using" - " `--amount`", - ), - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - network: Optional[list[str]] = Options.network, - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Allows users to withdraw their staked TAO from a delegate. - - The user must provide the delegate hotkey's ss58 address and the amount of TAO to undelegate. The function will then send a transaction to the blockchain to process the undelegation. This command can result in a change to the blockchain state and may incur transaction fees. - - EXAMPLE - - [green]$[/green] btcli undelegate --delegate_ss58key --amount - - [green]$[/green] btcli undelegate --delegate_ss58key --all - """ - self.verbosity_handler(quiet, verbose) - if amount and unstake_all: - err_console.print( - "Both `--amount` and `--all` are specified. Choose one or the other." - ) - if not unstake_all and not amount: - while True: - amount = FloatPrompt.ask( - "[blue bold]Amount to unstake (TAO τ)[/blue bold]", console=console - ) - confirmation = FloatPrompt.ask( - "[blue bold]Confirm the amount to unstake (TAO τ)[/blue bold]", - console=console, - ) - if amount == confirmation: - break - else: - err_console.print( - "[red]The amounts do not match. Please try again.[/red]" - ) - - wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] - ) - self._run_command( - root.delegate_unstake( - wallet, - self.initialize_chain(network), - amount, - delegate_ss58key, - prompt, - ) - ) - - def root_my_delegates( - self, - network: Optional[list[str]] = Options.network, - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - all_wallets: bool = typer.Option( - False, - "--all-wallets", - "--all", - "-a", - help="If specified, the command aggregates information across all the wallets.", - ), - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Shows a table with the details on the user's delegates. - - The table output includes the following columns: - - - Wallet: The name of the user's wallet (coldkey). - - - OWNER: The name of the delegate who owns the hotkey. - - - SS58: The truncated SS58 address of the delegate's hotkey. - - - Delegation: The amount of TAO staked by the user to the delegate. - - - τ/24h: The earnings from the delegate to the user over the past 24 hours. - - - NOMS: The number of nominators for the delegate. - - - OWNER STAKE(τ): The stake amount owned by the delegate. - - - TOTAL STAKE(τ): The total stake amount held by the delegate. - - - SUBNETS: The list of subnets the delegate is a part of. - - - VPERMIT: Validator permits held by the delegate for various subnets. - - - 24h/kτ: Earnings per 1000 TAO staked over the last 24 hours. - - - Desc: A description of the delegate. - - The command also sums and prints the total amount of TAO delegated across all wallets. - - EXAMPLE - - [green]$[/green] btcli root my-delegates - [green]$[/green] btcli root my-delegates --all - [green]$[/green] btcli root my-delegates --wallet-name my_wallet - - [blue bold]Note[/blue bold]: This command is not intended to be used directly in user code. - """ - self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=([WO.NAME, WO.PATH] if not all_wallets else [WO.PATH]), - validate=WV.WALLET if not all_wallets else WV.NONE, - ) - self._run_command( - root.my_delegates(wallet, self.initialize_chain(network), all_wallets) - ) - - def root_list_delegates( - self, - network: Optional[list[str]] = Options.network, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Displays a table of Bittensor network-wide delegates, providing a comprehensive overview of delegate statistics and information. - - This table helps users make informed decisions on which delegates to allocate their TAO stake. - - The table columns include: - - - INDEX: The delegate's index in the sorted list. - - - DELEGATE: The name of the delegate. - - - SS58: The delegate's unique ss58 address (truncated for display). - - - NOMINATORS: The count of nominators backing the delegate. - - - OWN STAKE(τ): The amount of delegate's own stake (not the TAO delegated from any nominators). - - - TOTAL STAKE(τ): The delegate's total stake, i.e., the sum of delegate's own stake and nominators' stakes. - - - CHANGE/(4h): The percentage change in the delegate's stake over the last four hours. - - - SUBNETS: The subnets in which the delegate is registered. - - - VPERMIT: Indicates the subnets in which the delegate has validator permits. - - - NOMINATOR/(24h)/kτ: The earnings per 1000 τ staked by nominators in the last 24 hours. - - - DELEGATE/(24h): The total earnings of the delegate in the last 24 hours. - - - DESCRIPTION: A brief description of the delegate's purpose and operations. - - [blue bold]NOTES:[/blue bold] - - - Sorting is done based on the `TOTAL STAKE` column in descending order. - - Changes in stake are shown as: increases in green and decreases in red. - - Entries with no previous data are marked with `NA`. - - Each delegate's name is a hyperlink to more information, if available. - - EXAMPLE - - [green]$[/green] btcli root list_delegates - - [green]$[/green] btcli root list_delegates --subtensor.network finney # can also be `test` or `local` - - [blue bold]NOTE[/blue bold]: This command is intended for use within a - console application. It prints directly to the console and does not return any value. - """ - self.verbosity_handler(quiet, verbose) - - if network: - if "finney" in network: - network = ["wss://archive.chain.opentensor.ai:443"] - elif (conf_net := self.config.get("network")) == "finney": - network = ["wss://archive.chain.opentensor.ai:443"] - elif conf_net: - network = [conf_net] - else: - network = ["wss://archive.chain.opentensor.ai:443"] - - sub = self.initialize_chain(network) - return self._run_command(root.list_delegates(sub)) - - # TODO: Confirm if we need a command for this - currently registering to root auto makes u delegate - def root_nominate( - self, - wallet_name: Optional[str] = Options.wallet_name, - wallet_path: Optional[str] = Options.wallet_path, - wallet_hotkey: Optional[str] = Options.wallet_hotkey, - network: Optional[list[str]] = Options.network, - prompt: bool = Options.prompt, - quiet: bool = Options.quiet, - verbose: bool = Options.verbose, - ): - """ - Enables a wallet's hotkey to become a delegate. - - This command handles the nomination process, including wallet unlocking and verification of the hotkey's current delegate status. - - The command performs several checks: - - - Verifies that the hotkey is not already a delegate to prevent redundant nominations. - - - Tries to nominate the wallet and reports success or failure. - - Upon success, the wallet's hotkey is registered as a delegate on the network. - - To run the command, the user must have a configured wallet with both hotkey and coldkey. If the wallet is not already nominated, this command will initiate the process. - - EXAMPLE - - [green]$[/green] btcli root nominate - - [green]$[/green] btcli root nominate --wallet-name my_wallet --wallet-hotkey my_hotkey - - [blue bold]Note[/blue bold]: This command prints the output directly to the console. It should not be called programmatically in user code due to its interactive nature and side effects on the network state. - """ - self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, - ) - return self._run_command( - root.nominate(wallet, self.initialize_chain(network), prompt) - ) - def stake_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/commands/root.py b/bittensor_cli/src/commands/root.py deleted file mode 100644 index 2401eb00..00000000 --- a/bittensor_cli/src/commands/root.py +++ /dev/null @@ -1,1770 +0,0 @@ -import asyncio -import json -from typing import Optional, TYPE_CHECKING - -from bittensor_wallet import Wallet -from bittensor_wallet.errors import KeyFileError -import numpy as np -from numpy.typing import NDArray -from rich import box -from rich.prompt import Confirm -from rich.table import Column, Table -from rich.text import Text -from scalecodec import GenericCall, ScaleType -from substrateinterface.exceptions import SubstrateRequestException -import typer - -from bittensor_cli.src import DelegatesDetails -from bittensor_cli.src.bittensor.balances import Balance -from bittensor_cli.src.bittensor.chain_data import ( - DelegateInfo, - NeuronInfoLite, - decode_account_id, -) -from bittensor_cli.src.bittensor.extrinsics.root import ( - root_register_extrinsic, - set_root_weights_extrinsic, -) -from bittensor_cli.src.commands.wallets import ( - get_coldkey_wallets_for_path, - set_id, - set_id_prompts, -) -from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface -from bittensor_cli.src.bittensor.utils import ( - console, - convert_weight_uids_and_vals_to_tensor, - create_table, - err_console, - print_verbose, - get_metadata_table, - render_table, - ss58_to_vec_u8, - update_metadata_table, - group_subnets, -) - -if TYPE_CHECKING: - from bittensor_cli.src.bittensor.subtensor_interface import ProposalVoteData - -# helpers - - -def display_votes( - vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails] -) -> str: - vote_list = list() - - for address in vote_data.ayes: - vote_list.append( - "{}: {}".format( - delegate_info[address].display if address in delegate_info else address, - "[bold green]Aye[/bold green]", - ) - ) - - for address in vote_data.nays: - vote_list.append( - "{}: {}".format( - delegate_info[address].display if address in delegate_info else address, - "[bold red]Nay[/bold red]", - ) - ) - - return "\n".join(vote_list) - - -def format_call_data(call_data: dict) -> str: - # Extract the module and call details - module, call_details = next(iter(call_data.items())) - - # Extract the call function name and arguments - call_info = call_details[0] - call_function, call_args = next(iter(call_info.items())) - - # Extract the argument, handling tuple values - formatted_args = ", ".join( - str(arg[0]) if isinstance(arg, tuple) else str(arg) - for arg in call_args.values() - ) - - # Format the final output string - return f"{call_function}({formatted_args})" - - -async def _get_senate_members( - subtensor: SubtensorInterface, block_hash: Optional[str] = None -) -> list[str]: - """ - Gets all members of the senate on the given subtensor's network - - :param subtensor: SubtensorInterface object to use for the query - - :return: list of the senate members' ss58 addresses - """ - senate_members = await subtensor.substrate.query( - module="SenateMembers", - storage_function="Members", - params=None, - block_hash=block_hash, - ) - try: - return [ - decode_account_id(i[x][0]) for i in senate_members for x in range(len(i)) - ] - except (IndexError, TypeError): - err_console.print("Unable to retrieve senate members.") - return [] - - -async def _get_proposals( - subtensor: SubtensorInterface, block_hash: str -) -> dict[str, tuple[dict, "ProposalVoteData"]]: - async def get_proposal_call_data(p_hash: str) -> Optional[GenericCall]: - proposal_data = await subtensor.substrate.query( - module="Triumvirate", - storage_function="ProposalOf", - block_hash=block_hash, - params=[p_hash], - ) - return proposal_data - - ph = await subtensor.substrate.query( - module="Triumvirate", - storage_function="Proposals", - params=None, - block_hash=block_hash, - ) - - try: - proposal_hashes: list[str] = [ - f"0x{bytes(ph[0][x][0]).hex()}" for x in range(len(ph[0])) - ] - except (IndexError, TypeError): - err_console.print("Unable to retrieve proposal vote data") - return {} - - call_data_, vote_data_ = await asyncio.gather( - asyncio.gather(*[get_proposal_call_data(h) for h in proposal_hashes]), - asyncio.gather(*[subtensor.get_vote_data(h) for h in proposal_hashes]), - ) - return { - proposal_hash: (cd, vd) - for cd, vd, proposal_hash in zip(call_data_, vote_data_, proposal_hashes) - } - - -def _validate_proposal_hash(proposal_hash: str) -> bool: - if proposal_hash[0:2] != "0x" or len(proposal_hash) != 66: - return False - else: - return True - - -async def _is_senate_member(subtensor: SubtensorInterface, hotkey_ss58: str) -> bool: - """ - Checks if a given neuron (identified by its hotkey SS58 address) is a member of the Bittensor senate. - The senate is a key governance body within the Bittensor network, responsible for overseeing and - approving various network operations and proposals. - - :param subtensor: SubtensorInterface object to use for the query - :param hotkey_ss58: The `SS58` address of the neuron's hotkey. - - :return: `True` if the neuron is a senate member at the given block, `False` otherwise. - - This function is crucial for understanding the governance dynamics of the Bittensor network and for - identifying the neurons that hold decision-making power within the network. - """ - - senate_members = await _get_senate_members(subtensor) - - if not hasattr(senate_members, "count"): - return False - - return senate_members.count(hotkey_ss58) > 0 - - -async def vote_senate_extrinsic( - subtensor: SubtensorInterface, - wallet: Wallet, - proposal_hash: str, - proposal_idx: int, - vote: bool, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - prompt: bool = False, -) -> bool: - """Votes ayes or nays on proposals. - - :param subtensor: The SubtensorInterface object to use for the query - :param wallet: Bittensor wallet object, with coldkey and hotkey unlocked. - :param proposal_hash: The hash of the proposal for which voting data is requested. - :param proposal_idx: The index of the proposal to vote. - :param vote: Whether to vote aye or nay. - :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, - or returns `False` if the extrinsic fails to be finalized within the timeout. - :param prompt: If `True`, the call waits for confirmation from the user before proceeding. - - :return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for - finalization/inclusion, the response is `True`. - """ - - if prompt: - # Prompt user for confirmation. - if not Confirm.ask(f"Cast a vote of {vote}?"): - return False - - with console.status(":satellite: Casting vote..", spinner="aesthetic"): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="vote", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "proposal": proposal_hash, - "index": proposal_idx, - "approve": vote, - }, - ) - success, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization - ) - if not success: - err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") - await asyncio.sleep(0.5) - return False - # Successful vote, final check for data - else: - if vote_data := await subtensor.get_vote_data(proposal_hash): - if ( - vote_data.ayes.count(wallet.hotkey.ss58_address) > 0 - or vote_data.nays.count(wallet.hotkey.ss58_address) > 0 - ): - console.print(":white_heavy_check_mark: [green]Vote cast.[/green]") - return True - else: - # hotkey not found in ayes/nays - err_console.print( - ":cross_mark: [red]Unknown error. Couldn't find vote.[/red]" - ) - return False - else: - return False - - -async def burned_register_extrinsic( - subtensor: SubtensorInterface, - wallet: Wallet, - netuid: int, - recycle_amount: Balance, - old_balance: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, - prompt: bool = False, -) -> bool: - """Registers the wallet to chain by recycling TAO. - - :param subtensor: The SubtensorInterface object to use for the call, initialized - :param wallet: Bittensor wallet object. - :param netuid: The `netuid` of the subnet to register on. - :param recycle_amount: The amount of TAO required for this burn. - :param old_balance: The wallet balance prior to the registration burn. - :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, - or returns `False` if the extrinsic fails to be finalized within the timeout. - :param prompt: If `True`, the call waits for confirmation from the user before proceeding. - - :return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for - finalization/inclusion, the response is `True`. - """ - - try: - wallet.unlock_coldkey() - except KeyFileError: - err_console.print("Error decrypting coldkey (possibly incorrect password)") - return False - - with console.status( - f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...", - spinner="aesthetic", - ) as status: - my_uid = await subtensor.substrate.query( - "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address] - ) - - print_verbose("Checking if already registered", status) - neuron = await subtensor.neuron_for_uid( - uid=my_uid, - netuid=netuid, - block_hash=subtensor.substrate.last_block_hash, - ) - - if not neuron.is_null: - console.print( - ":white_heavy_check_mark: [green]Already Registered[/green]:\n" - f"uid: [bold white]{neuron.uid}[/bold white]\n" - f"netuid: [bold white]{neuron.netuid}[/bold white]\n" - f"hotkey: [bold white]{neuron.hotkey}[/bold white]\n" - f"coldkey: [bold white]{neuron.coldkey}[/bold white]" - ) - return True - - with console.status( - ":satellite: Recycling TAO for Registration...", spinner="aesthetic" - ): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - success, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization - ) - - if not success: - err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") - await asyncio.sleep(0.5) - return False - # Successful registration, final check for neuron and pubkey - else: - with console.status(":satellite: Checking Balance...", spinner="aesthetic"): - block_hash = await subtensor.substrate.get_chain_head() - new_balance, netuids_for_hotkey, my_uid = await asyncio.gather( - subtensor.get_balance( - wallet.coldkeypub.ss58_address, - block_hash=block_hash, - reuse_block=False, - ), - subtensor.get_netuids_for_hotkey( - wallet.hotkey.ss58_address, block_hash=block_hash - ), - subtensor.substrate.query( - "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address] - ), - ) - - console.print( - "Balance:\n" - f" [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]" - ) - - if len(netuids_for_hotkey) > 0: - console.print( - f":white_heavy_check_mark: [green]Registered on netuid {netuid} with UID {my_uid}[/green]" - ) - return True - else: - # neuron not found, try again - err_console.print( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" - ) - return False - - -async def set_take_extrinsic( - subtensor: SubtensorInterface, - wallet: Wallet, - delegate_ss58: str, - take: float = 0.0, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, -) -> bool: - """ - Set delegate hotkey take - - :param subtensor: SubtensorInterface (initialized) - :param wallet: The wallet containing the hotkey to be nominated. - :param delegate_ss58: Hotkey - :param take: Delegate take on subnet ID - :param wait_for_finalization: If `True`, waits until the transaction is finalized on the - blockchain. - :param wait_for_inclusion: If `True`, waits until the transaction is included in a block. - - :return: `True` if the process is successful, `False` otherwise. - - This function is a key part of the decentralized governance mechanism of Bittensor, allowing for the - dynamic selection and participation of validators in the network's consensus process. - """ - - async def _get_delegate_by_hotkey(ss58: str) -> Optional[DelegateInfo]: - """Retrieves the delegate info for a given hotkey's ss58 address""" - encoded_hotkey = ss58_to_vec_u8(ss58) - json_body = await subtensor.substrate.rpc_request( - method="delegateInfo_getDelegate", # custom rpc method - params=([encoded_hotkey, subtensor.substrate.last_block_hash]), - ) - if not (result := json_body.get("result", None)): - return None - else: - return DelegateInfo.from_vec_u8(bytes(result)) - - # Calculate u16 representation of the take - take_u16 = int(take * 0xFFFF) - - print_verbose("Checking current take") - # Check if the new take is greater or lower than existing take or if existing is set - delegate = await _get_delegate_by_hotkey(delegate_ss58) - current_take = None - if delegate is not None: - current_take = int( - float(delegate.take) * 65535.0 - ) # TODO verify this, why not u16_float_to_int? - - if take_u16 == current_take: - console.print("Nothing to do, take hasn't changed") - return True - if current_take is None or current_take < take_u16: - console.print( - f"Current take is {float(delegate.take):.4f}. Increasing to {take:.4f}." - ) - with console.status( - f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..." - ): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": delegate_ss58, - "take": take_u16, - }, - ) - success, err = await subtensor.sign_and_send_extrinsic(call, wallet) - - else: - console.print( - f"Current take is {float(delegate.take):.4f}. Decreasing to {take:.4f}." - ) - with console.status( - f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..." - ): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": delegate_ss58, - "take": take_u16, - }, - ) - success, err = await subtensor.sign_and_send_extrinsic(call, wallet) - - if not success: - err_console.print(err) - else: - console.print(":white_heavy_check_mark: [green]Finalized[/green]") - return success - - -async def delegate_extrinsic( - subtensor: SubtensorInterface, - wallet: Wallet, - delegate_ss58: str, - amount: Optional[float], - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - prompt: bool = False, - delegate: bool = True, -) -> bool: - """Delegates the specified amount of stake to the passed delegate. - - :param subtensor: The SubtensorInterface used to perform the delegation, initialized. - :param wallet: Bittensor wallet object. - :param delegate_ss58: The `ss58` address of the delegate. - :param amount: Amount to stake as bittensor balance, None to stake all available TAO. - :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, - or returns `False` if the extrinsic fails to be finalized within the timeout. - :param prompt: If `True`, the call waits for confirmation from the user before proceeding. - :param delegate: whether to delegate (`True`) or undelegate (`False`) - - :return: `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, - the response is `True`. - """ - - async def _do_delegation(staking_balance_: Balance) -> tuple[bool, str]: - """Performs the delegation extrinsic call to the chain.""" - if delegate: - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": delegate_ss58, - "amount_staked": staking_balance_.rao, - }, - ) - else: - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": delegate_ss58, - "amount_unstaked": staking_balance_.rao, - }, - ) - return await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization - ) - - async def get_hotkey_owner(ss58: str, block_hash_: str): - """Returns the coldkey owner of the passed hotkey.""" - if not await subtensor.does_hotkey_exist(ss58, block_hash=block_hash_): - return None - _result = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="Owner", - params=[ss58], - block_hash=block_hash_, - ) - return decode_account_id(_result[0]) - - async def get_stake_for_coldkey_and_hotkey( - hotkey_ss58: str, coldkey_ss58: str, block_hash_: str - ): - """Returns the stake under a coldkey - hotkey pairing.""" - _result = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="Stake", - params=[hotkey_ss58, coldkey_ss58], - block_hash=block_hash_, - ) - return Balance.from_rao(_result or 0) - - delegate_string = "delegate" if delegate else "undelegate" - - # Decrypt key - try: - wallet.unlock_coldkey() - except KeyFileError: - err_console.print("Error decrypting coldkey (possibly incorrect password)") - return False - - print_verbose("Checking if hotkey is a delegate") - if not await subtensor.is_hotkey_delegate(delegate_ss58): - err_console.print(f"Hotkey: {delegate_ss58} is not a delegate.") - return False - - # Get state. - with console.status( - f":satellite: Syncing with [bold white]{subtensor}[/bold white] ...", - spinner="aesthetic", - ) as status: - print_verbose("Fetching balance, stake, and ownership", status) - initial_block_hash = await subtensor.substrate.get_chain_head() - ( - my_prev_coldkey_balance_, - delegate_owner, - my_prev_delegated_stake, - ) = await asyncio.gather( - subtensor.get_balance( - wallet.coldkey.ss58_address, block_hash=initial_block_hash - ), - get_hotkey_owner(delegate_ss58, block_hash_=initial_block_hash), - get_stake_for_coldkey_and_hotkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=delegate_ss58, - block_hash_=initial_block_hash, - ), - ) - - my_prev_coldkey_balance = my_prev_coldkey_balance_[wallet.coldkey.ss58_address] - - # Convert to bittensor.Balance - if amount is None: - # Stake it all. - if delegate_string == "delegate": - staking_balance = Balance.from_tao(my_prev_coldkey_balance.tao) - else: - # Unstake all - staking_balance = Balance.from_tao(my_prev_delegated_stake.tao) - else: - staking_balance = Balance.from_tao(amount) - - # Check enough balance to stake. - if delegate_string == "delegate" and staking_balance > my_prev_coldkey_balance: - err_console.print( - ":cross_mark: [red]Not enough balance to stake[/red]:\n" - f" [bold blue]current balance[/bold blue]:{my_prev_coldkey_balance}\n" - f" [bold red]amount staking[/bold red]: {staking_balance}\n" - f" [bold white]coldkey: {wallet.name}[/bold white]" - ) - return False - - if delegate_string == "undelegate" and ( - my_prev_delegated_stake is None or staking_balance > my_prev_delegated_stake - ): - err_console.print( - "\n:cross_mark: [red]Not enough balance to unstake[/red]:\n" - f" [bold blue]current stake[/bold blue]: {my_prev_delegated_stake}\n" - f" [bold red]amount unstaking[/bold red]: {staking_balance}\n" - f" [bold white]coldkey: {wallet.name}[bold white]\n\n" - ) - return False - - if delegate: - # Grab the existential deposit. - existential_deposit = await subtensor.get_existential_deposit() - - # Remove existential balance to keep key alive. - if staking_balance > my_prev_coldkey_balance - existential_deposit: - staking_balance = my_prev_coldkey_balance - existential_deposit - else: - staking_balance = staking_balance - - # Ask before moving on. - if prompt: - if not Confirm.ask( - f"\n[bold blue]Current stake[/bold blue]: [blue]{my_prev_delegated_stake}[/blue]\n" - f"[bold white]Do you want to {delegate_string}:[/bold white]\n" - f" [bold red]amount[/bold red]: [red]{staking_balance}\n[/red]" - f" [bold yellow]{'to' if delegate_string == 'delegate' else 'from'} hotkey[/bold yellow]: [yellow]{delegate_ss58}\n[/yellow]" - f" [bold green]hotkey owner[/bold green]: [green]{delegate_owner}[/green]" - ): - return False - - with console.status( - f":satellite: Staking to: [bold white]{subtensor}[/bold white] ...", - spinner="aesthetic", - ) as status: - print_verbose("Transmitting delegate operation call") - staking_response, err_msg = await _do_delegation(staking_balance) - - if staking_response is True: # If we successfully staked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True - - console.print(":white_heavy_check_mark: [green]Finalized[/green]\n") - with console.status( - f":satellite: Checking Balance on: [white]{subtensor}[/white] ...", - spinner="aesthetic", - ) as status: - print_verbose("Fetching balance and stakes", status) - block_hash = await subtensor.substrate.get_chain_head() - new_balance, new_delegate_stake = await asyncio.gather( - subtensor.get_balance( - wallet.coldkey.ss58_address, block_hash=block_hash - ), - get_stake_for_coldkey_and_hotkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=delegate_ss58, - block_hash_=block_hash, - ), - ) - - console.print( - "Balance:\n" - f" [blue]{my_prev_coldkey_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]\n" - "Stake:\n" - f" [blue]{my_prev_delegated_stake}[/blue] :arrow_right: [green]{new_delegate_stake}[/green]" - ) - return True - else: - err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") - return False - - -async def nominate_extrinsic( - subtensor: SubtensorInterface, - wallet: Wallet, - wait_for_finalization: bool = False, - wait_for_inclusion: bool = True, -) -> bool: - """Becomes a delegate for the hotkey. - - :param wallet: The unlocked wallet to become a delegate for. - :param subtensor: The SubtensorInterface to use for the transaction - :param wait_for_finalization: Wait for finalization or not - :param wait_for_inclusion: Wait for inclusion or not - - :return: success - """ - with console.status( - ":satellite: Sending nominate call on [white]{}[/white] ...".format( - subtensor.network - ) - ): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="become_delegate", - call_params={"hotkey": wallet.hotkey.ss58_address}, - ) - success, err_msg = await subtensor.sign_and_send_extrinsic( - call, - wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if success is True: - console.print(":white_heavy_check_mark: [green]Finalized[/green]") - - else: - err_console.print(f":cross_mark: [red]Failed[/red]: error:{err_msg}") - return success - - -# Commands - - -async def root_list(subtensor: SubtensorInterface): - """List the root network""" - - async def _get_list() -> tuple: - senate_query = await subtensor.substrate.query( - module="SenateMembers", - storage_function="Members", - params=None, - ) - sm = [decode_account_id(i[x][0]) for i in senate_query for x in range(len(i))] - - rn: list[NeuronInfoLite] = await subtensor.neurons_lite(netuid=0) - if not rn: - return [], [], {}, {} - - di: dict[str, DelegatesDetails] = await subtensor.get_delegate_identities() - ts: dict[str, ScaleType] = await subtensor.substrate.query_multiple( - [n.hotkey for n in rn], - module="SubtensorModule", - storage_function="TotalHotkeyStake", - reuse_block_hash=True, - ) - return sm, rn, di, ts - - with console.status( - f":satellite: Syncing with chain: [white]{subtensor}[/white] ...", - spinner="aesthetic", - ): - senate_members, root_neurons, delegate_info, total_stakes = await _get_list() - total_tao = sum( - float(Balance.from_rao(total_stakes[neuron.hotkey])) - for neuron in root_neurons - ) - - table = Table( - Column( - "[bold white]UID", - style="dark_orange", - no_wrap=True, - footer=f"[bold]{len(root_neurons)}[/bold]", - ), - Column( - "[bold white]NAME", - style="bright_cyan", - no_wrap=True, - ), - Column( - "[bold white]ADDRESS", - style="bright_magenta", - no_wrap=True, - ), - Column( - "[bold white]STAKE(\u03c4)", - justify="right", - style="light_goldenrod2", - no_wrap=True, - footer=f"{total_tao:.2f} (\u03c4) ", - ), - Column( - "[bold white]SENATOR", - style="dark_sea_green", - no_wrap=True, - ), - title=f"[underline dark_orange]Root Network[/underline dark_orange]\n[dark_orange]Network {subtensor.network}", - show_footer=True, - show_edge=False, - expand=False, - border_style="bright_black", - leading=True, - ) - - if not root_neurons: - err_console.print( - f"[red]Error: No neurons detected on the network:[/red] [white]{subtensor}" - ) - raise typer.Exit() - - sorted_root_neurons = sorted( - root_neurons, - key=lambda neuron: float(Balance.from_rao(total_stakes[neuron.hotkey])), - reverse=True, - ) - - for neuron_data in sorted_root_neurons: - table.add_row( - str(neuron_data.uid), - ( - delegate_info[neuron_data.hotkey].display - if neuron_data.hotkey in delegate_info - else "~" - ), - neuron_data.hotkey, - "{:.5f}".format(float(Balance.from_rao(total_stakes[neuron_data.hotkey]))), - "Yes" if neuron_data.hotkey in senate_members else "No", - ) - - return console.print(table) - - -async def set_weights( - wallet: Wallet, - subtensor: SubtensorInterface, - netuids: list[int], - weights: list[float], - prompt: bool, -): - """Set weights for root network.""" - netuids_ = np.array(netuids, dtype=np.int64) - weights_ = np.array(weights, dtype=np.float32) - console.print(f"Setting weights in [dark_orange]network: {subtensor.network}") - - # Run the set weights operation. - - await set_root_weights_extrinsic( - subtensor=subtensor, - wallet=wallet, - netuids=netuids_, - weights=weights_, - version_key=0, - prompt=prompt, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -async def get_weights( - subtensor: SubtensorInterface, - limit_min_col: Optional[int], - limit_max_col: Optional[int], - reuse_last: bool, - html_output: bool, - no_cache: bool, -): - """Get weights for root network.""" - if not reuse_last: - with console.status( - ":satellite: Fetching weights from chain...", spinner="aesthetic" - ): - weights = await subtensor.weights(0) - - uid_to_weights: dict[int, dict] = {} - netuids = set() - for matrix in weights: - [uid, weights_data] = matrix - - if not len(weights_data): - uid_to_weights[uid] = {} - normalized_weights = [] - else: - normalized_weights = np.array(weights_data)[:, 1] / max( - np.sum(weights_data, axis=0)[1], 1 - ) - - for weight_data, normalized_weight in zip(weights_data, normalized_weights): - [netuid, _] = weight_data - netuids.add(netuid) - if uid not in uid_to_weights: - uid_to_weights[uid] = {} - - uid_to_weights[uid][netuid] = normalized_weight - rows: list[list[str]] = [] - for uid in uid_to_weights: - row = [str(uid)] - - uid_weights = uid_to_weights[uid] - for netuid in netuids: - if netuid in uid_weights: - row.append("{:0.2f}%".format(uid_weights[netuid] * 100)) - else: - row.append("~") - rows.append(row) - - if not no_cache: - db_cols = [("UID", "INTEGER")] - for netuid in netuids: - db_cols.append((f"_{netuid}", "TEXT")) - create_table("rootgetweights", db_cols, rows) - netuids = list(netuids) - update_metadata_table( - "rootgetweights", - {"rows": json.dumps(rows), "netuids": json.dumps(netuids)}, - ) - else: - metadata = get_metadata_table("rootgetweights") - rows = json.loads(metadata["rows"]) - netuids = json.loads(metadata["netuids"]) - - _min_lim = limit_min_col if limit_min_col is not None else 0 - _max_lim = limit_max_col + 1 if limit_max_col is not None else len(netuids) - _max_lim = min(_max_lim, len(netuids)) - - if _min_lim is not None and _min_lim > len(netuids): - err_console.print("Minimum limit greater than number of netuids") - return - - if not html_output: - table = Table( - show_footer=True, - box=None, - pad_edge=False, - width=None, - title="[white]Root Network Weights", - ) - table.add_column( - "[white]UID", - header_style="overline white", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - netuids = list(netuids) - for netuid in netuids[_min_lim:_max_lim]: - table.add_column( - f"[white]{netuid}", - header_style="overline white", - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - - if not rows: - err_console.print("No weights exist on the root network.") - return - - # Adding rows - for row in rows: - new_row = [row[0]] + row[_min_lim + 1 : _max_lim + 1] - table.add_row(*new_row) - - return console.print(table) - - else: - html_cols = [{"title": "UID", "field": "UID"}] - for netuid in netuids[_min_lim:_max_lim]: - html_cols.append({"title": str(netuid), "field": f"_{netuid}"}) - render_table( - "rootgetweights", - "Root Network Weights", - html_cols, - ) - - -async def _get_my_weights( - subtensor: SubtensorInterface, ss58_address: str, my_uid: str -) -> NDArray[np.float32]: - """Retrieves the weight array for a given hotkey SS58 address.""" - - my_weights_, total_subnets_ = await asyncio.gather( - subtensor.substrate.query( - "SubtensorModule", "Weights", [0, my_uid], reuse_block_hash=True - ), - subtensor.substrate.query( - "SubtensorModule", "TotalNetworks", reuse_block_hash=True - ), - ) - # If setting weights for the first time, pass 0 root weights - my_weights: list[tuple[int, int]] = ( - my_weights_ if my_weights_ is not None else [(0, 0)] - ) - total_subnets: int = total_subnets_ - - print_verbose("Fetching current weights") - for _, w in enumerate(my_weights): - if w: - print_verbose(f"{w}") - - uids, values = zip(*my_weights) - weight_array = convert_weight_uids_and_vals_to_tensor(total_subnets, uids, values) - return weight_array - - -async def set_boost( - wallet: Wallet, - subtensor: SubtensorInterface, - netuid: int, - amount: float, - prompt: bool, -): - """Boosts weight of a given netuid for root network.""" - console.print(f"Boosting weights in [dark_orange]network: {subtensor.network}") - print_verbose(f"Fetching uid of hotkey on root: {wallet.hotkey_str}") - my_uid = await subtensor.substrate.query( - "SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address] - ) - - if my_uid is None: - err_console.print("Your hotkey is not registered to the root network") - return False - - print_verbose("Fetching current weights") - my_weights = await _get_my_weights(subtensor, wallet.hotkey.ss58_address, my_uid) - prev_weights = my_weights.copy() - my_weights[netuid] += amount - all_netuids = np.arange(len(my_weights)) - - console.print( - f"Boosting weight for netuid {netuid}\n\tfrom {prev_weights[netuid]} to {my_weights[netuid]}\n" - ) - console.print( - f"Previous weights -> Raw weights: \n\t{prev_weights} -> \n\t{my_weights}" - ) - - print_verbose(f"All netuids: {all_netuids}") - await set_root_weights_extrinsic( - subtensor=subtensor, - wallet=wallet, - netuids=all_netuids, - weights=my_weights, - version_key=0, - wait_for_inclusion=True, - wait_for_finalization=True, - prompt=prompt, - ) - - -async def set_slash( - wallet: Wallet, - subtensor: SubtensorInterface, - netuid: int, - amount: float, - prompt: bool, -): - """Slashes weight""" - console.print(f"Slashing weights in [dark_orange]network: {subtensor.network}") - print_verbose(f"Fetching uid of hotkey on root: {wallet.hotkey_str}") - my_uid = await subtensor.substrate.query( - "SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address] - ) - if my_uid is None: - err_console.print("Your hotkey is not registered to the root network") - return False - - print_verbose("Fetching current weights") - my_weights = await _get_my_weights(subtensor, wallet.hotkey.ss58_address, my_uid) - prev_weights = my_weights.copy() - my_weights[netuid] -= amount - my_weights[my_weights < 0] = 0 # Ensure weights don't go negative - all_netuids = np.arange(len(my_weights)) - - console.print( - f"Slashing weight for netuid {netuid}\n\tfrom {prev_weights[netuid]} to {my_weights[netuid]}\n" - ) - console.print( - f"Previous weights -> Raw weights: \n\t{prev_weights} -> \n\t{my_weights}" - ) - - await set_root_weights_extrinsic( - subtensor=subtensor, - wallet=wallet, - netuids=all_netuids, - weights=my_weights, - version_key=0, - wait_for_inclusion=True, - wait_for_finalization=True, - prompt=prompt, - ) - - -async def senate_vote( - wallet: Wallet, - subtensor: SubtensorInterface, - proposal_hash: str, - vote: bool, - prompt: bool, -) -> bool: - """Vote in Bittensor's governance protocol proposals""" - - if not proposal_hash: - err_console.print( - "Aborting: Proposal hash not specified. View all proposals with the `proposals` command." - ) - return False - elif not _validate_proposal_hash(proposal_hash): - err_console.print( - "Aborting. Proposal hash is invalid. Proposal hashes should start with '0x' and be 32 bytes long" - ) - return False - - print_verbose(f"Fetching senate status of {wallet.hotkey_str}") - if not await _is_senate_member(subtensor, hotkey_ss58=wallet.hotkey.ss58_address): - err_console.print( - f"Aborting: Hotkey {wallet.hotkey.ss58_address} isn't a senate member." - ) - return False - - # Unlock the wallet. - try: - wallet.unlock_hotkey() - wallet.unlock_coldkey() - except KeyFileError: - err_console.print("Error decrypting coldkey (possibly incorrect password)") - return False - - console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}") - vote_data = await subtensor.get_vote_data(proposal_hash, reuse_block=True) - if not vote_data: - err_console.print(":cross_mark: [red]Failed[/red]: Proposal not found.") - return False - - success = await vote_senate_extrinsic( - subtensor=subtensor, - wallet=wallet, - proposal_hash=proposal_hash, - proposal_idx=vote_data.index, - vote=vote, - wait_for_inclusion=True, - wait_for_finalization=False, - prompt=prompt, - ) - - return success - - -async def get_senate(subtensor: SubtensorInterface): - """View Bittensor's governance protocol proposals""" - with console.status( - f":satellite: Syncing with chain: [white]{subtensor}[/white] ...", - spinner="aesthetic", - ) as status: - print_verbose("Fetching senate members", status) - senate_members = await _get_senate_members(subtensor) - - print_verbose("Fetching member details from Github") - delegate_info: dict[ - str, DelegatesDetails - ] = await subtensor.get_delegate_identities() - - table = Table( - Column( - "[bold white]NAME", - style="bright_cyan", - no_wrap=True, - ), - Column( - "[bold white]ADDRESS", - style="bright_magenta", - no_wrap=True, - ), - title=f"[underline dark_orange]Senate[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n", - show_footer=True, - show_edge=False, - expand=False, - border_style="bright_black", - leading=True, - ) - - for ss58_address in senate_members: - table.add_row( - ( - delegate_info[ss58_address].display - if ss58_address in delegate_info - else "~" - ), - ss58_address, - ) - - return console.print(table) - - -async def register(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool): - """Register neuron by recycling some TAO.""" - - console.print( - f"Registering on [dark_orange]netuid 0[/dark_orange] on network: [dark_orange]{subtensor.network}" - ) - - # Check current recycle amount - print_verbose("Fetching recycle amount & balance") - recycle_call, balance_ = await asyncio.gather( - subtensor.get_hyperparameter(param_name="Burn", netuid=0, reuse_block=True), - subtensor.get_balance(wallet.coldkeypub.ss58_address, reuse_block=True), - ) - current_recycle = Balance.from_rao(int(recycle_call)) - try: - balance: Balance = balance_[wallet.coldkeypub.ss58_address] - except TypeError as e: - err_console.print(f"Unable to retrieve current recycle. {e}") - return False - except KeyError: - err_console.print("Unable to retrieve current balance.") - return False - - # Check balance is sufficient - if balance < current_recycle: - err_console.print( - f"[red]Insufficient balance {balance} to register neuron. " - f"Current recycle is {current_recycle} TAO[/red]" - ) - return False - - if prompt: - if not Confirm.ask( - f"Your balance is: [bold green]{balance}[/bold green]\n" - f"The cost to register by recycle is [bold red]{current_recycle}[/bold red]\n" - f"Do you want to continue?", - default=False, - ): - return False - - await root_register_extrinsic( - subtensor, - wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - prompt=prompt, - ) - - -async def proposals(subtensor: SubtensorInterface): - console.print( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - subtensor.network - ) - ) - print_verbose("Fetching senate members & proposals") - block_hash = await subtensor.substrate.get_chain_head() - senate_members, all_proposals = await asyncio.gather( - _get_senate_members(subtensor, block_hash), - _get_proposals(subtensor, block_hash), - ) - - print_verbose("Fetching member information from Chain") - registered_delegate_info: dict[ - str, DelegatesDetails - ] = await subtensor.get_delegate_identities() - - table = Table( - Column( - "[white]HASH", - style="light_goldenrod2", - no_wrap=True, - ), - Column("[white]THRESHOLD", style="rgb(42,161,152)"), - Column("[white]AYES", style="green"), - Column("[white]NAYS", style="red"), - Column( - "[white]VOTES", - style="rgb(50,163,219)", - ), - Column("[white]END", style="bright_cyan"), - Column("[white]CALLDATA", style="dark_sea_green"), - title=f"\n[dark_orange]Proposals\t\t\nActive Proposals: {len(all_proposals)}\t\tSenate Size: {len(senate_members)}\nNetwork: {subtensor.network}", - show_footer=True, - box=box.SIMPLE_HEAVY, - pad_edge=False, - width=None, - border_style="bright_black", - ) - for hash_, (call_data, vote_data) in all_proposals.items(): - table.add_row( - hash_, - str(vote_data.threshold), - str(len(vote_data.ayes)), - str(len(vote_data.nays)), - display_votes(vote_data, registered_delegate_info), - str(vote_data.end), - format_call_data(call_data), - ) - return console.print(table) - - -async def set_take(wallet: Wallet, subtensor: SubtensorInterface, take: float) -> bool: - """Set delegate take.""" - - async def _do_set_take() -> bool: - """ - Just more easily allows an early return and to close the substrate interface after the logic - """ - print_verbose("Checking if hotkey is a delegate") - # Check if the hotkey is not a delegate. - if not await subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address): - err_console.print( - f"Aborting: Hotkey {wallet.hotkey.ss58_address} is NOT a delegate." - ) - return False - - if take > 0.18 or take < 0: - err_console.print("ERROR: Take value should not exceed 18% or be below 0%") - return False - - result: bool = await set_take_extrinsic( - subtensor=subtensor, - wallet=wallet, - delegate_ss58=wallet.hotkey.ss58_address, - take=take, - ) - - if not result: - err_console.print("Could not set the take") - return False - else: - # Check if we are a delegate. - is_delegate: bool = await subtensor.is_hotkey_delegate( - wallet.hotkey.ss58_address - ) - if not is_delegate: - err_console.print( - "Could not set the take [white]{}[/white]".format(subtensor.network) - ) - return False - else: - console.print( - "Successfully set the take on [white]{}[/white]".format( - subtensor.network - ) - ) - return True - - console.print(f"Setting take on [dark_orange]network: {subtensor.network}") - # Unlock the wallet. - try: - wallet.unlock_hotkey() - wallet.unlock_coldkey() - except KeyFileError: - err_console.print("Error decrypting coldkey (possibly incorrect password)") - return False - - result_ = await _do_set_take() - - return result_ - - -async def delegate_stake( - wallet: Wallet, - subtensor: SubtensorInterface, - amount: Optional[float], - delegate_ss58key: str, - prompt: bool, -): - """Delegates stake to a chain delegate.""" - console.print(f"Delegating stake on [dark_orange]network: {subtensor.network}") - await delegate_extrinsic( - subtensor, - wallet, - delegate_ss58key, - amount, - wait_for_inclusion=True, - prompt=prompt, - delegate=True, - ) - - -async def delegate_unstake( - wallet: Wallet, - subtensor: SubtensorInterface, - amount: Optional[float], - delegate_ss58key: str, - prompt: bool, -): - """Undelegates stake from a chain delegate.""" - console.print(f"Undelegating stake on [dark_orange]network: {subtensor.network}") - await delegate_extrinsic( - subtensor, - wallet, - delegate_ss58key, - amount, - wait_for_inclusion=True, - prompt=prompt, - delegate=False, - ) - - -async def my_delegates( - wallet: Wallet, subtensor: SubtensorInterface, all_wallets: bool -): - """Delegates stake to a chain delegate.""" - - async def wallet_to_delegates( - w: Wallet, bh: str - ) -> tuple[Optional[Wallet], Optional[list[tuple[DelegateInfo, Balance]]]]: - """Helper function to retrieve the validity of the wallet (if it has a coldkeypub on the device) - and its delegate info.""" - if not w.coldkeypub_file.exists_on_device(): - return None, None - else: - delegates_ = await subtensor.get_delegated( - w.coldkeypub.ss58_address, block_hash=bh - ) - return w, delegates_ - - wallets = get_coldkey_wallets_for_path(wallet.path) if all_wallets else [wallet] - - table = Table( - Column("[white]Wallet", style="bright_cyan"), - Column( - "[white]OWNER", - style="bold bright_cyan", - overflow="fold", - justify="left", - ratio=1, - ), - Column( - "[white]SS58", - style="bright_magenta", - justify="left", - overflow="fold", - ratio=3, - ), - Column("[white]Delegation", style="dark_orange", no_wrap=True, ratio=1), - Column("[white]\u03c4/24h", style="bold green", ratio=1), - Column( - "[white]NOMS", - justify="center", - style="rgb(42,161,152)", - no_wrap=True, - ratio=1, - ), - Column( - "[white]OWNER STAKE(\u03c4)", - justify="right", - style="light_goldenrod2", - no_wrap=True, - ratio=1, - ), - Column( - "[white]TOTAL STAKE(\u03c4)", - justify="right", - style="light_goldenrod2", - no_wrap=True, - ratio=1, - ), - Column("[white]SUBNETS", justify="right", style="white", ratio=1), - Column("[white]VPERMIT", justify="right"), - Column( - "[white]24h/k\u03c4", style="rgb(42,161,152)", justify="center", ratio=1 - ), - Column("[white]Desc", style="rgb(50,163,219)", ratio=3), - title=f"[underline dark_orange]My Delegates[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n", - show_footer=True, - show_edge=False, - expand=False, - box=box.SIMPLE_HEAVY, - border_style="bright_black", - leading=True, - ) - - total_delegated = 0 - - # TODO: this doesnt work when passed to wallets_with_delegates - # block_hash = await subtensor.substrate.get_chain_head() - - registered_delegate_info: dict[str, DelegatesDetails] - wallets_with_delegates: tuple[ - tuple[Optional[Wallet], Optional[list[tuple[DelegateInfo, Balance]]]] - ] - - print_verbose("Fetching delegate information") - wallets_with_delegates, registered_delegate_info = await asyncio.gather( - asyncio.gather(*[wallet_to_delegates(wallet_, None) for wallet_ in wallets]), - subtensor.get_delegate_identities(), - ) - if not registered_delegate_info: - console.print( - ":warning:[yellow]Could not get delegate info from chain.[/yellow]" - ) - - print_verbose("Processing delegate information") - for wall, delegates in wallets_with_delegates: - if not wall or not delegates: - continue - - my_delegates_ = {} # hotkey, amount - for delegate in delegates: - for coldkey_addr, staked in delegate[0].nominators: - if coldkey_addr == wall.coldkeypub.ss58_address and staked.tao > 0: - my_delegates_[delegate[0].hotkey_ss58] = staked - - delegates.sort(key=lambda d: d[0].total_stake, reverse=True) - total_delegated += sum(my_delegates_.values()) - - for i, delegate in enumerate(delegates): - owner_stake = next( - ( - stake - for owner, stake in delegate[0].nominators - if owner == delegate[0].owner_ss58 - ), - Balance.from_rao(0), # default to 0 if no owner stake. - ) - if delegate[0].hotkey_ss58 in registered_delegate_info: - delegate_name = registered_delegate_info[ - delegate[0].hotkey_ss58 - ].display - delegate_url = registered_delegate_info[delegate[0].hotkey_ss58].web - delegate_description = registered_delegate_info[ - delegate[0].hotkey_ss58 - ].additional - else: - delegate_name = "~" - delegate_url = "" - delegate_description = "" - - if delegate[0].hotkey_ss58 in my_delegates_: - twenty_four_hour = delegate[0].total_daily_return.tao * ( - my_delegates_[delegate[0].hotkey_ss58] / delegate[0].total_stake.tao - ) - table.add_row( - wall.name, - Text(delegate_name, style=f"link {delegate_url}"), - f"{delegate[0].hotkey_ss58}", - f"{my_delegates_[delegate[0].hotkey_ss58]!s:13.13}", - f"{twenty_four_hour!s:6.6}", - str(len(delegate[0].nominators)), - f"{owner_stake!s:13.13}", - f"{delegate[0].total_stake!s:13.13}", - group_subnets(delegate[0].registrations), - group_subnets(delegate[0].validator_permits), - f"{delegate[0].total_daily_return.tao * (1000 / (0.001 + delegate[0].total_stake.tao))!s:6.6}", - str(delegate_description), - ) - if console.width < 150: - console.print( - "[yellow]Warning: Your terminal width might be too small to view all the information clearly" - ) - console.print(table) - console.print(f"Total delegated TAO: {total_delegated}") - - -async def list_delegates(subtensor: SubtensorInterface): - """List all delegates on the network.""" - - with console.status( - ":satellite: Loading delegates...", spinner="aesthetic" - ) as status: - print_verbose("Fetching delegate details from chain", status) - block_hash = await subtensor.substrate.get_chain_head() - registered_delegate_info, block_number, delegates = await asyncio.gather( - subtensor.get_delegate_identities(block_hash=block_hash), - subtensor.substrate.get_block_number(block_hash), - subtensor.get_delegates(block_hash=block_hash), - ) - - print_verbose("Fetching previous delegates info from chain", status) - - async def get_prev_delegates(fallback_offsets=(1200, 200)): - for offset in fallback_offsets: - try: - prev_block_hash = await subtensor.substrate.get_block_hash( - max(0, block_number - offset) - ) - return await subtensor.get_delegates(block_hash=prev_block_hash) - except SubstrateRequestException: - continue - return None - - prev_delegates = await get_prev_delegates() - - if prev_delegates is None: - err_console.print( - ":warning: [yellow]Could not fetch delegates history. [/yellow]" - ) - - delegates.sort(key=lambda d: d.total_stake, reverse=True) - prev_delegates_dict = {} - if prev_delegates is not None: - for prev_delegate in prev_delegates: - prev_delegates_dict[prev_delegate.hotkey_ss58] = prev_delegate - - if not registered_delegate_info: - console.print( - ":warning:[yellow]Could not get delegate info from chain.[/yellow]" - ) - table = Table( - Column( - "[white]INDEX\n\n", - str(len(delegates)), - style="bold white", - ), - Column( - "[white]DELEGATE\n\n", - style="bold bright_cyan", - justify="left", - overflow="fold", - ratio=1, - ), - Column( - "[white]SS58\n\n", - style="bright_magenta", - no_wrap=False, - overflow="fold", - ratio=2, - ), - Column( - "[white]NOMINATORS\n\n", - justify="center", - style="gold1", - no_wrap=True, - ratio=1, - ), - Column( - "[white]OWN STAKE\n(\u03c4)\n", - justify="right", - style="orange1", - no_wrap=True, - ratio=1, - ), - Column( - "[white]TOTAL STAKE\n(\u03c4)\n", - justify="right", - style="light_goldenrod2", - no_wrap=True, - ratio=1, - ), - Column("[white]CHANGE\n/(4h)\n", style="grey0", justify="center", ratio=1), - Column("[white]TAKE\n\n", style="white", no_wrap=True, ratio=1), - Column( - "[white]NOMINATOR\n/(24h)/k\u03c4\n", - style="dark_olive_green3", - justify="center", - ratio=1, - ), - Column( - "[white]DELEGATE\n/(24h)\n", - style="dark_olive_green3", - justify="center", - ratio=1, - ), - Column( - "[white]VPERMIT\n\n", - justify="center", - no_wrap=False, - max_width=20, - style="dark_sea_green", - ratio=2, - ), - Column("[white]Desc\n\n", style="rgb(50,163,219)", max_width=30, ratio=2), - title=f"[underline dark_orange]Root Delegates[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n", - show_footer=True, - pad_edge=False, - box=None, - ) - - for i, delegate in enumerate(delegates): - owner_stake = next( - ( - stake - for owner, stake in delegate.nominators - if owner == delegate.owner_ss58 - ), - Balance.from_rao(0), # default to 0 if no owner stake. - ) - if delegate.hotkey_ss58 in registered_delegate_info: - delegate_name = registered_delegate_info[delegate.hotkey_ss58].display - delegate_url = registered_delegate_info[delegate.hotkey_ss58].web - delegate_description = registered_delegate_info[ - delegate.hotkey_ss58 - ].additional - else: - delegate_name = "~" - delegate_url = "" - delegate_description = "" - - if delegate.hotkey_ss58 in prev_delegates_dict: - prev_stake = prev_delegates_dict[delegate.hotkey_ss58].total_stake - if prev_stake == 0: - if delegate.total_stake > 0: - rate_change_in_stake_str = "[green]100%[/green]" - else: - rate_change_in_stake_str = "[grey0]0%[/grey0]" - else: - rate_change_in_stake = ( - 100 - * (float(delegate.total_stake) - float(prev_stake)) - / float(prev_stake) - ) - if rate_change_in_stake > 0: - rate_change_in_stake_str = "[green]{:.2f}%[/green]".format( - rate_change_in_stake - ) - elif rate_change_in_stake < 0: - rate_change_in_stake_str = "[red]{:.2f}%[/red]".format( - rate_change_in_stake - ) - else: - rate_change_in_stake_str = "[grey0]0%[/grey0]" - else: - rate_change_in_stake_str = "[grey0]NA[/grey0]" - table.add_row( - # INDEX - str(i), - # DELEGATE - Text(delegate_name, style=f"link {delegate_url}"), - # SS58 - f"{delegate.hotkey_ss58}", - # NOMINATORS - str(len([nom for nom in delegate.nominators if nom[1].rao > 0])), - # DELEGATE STAKE - f"{owner_stake!s:13.13}", - # TOTAL STAKE - f"{delegate.total_stake!s:13.13}", - # CHANGE/(4h) - rate_change_in_stake_str, - # TAKE - f"{delegate.take * 100:.1f}%", - # NOMINATOR/(24h)/k - f"{Balance.from_tao(delegate.total_daily_return.tao * (1000 / (0.001 + delegate.total_stake.tao)))!s:6.6}", - # DELEGATE/(24h) - f"{Balance.from_tao(delegate.total_daily_return.tao * 0.18) !s:6.6}", - # VPERMIT - str(group_subnets(delegate.registrations)), - # Desc - str(delegate_description), - end_section=True, - ) - console.print(table) - - -async def nominate(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool): - """Nominate wallet.""" - - console.print(f"Nominating on [dark_orange]network: {subtensor.network}") - # Unlock the wallet. - try: - wallet.unlock_hotkey() - wallet.unlock_coldkey() - except KeyFileError: - err_console.print("Error decrypting coldkey (possibly incorrect password)") - return False - - print_verbose(f"Checking hotkey ({wallet.hotkey_str}) is a delegate") - # Check if the hotkey is already a delegate. - if await subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address): - err_console.print( - f"Aborting: Hotkey {wallet.hotkey.ss58_address} is already a delegate." - ) - return - - print_verbose("Nominating hotkey as a delegate") - result: bool = await nominate_extrinsic(subtensor, wallet) - if not result: - err_console.print( - f"Could not became a delegate on [white]{subtensor.network}[/white]" - ) - return - else: - # Check if we are a delegate. - print_verbose("Confirming delegate status") - is_delegate: bool = await subtensor.is_hotkey_delegate( - wallet.hotkey.ss58_address - ) - if not is_delegate: - err_console.print( - f"Could not became a delegate on [white]{subtensor.network}[/white]" - ) - return - console.print( - f"Successfully became a delegate on [white]{subtensor.network}[/white]" - ) - - # Prompt use to set identity on chain. - if prompt: - do_set_identity = Confirm.ask("Would you like to set your identity? [y/n]") - - if do_set_identity: - id_prompts = set_id_prompts(validator=True) - await set_id(wallet, subtensor, *id_prompts, prompt=prompt) From 1215d67fc18488b9b3b01edee2122aa44abab866 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 17 Oct 2024 17:29:24 +0200 Subject: [PATCH 025/157] subnets list --- bittensor_cli/cli.py | 28 +-- bittensor_cli/src/commands/subnets.py | 294 +++++++++----------------- 2 files changed, 115 insertions(+), 207 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 8566a180..11e72806 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3034,8 +3034,8 @@ def sudo_get( def subnets_list( self, network: Optional[list[str]] = Options.network, - reuse_last: bool = Options.reuse_last, - html_output: bool = Options.html_output, + # reuse_last: bool = Options.reuse_last, + # html_output: bool = Options.html_output, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -3058,21 +3058,21 @@ def subnets_list( [green]$[/green] btcli subnets list """ self.verbosity_handler(quiet, verbose) - if (reuse_last or html_output) and self.config.get("use_cache") is False: - err_console.print( - "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. " - "Change the config to 'False' using `btcli config set`." - ) - raise typer.Exit() - if reuse_last: - subtensor = None - else: - subtensor = self.initialize_chain(network) + # if (reuse_last or html_output) and self.config.get("use_cache") is False: + # err_console.print( + # "Unable to use `--reuse-last` or `--html` when config 'no-cache' is set to 'True'. " + # "Change the config to 'False' using `btcli config set`." + # ) + # raise typer.Exit() + # if reuse_last: + # subtensor = None + # else: + subtensor = self.initialize_chain(network) return self._run_command( subnets.subnets_list( subtensor, - reuse_last, - html_output, + False, # reuse-last + False, # html-output not self.config.get("use_cache", True), ) ) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 3e934982..93e6390d 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -153,204 +153,112 @@ async def subnets_list( subtensor: "SubtensorInterface", reuse_last: bool, html_output: bool, no_cache: bool ): """List all subnet netuids in the network.""" - - async def _get_all_subnets_info(): - hex_bytes_result = await subtensor.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", method="get_subnets_info", params=[] - ) - try: - bytes_result = bytes.fromhex(hex_bytes_result[2:]) - except ValueError: - bytes_result = bytes.fromhex(hex_bytes_result) - - return SubnetInfo.list_from_vec_u8(bytes_result) - - if not reuse_last: - subnets: list[SubnetInfo] - delegate_info: dict[str, DelegatesDetails] - - print_verbose("Fetching subnet and delegate information") - subnets, delegate_info = await asyncio.gather( - _get_all_subnets_info(), - subtensor.get_delegate_identities(), - ) - - if not subnets: - err_console.print("[red]No subnets found[/red]") - return - - rows = [] - db_rows = [] - total_neurons = 0 - max_neurons = 0 - - for subnet in subnets: - total_neurons += subnet.subnetwork_n - max_neurons += subnet.max_n - rows.append( - ( - str(subnet.netuid), - str(subnet.subnetwork_n), - str(millify(subnet.max_n)), - f"{subnet.emission_value / RAO_PER_TAO * 100:0.2f}%", - str(subnet.tempo), - f"{subnet.burn!s:8.8}", - str(millify(subnet.difficulty)), - str( - delegate_info[subnet.owner_ss58].display - if subnet.owner_ss58 in delegate_info - else subnet.owner_ss58 - ), - ) - ) - db_rows.append( - [ - int(subnet.netuid), - int(subnet.subnetwork_n), - int(subnet.max_n), # millified in HTML table - float( - subnet.emission_value / RAO_PER_TAO * 100 - ), # shown as percentage in HTML table - int(subnet.tempo), - float(subnet.burn), - int(subnet.difficulty), # millified in HTML table - str( - delegate_info[subnet.owner_ss58].display - if subnet.owner_ss58 in delegate_info - else subnet.owner_ss58 - ), - ] + # TODO add reuse-last and html-output and no-cache + + # Initialize variables to store aggregated data + rows = [] + subnets = await subtensor.get_all_subnet_dynamic_info() + for subnet in subnets: + symbol = f"{subnet.symbol}\u200e" + rows.append( + ( + str(subnet.netuid), + f"[light_goldenrod1]{subnet.symbol}[light_goldenrod1]", + f"τ {subnet.emission.tao:.4f}", + # f"P( τ {subnet.tao_in.tao:,.4f},", + f"τ {subnet.tao_in.tao:,.4f}", + # f"{subnet.alpha_in.tao:,.4f} {subnet.symbol} )", + f"{subnet.alpha_out.tao:,.4f} {symbol}", + f"{subnet.price.tao:.4f} τ/{symbol}", + str(subnet.blocks_since_last_step) + "/" + str(subnet.tempo), + # f"{subnet.owner_locked}" + "/" + f"{subnet.total_locked}", + # f"{subnet.owner[:3]}...{subnet.owner[-3:]}", ) - metadata = { - "network": subtensor.network, - "netuid_count": len(subnets), - "N": total_neurons, - "MAX_N": max_neurons, - "rows": json.dumps(rows), - } - if not no_cache: - create_table( - "subnetslist", - [ - ("NETUID", "INTEGER"), - ("N", "INTEGER"), - ("MAX_N", "BLOB"), - ("EMISSION", "REAL"), - ("TEMPO", "INTEGER"), - ("RECYCLE", "REAL"), - ("DIFFICULTY", "BLOB"), - ("SUDO", "TEXT"), - ], - db_rows, - ) - update_metadata_table("subnetslist", values=metadata) - else: - try: - metadata = get_metadata_table("subnetslist") - rows = json.loads(metadata["rows"]) - except sqlite3.OperationalError: - err_console.print( - "[red]Error[/red] Unable to retrieve table data. This is usually caused by attempting to use " - "`--reuse-last` before running the command a first time. In rare cases, this could also be due to " - "a corrupted database. Re-run the command (do not use `--reuse-last`) and see if that resolves your " - "issue." - ) - return - if not html_output: - table = Table( - title=f"[underline dark_orange]Subnets[/underline dark_orange]\n[dark_orange]Network: {metadata['network']}[/dark_orange]\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, ) - table.add_column( - "[bold white]NETUID", - footer=f"[white]{metadata['netuid_count']}[/white]", - style="white", - justify="center", - ) - table.add_column( - "[bold white]N", - footer=f"[white]{metadata['N']}[/white]", - style="bright_cyan", - justify="right", - ) - table.add_column( - "[bold white]MAX_N", - footer=f"[white]{metadata['MAX_N']}[/white]", - style="bright_cyan", - justify="right", - ) - table.add_column( - "[bold white]EMISSION", style="light_goldenrod2", justify="right" - ) - table.add_column("[bold white]TEMPO", style="rgb(42,161,152)", justify="right") - table.add_column("[bold white]RECYCLE", style="light_salmon3", justify="right") - table.add_column("[bold white]POW", style="medium_purple", justify="right") - table.add_column( - "[bold white]SUDO", style="bright_magenta", justify="right", overflow="fold" - ) + # Define table properties + console_width = console.width - 5 + table = Table( + title="Subnet Info", + width=console_width, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + table.title = f"[white]Subnets - {subtensor.network}\n" + + # Add columns to the table + # price_total = f"τ{total_price.tao:.2f}/{bt.Balance.from_rao(dynamic_emission).tao:.2f}" + # above_price_threshold = total_price.tao > bt.Balance.from_rao(dynamic_emission).tao + + table.add_column("Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center") + table.add_column("Symbol", style="rgb(211,54,130)", no_wrap=True, justify="center") + table.add_column( + f"Emission ({Balance.get_unit(0)})", + style="rgb(38,139,210)", + no_wrap=True, + justify="right", + ) + table.add_column( + f"TAO({Balance.get_unit(0)})", + style="medium_purple", + no_wrap=True, + justify="right", + ) + # table.add_column(f"{bt.Balance.get_unit(1)})", style="rgb(42,161,152)", no_wrap=True, justify="left") + table.add_column( + f"Stake({Balance.get_unit(1)})", style="green", no_wrap=True, justify="right" + ) + table.add_column( + f"Rate ({Balance.get_unit(1)}/{Balance.get_unit(0)})", + style="light_goldenrod2", + no_wrap=True, + justify="center", + ) + table.add_column( + "Tempo (k/n)", style="light_salmon3", no_wrap=True, justify="center" + ) + # table.add_column(f"Locked ({bt.Balance.get_unit(1)})", style="rgb(38,139,210)", no_wrap=True, justify="center") + # table.add_column("Owner", style="rgb(38,139,210)", no_wrap=True, justify="center") - for row in rows: - table.add_row(*row) + # Sort rows by subnet.emission.tao, keeping the first subnet in the first position + sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) - console.print(table) - console.print( - dedent( - """ - Description: - The table displays the list of subnets registered in the Bittensor network. - - NETUID: The network identifier of the subnet. - - N: The current UIDs registered to the network. - - MAX_N: The total UIDs allowed on the network. - - EMISSION: The emission accrued by this subnet in the network. - - TEMPO: A duration of a number of blocks. Several subnet events occur at the end of every tempo period. - - RECYCLE: Cost to register to the subnet. - - POW: Proof of work metric of the subnet. - - SUDO: Owner's identity. - """ - ) - ) - else: - render_table( - "subnetslist", - f"Subnets List | Network: {metadata['network']} - " - f"Netuids: {metadata['netuid_count']} - N: {metadata['N']}", - columns=[ - {"title": "NetUID", "field": "NETUID"}, - {"title": "N", "field": "N"}, - {"title": "MAX_N", "field": "MAX_N", "customFormatter": "millify"}, - { - "title": "EMISSION", - "field": "EMISSION", - "formatter": "money", - "formatterParams": { - "symbolAfter": "p", - "symbol": "%", - "precision": 2, - }, - }, - {"title": "Tempo", "field": "TEMPO"}, - { - "title": "Recycle", - "field": "RECYCLE", - "formatter": "money", - "formatterParams": {"symbol": "τ", "precision": 5}, - }, - { - "title": "Difficulty", - "field": "DIFFICULTY", - "customFormatter": "millify", - }, - {"title": "sudo", "field": "SUDO"}, - ], - ) + # Add rows to the table + for row in sorted_rows: + table.add_row(*row) + + # Print the table + console.print(table) + console.print( + """ +[bold white]Description[/bold white]: +The table displays relevant information about each subnet on the network. +The columns are as follows: + - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). + - [bold white]Symbol[/bold white]: The symbol representing the subnet's stake. + - [bold white]Emission[/bold white]: The amount of TAO added to the subnet every block. Calculated by dividing the TAO (t) column values by the sum of the TAO (t) column. + - [bold white]TAO[/bold white]: The TAO staked into the subnet ( which dynamically changes during stake, unstake and emission events ). + - [bold white]Stake[/bold white]: The outstanding supply of stake across all staking accounts on this subnet. + - [bold white]Rate[/bold white]: The rate of conversion between TAO and the subnet's staking unit. + - [bold white]Tempo[/bold white]: The number of blocks between epochs. Represented as (k/n) where k is the blocks since the last epoch and n is the total blocks in the epoch. +""" + ) async def lock_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: From bc0be72da7083b4b87f46972b8a6c262e5924aa5 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Oct 2024 10:49:31 +0200 Subject: [PATCH 026/157] subnets register --- .../src/bittensor/extrinsics/registration.py | 112 ++++++++++++++++++ bittensor_cli/src/commands/subnets.py | 60 +++++++++- 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index af696210..d1a0629c 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -29,6 +29,7 @@ from substrateinterface.exceptions import SubstrateRequestException from bittensor_cli.src.bittensor.chain_data import NeuronInfo +from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.utils import ( console, err_console, @@ -674,6 +675,117 @@ async def get_neuron_for_pubkey_and_subnet(): return False +async def burned_register_extrinsic( + subtensor: SubtensorInterface, + wallet: Wallet, + netuid: int, + old_balance: Balance, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + prompt: bool = False, +) -> bool: + """Registers the wallet to chain by recycling TAO. + + :param subtensor: The SubtensorInterface object to use for the call, initialized + :param wallet: Bittensor wallet object. + :param netuid: The `netuid` of the subnet to register on. + :param old_balance: The wallet balance prior to the registration burn. + :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns + `False` if the extrinsic fails to enter the block within the timeout. + :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, + or returns `False` if the extrinsic fails to be finalized within the timeout. + :param prompt: If `True`, the call waits for confirmation from the user before proceeding. + + :return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for + finalization/inclusion, the response is `True`. + """ + + try: + wallet.unlock_coldkey() + except KeyFileError: + err_console.print("Error decrypting coldkey (possibly incorrect password)") + return False + + with console.status( + f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...", + spinner="aesthetic", + ) as status: + my_uid = await subtensor.substrate.query( + "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address] + ) + + print_verbose("Checking if already registered", status) + neuron = await subtensor.neuron_for_uid( + uid=my_uid, + netuid=netuid, + block_hash=subtensor.substrate.last_block_hash, + ) + + if not neuron.is_null: + console.print( + ":white_heavy_check_mark: [green]Already Registered[/green]:\n" + f"uid: [bold white]{neuron.uid}[/bold white]\n" + f"netuid: [bold white]{neuron.netuid}[/bold white]\n" + f"hotkey: [bold white]{neuron.hotkey}[/bold white]\n" + f"coldkey: [bold white]{neuron.coldkey}[/bold white]" + ) + return True + + with console.status( + ":satellite: Recycling TAO for Registration...", spinner="aesthetic" + ): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + success, err_msg = await subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization + ) + + if not success: + err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") + await asyncio.sleep(0.5) + return False + # Successful registration, final check for neuron and pubkey + else: + with console.status(":satellite: Checking Balance...", spinner="aesthetic"): + block_hash = await subtensor.substrate.get_chain_head() + new_balance, netuids_for_hotkey, my_uid = await asyncio.gather( + subtensor.get_balance( + wallet.coldkeypub.ss58_address, + block_hash=block_hash, + reuse_block=False, + ), + subtensor.get_netuids_for_hotkey( + wallet.hotkey.ss58_address, block_hash=block_hash + ), + subtensor.substrate.query( + "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address] + ), + ) + + console.print( + "Balance:\n" + f" [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]" + ) + + if len(netuids_for_hotkey) > 0: + console.print( + f":white_heavy_check_mark: [green]Registered on netuid {netuid} with UID {my_uid}[/green]" + ) + return True + else: + # neuron not found, try again + err_console.print( + ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + ) + return False + + async def run_faucet_extrinsic( subtensor: "SubtensorInterface", wallet: Wallet, diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 93e6390d..189f0dbb 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -12,9 +12,11 @@ from bittensor_cli.src import DelegatesDetails from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import SubnetInfo -from bittensor_cli.src.bittensor.extrinsics.registration import register_extrinsic +from bittensor_cli.src.bittensor.extrinsics.registration import ( + register_extrinsic, + burned_register_extrinsic, +) from bittensor_cli.src.bittensor.minigraph import MiniGraph -from bittensor_cli.src.commands.root import burned_register_extrinsic from bittensor_cli.src.commands.wallets import set_id, set_id_prompts from bittensor_cli.src.bittensor.utils import ( RAO_PER_TAO, @@ -176,6 +178,7 @@ async def subnets_list( ) ) + # TODO make this a reusable function # Define table properties console_width = console.width - 5 table = Table( @@ -359,6 +362,58 @@ async def register( return if prompt: + # TODO make this a reusable function, also used in subnets list + # Show creation table. + console_width = console.width - 5 + table = Table( + title="Subnet Info", + width=console_width, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + table.title = f"[white]Register - {subtensor.network}\n" + table.add_column( + "Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center" + ) + table.add_column( + "Symbol", style="rgb(211,54,130)", no_wrap=True, justify="center" + ) + table.add_column( + f"Cost ({Balance.get_unit(0)})", + style="rgb(38,139,210)", + no_wrap=True, + justify="right", + ) + table.add_column( + "Hotkey", style="light_salmon3", no_wrap=True, justify="center" + ) + table.add_column( + "Coldkey", style="bold dark_green", no_wrap=True, justify="center" + ) + table.add_row( + str(netuid), + f"[light_goldenrod1]{Balance.get_unit(netuid)}[light_goldenrod1]", + f"τ {current_recycle.tao:.4f}", + f"{wallet.hotkey.ss58_address}", + f"{wallet.coldkeypub.ss58_address}", + ) + console.print(table) if not ( Confirm.ask( f"Your balance is: [bold green]{balance}[/bold green]\nThe cost to register by recycle is " @@ -373,7 +428,6 @@ async def register( wallet=wallet, netuid=netuid, prompt=False, - recycle_amount=current_recycle, old_balance=balance, ) From ec0cfd1b0560b9bb45a0b91a0456dd930d79eb4f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Oct 2024 13:06:06 +0200 Subject: [PATCH 027/157] Subnets show --- bittensor_cli/cli.py | 18 ++ bittensor_cli/src/commands/subnets.py | 321 +++++++++++++++++++++++++- 2 files changed, 337 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 11e72806..aa798d19 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3077,6 +3077,24 @@ def subnets_list( ) ) + def subnets_show( + self, + network: Optional[list[str]] = Options.network, + netuid: int = Options.netuid, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + prompt: bool = Options.prompt, + ): + self.verbosity_handler(quiet, verbose) + subtensor = self.initialize_chain(network) + return self._run_command( + subnets.show( + subtensor, + netuid, + prompt=prompt, + ) + ) + def subnets_lock_cost( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 189f0dbb..1e7cda68 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -9,9 +9,8 @@ from rich.prompt import Confirm from rich.table import Column, Table -from bittensor_cli.src import DelegatesDetails from bittensor_cli.src.bittensor.balances import Balance -from bittensor_cli.src.bittensor.chain_data import SubnetInfo +from bittensor_cli.src.bittensor.chain_data import SubnetState from bittensor_cli.src.bittensor.extrinsics.registration import ( register_extrinsic, burned_register_extrinsic, @@ -264,6 +263,324 @@ async def subnets_list( ) +async def show(subtensor: "SubtensorInterface", netuid: int, prompt: bool = True): + async def show_root(): + all_subnets = await subtensor.get_all_subnet_dynamic_info() + root_state: "SubnetState" = SubnetState.from_vec_u8( + ( + await subtensor.substrate.rpc_request( + method="subnetInfo_getSubnetState", params=[0, None] + ) + )["result"] + ) + if root_state is None: + err_console.print("The root subnet does not exist") + return + if len(root_state.hotkeys) == 0: + err_console.print( + "The root-subnet is currently empty with 0 UIDs registered." + ) + return + + console_width = console.width - 5 + table = Table( + title="[white]Root Network", + width=console_width, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + # Add columns to the table + table.add_column( + "Position", style="rgb(253,246,227)", no_wrap=True, justify="center" + ) + table.add_column( + f"TAO ({Balance.get_unit(0)})", + style="medium_purple", + no_wrap=True, + justify="center", + ) + table.add_column( + f"Stake ({Balance.get_unit(0)})", + style="dark_sea_green", + no_wrap=True, + justify="center", + ) + table.add_column( + f"Emission ({Balance.get_unit(0)}/block)", + style="rgb(42,161,152)", + no_wrap=True, + justify="center", + ) + table.add_column( + "Hotkey", style="light_salmon3", no_wrap=True, justify="center" + ) + sorted_hotkeys = sorted( + enumerate(root_state.hotkeys), + key=lambda x: root_state.global_stake[x[0]], + reverse=True, + ) + for pos, (idx, hk) in enumerate(sorted_hotkeys): + total_emission_per_block = 0 + for netuid_ in range(len(all_subnets)): + subnet = all_subnets[netuid_] + emission_on_subnet = ( + root_state.emission_history[netuid_][idx] / subnet.tempo + ) + total_emission_per_block += subnet.alpha_to_tao( + Balance.from_rao(emission_on_subnet) + ) + table.add_row( + str((pos + 1)), + str(root_state.global_stake[idx]), + str(root_state.local_stake[idx]), + str(total_emission_per_block), + f"{root_state.hotkeys[idx]}", + ) + + # Print the table + console.print(table) + console.print( + """ + Description: + The table displays the root subnet participants and their metrics. + The columns are as follows: + - Position: The sorted position of the hotkey by total TAO. + - TAO: The sum of all TAO balances for this hotkey accross all subnets. + - Stake: The stake balance of this hotkey on root (measured in TAO). + - Emission: The emission accrued to this hotkey across all subnets every block measured in TAO. + - Hotkey: The hotkey ss58 address. + """ + ) + + async def show_subnet(netuid_: int): + subnet_info = await subtensor.get_subnet_dynamic_info(netuid_) + subnet_state: "SubnetState" = SubnetState.from_vec_u8( + ( + await subtensor.substrate.rpc_request( + method="subnetInfo_getSubnetState", params=[netuid_, None] + ) + )["result"] + ) + if subnet_info is None: + err_console.print(f"Subnet {netuid_} does not exist") + return + elif len(subnet_state.hotkeys) == 0: + err_console.print( + f"Subnet {netuid_} is currently empty with 0 UIDs registered." + ) + return + + # Define table properties + console_width = console.width - 5 + table = Table( + title=f"[white]Subnet {netuid_} Metagraph", + width=console_width, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + subnet_info_table = Table( + width=console_width, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=False, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + + subnet_info_table.add_column( + "Index", style="grey89", no_wrap=True, justify="center" + ) + subnet_info_table.add_column( + "Symbol", style="rgb(211,54,130)", no_wrap=True, justify="center" + ) + subnet_info_table.add_column( + f"Emission ({Balance.get_unit(0)})", + style="rgb(38,139,210)", + no_wrap=True, + justify="center", + ) + subnet_info_table.add_column( + f"P({Balance.get_unit(0)},", + style="rgb(108,113,196)", + no_wrap=True, + justify="right", + ) + subnet_info_table.add_column( + f"{Balance.get_unit(1)})", + style="rgb(42,161,152)", + no_wrap=True, + justify="left", + ) + subnet_info_table.add_column( + f"{Balance.get_unit(1)}", + style="rgb(133,153,0)", + no_wrap=True, + justify="center", + ) + subnet_info_table.add_column( + f"Rate ({Balance.get_unit(1)}/{Balance.get_unit(0)})", + style="rgb(181,137,0)", + no_wrap=True, + justify="center", + ) + subnet_info_table.add_column( + "Tempo", style="rgb(38,139,210)", no_wrap=True, justify="center" + ) + subnet_info_table.add_row( + str(netuid_), + f"[light_goldenrod1]{str(subnet_info.symbol)}[light_goldenrod1]", + f"τ{subnet_info.emission.tao:.4f}", + f"P( τ{subnet_info.tao_in.tao:,.4f},", + f"{subnet_info.alpha_in.tao:,.4f}{subnet_info.symbol} )", + f"{subnet_info.alpha_out.tao:,.4f}{subnet_info.symbol}", + f"{subnet_info.price.tao:.4f}τ/{subnet_info.symbol}", + str(subnet_info.blocks_since_last_step) + "/" + str(subnet_info.tempo), + ) + + rows = [] + emission_sum = sum( + [ + subnet_state.emission[idx].tao + for idx in range(len(subnet_state.emission)) + ] + ) + for idx, hk in enumerate(subnet_state.hotkeys): + hotkey_block_emission = ( + subnet_state.emission[idx].tao / emission_sum + if emission_sum != 0 + else 0 + ) + rows.append( + ( + str(idx), + str(subnet_state.global_stake[idx]), + f"{subnet_state.local_stake[idx].tao:.4f} {subnet_info.symbol}", + f"{subnet_state.stake_weight[idx]:.4f}", + # str(subnet_state.dividends[idx]), + f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", + str(subnet_state.incentives[idx]), + f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", + f"{subnet_state.hotkeys[idx]}", + f"{subnet_state.coldkeys[idx]}", + ) + ) + # Add columns to the table + table.add_column("UID", style="grey89", no_wrap=True, justify="center") + table.add_column( + f"TAO({Balance.get_unit(0)})", + style="medium_purple", + no_wrap=True, + justify="right", + ) + table.add_column( + f"Stake({Balance.get_unit(netuid_)})", + style="green", + no_wrap=True, + justify="right", + ) + table.add_column( + f"Weight({Balance.get_unit(0)}•{Balance.get_unit(netuid_)})", + style="blue", + no_wrap=True, + justify="center", + ) + table.add_column( + "Dividends", style="rgb(181,137,0)", no_wrap=True, justify="center" + ) + table.add_column( + "Incentive", style="rgb(220,50,47)", no_wrap=True, justify="center" + ) + table.add_column( + f"Emission ({Balance.get_unit(netuid_)})", + style="aquamarine3", + no_wrap=True, + justify="center", + ) + table.add_column( + "Hotkey", style="light_salmon3", no_wrap=True, justify="center" + ) + table.add_column( + "Coldkey", style="bold dark_green", no_wrap=True, justify="center" + ) + for row in rows: + table.add_row(*row) + + # Print the table + # bt.__console__.print("\n\n\n") + # bt.__console__.print(subnet_info_table) + console.print("\n\n") + console.print(table) + console.print("\n") + console.print( + f"Subnet: {netuid_}:\n Owner: [light_salmon3]{subnet_info.owner}[/light_salmon3]\n Total Locked: [green]{subnet_info.total_locked}[/green]\n Owner Locked: [green]{subnet_info.owner_locked}[/green]" + ) + console.print( + """ +Description: + The table displays the subnet participants and their metrics. + The columns are as follows: + - UID: The hotkey index in the subnet. + - TAO: The sum of all TAO balances for this hotkey accross all subnets. + - Stake: The stake balance of this hotkey on this subnet. + - Weight: The stake-weight of this hotkey on this subnet. Computed as an average of the normalized TAO and Stake columns of this subnet. + - Dividends: Validating dividends earned by the hotkey. + - Incentives: Mining incentives earned by the hotkey (always zero in the RAO demo.) + - Emission: The emission accrued to this hokey on this subnet every block (in staking units). + - Hotkey: The hotkey ss58 address. +""" + ) + + if netuid == 0: + await show_root() + else: + await show_subnet(netuid) + + async def lock_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: """View locking cost of creating a new subnetwork""" with console.status( From 0acb2b4442d8ddde29c861f3302a60c87113a671 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 21 Oct 2024 18:43:09 +0200 Subject: [PATCH 028/157] Updated requirements and use quotes --- bittensor_cli/src/bittensor/extrinsics/registration.py | 2 +- requirements.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index d1a0629c..112ce4fd 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -676,7 +676,7 @@ async def get_neuron_for_pubkey_and_subnet(): async def burned_register_extrinsic( - subtensor: SubtensorInterface, + subtensor: "SubtensorInterface", wallet: Wallet, netuid: int, old_balance: Balance, diff --git a/requirements.txt b/requirements.txt index 2e891bcf..c3a5dfc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ wheel async-property==0.2.2 aiohttp~=3.10.2 -backoff~=2.2.1 GitPython>=3.0.0 fuzzywuzzy~=0.18.0 netaddr~=1.3.0 From 28ca04a6b6e3289a1bacac2563f596b7c2d56f0a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 21 Oct 2024 19:03:59 +0200 Subject: [PATCH 029/157] Scrapped brahmi script. --- bittensor_cli/src/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index acd8cd90..b937220b 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -446,12 +446,6 @@ class WalletValidationTypes(Enum): "\u1684", # ᚄ (Sail, willow, 94) "\u1685", # ᚅ (Nion, ash, 95) "\u169b", # ᚛ (Forfeda, 96) - # Brahmi Script TODO verify these https://discord.com/channels/799672011265015819/1176889593136693339/1288500713625878558 - "\u11000", # 𑀀 (A, 122) - "\u11001", # 𑀁 (Aa, 123) - "\u11002", # 𑀂 (I, 124) - "\u11003", # 𑀃 (Ii, 125) - "\u11005", # 𑀅 (U, 126) # Tifinagh Alphabet "\u2d30", # ⴰ (Ya, 127) "\u2d31", # ⴱ (Yab, 128) From 407e0115279891db5f11afdbfbbf9d39d98de119 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 21 Oct 2024 21:45:24 +0200 Subject: [PATCH 030/157] Add optional netuid grabber. --- bittensor_cli/cli.py | 55 ++++++++++++++++++++--- bittensor_cli/src/commands/stake/stake.py | 5 ++- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index aa798d19..b9304619 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -159,10 +159,22 @@ class Options: "-n", help="Set the netuid(s) to exclude. Separate multiple netuids with a comma, for example: `-n 0,1,2`.", ) - netuid = typer.Option( + netuid = ( + typer.Option( + None, + help="The netuid of the subnet in the root network, (e.g. 1).", + prompt=True, + ), + ) + netuid_not_req = typer.Option( None, help="The netuid of the subnet in the root network, (e.g. 1).", - prompt=True, + prompt=False, + ) + all_netuids = typer.Option( + False, + help="Use all netuids", + prompt=False, ) weights = typer.Option( None, @@ -270,6 +282,25 @@ def verbosity_console_handler(verbosity_level: int = 1) -> None: verbose_console.quiet = False +def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[int]: + """ + Parses options to determine if the user wants to use a specific netuid or all netuids (None) + + Returns: + None if using all netuids, otherwise int for the netuid to use + """ + if netuid is None and all_netuids is True: + return None + elif netuid is None and all_netuids is False: + return typer.prompt( + "Enter the netuid to use. Leave blank for all netuids.", + default=None, + show_default=False, + ) + else: + return netuid + + def get_n_words(n_words: Optional[int]) -> int: """ Prompts the user to select the number of words used in the mnemonic if not supplied or not within the @@ -2384,7 +2415,8 @@ def stake_add( help="When set, this command stakes to all hotkeys associated with the wallet. Do not use if specifying " "hotkeys in `--include-hotkeys`.", ), - netuid: Optional[int] = Options.netuid, + netuid: Optional[int] = Options.netuid_not_req, + all_netuids: bool = Options.all_netuids, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -2405,6 +2437,7 @@ def stake_add( [green]$[/green] btcli stake add --amount 100 --wallet-name --wallet-hotkey """ self.verbosity_handler(quiet, verbose) + netuid = get_optional_netuid(netuid, all_netuids) if stake_all and amount: err_console.print( @@ -2508,6 +2541,8 @@ def stake_remove( wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, + netuid: Optional[int] = Options.netuid_not_req, + all_netuids: bool = Options.all_netuids, unstake_all: bool = typer.Option( False, "--unstake-all", @@ -2561,6 +2596,7 @@ def stake_remove( [blue bold]Note[/blue bold]: This command is for users who wish to reallocate their stake or withdraw them from the network. It allows for flexible management of TAO stake across different neurons (hotkeys) on the network. """ self.verbosity_handler(quiet, verbose) + netuid = get_optional_netuid(netuid, all_netuids) if all_hotkeys and include_hotkeys: err_console.print( @@ -2627,29 +2663,34 @@ def stake_remove( ) if include_hotkeys: - include_hotkeys = parse_to_list( + included_hotkeys = parse_to_list( include_hotkeys, str, "Hotkeys must be a comma-separated list of ss58s, e.g., `--include-hotkeys 5Grw....,5Grw....`.", is_ss58=True, ) + else: + included_hotkeys = [] if exclude_hotkeys: - exclude_hotkeys = parse_to_list( + excluded_hotkeys = parse_to_list( exclude_hotkeys, str, "Hotkeys must be a comma-separated list of ss58s, e.g., `--exclude-hotkeys 5Grw....,5Grw....`.", is_ss58=True, ) + else: + excluded_hotkeys = [] return self._run_command( stake.unstake( wallet, self.initialize_chain(network), hotkey_ss58_address, + netuid, all_hotkeys, - include_hotkeys, - exclude_hotkeys, + included_hotkeys, + excluded_hotkeys, amount, keep_stake, unstake_all, diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 07c9891d..11b91a6a 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1145,6 +1145,7 @@ async def unstake( wallet: Wallet, subtensor: "SubtensorInterface", hotkey_ss58_address: str, + netuid: Optional[int], all_hotkeys: bool, include_hotkeys: list[str], exclude_hotkeys: list[str], @@ -1154,7 +1155,9 @@ async def unstake( prompt: bool, ): """Unstake token of amount from hotkey(s).""" - + netuids = ( + [netuid] if netuid is not None else await subtensor.get_all_subnet_netuids() + ) # Get the hotkey_names (if any) and the hotkey_ss58s. hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = [] if hotkey_ss58_address: From 7370971fc3e247cb8506802d96a35a905e981741 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 21 Oct 2024 22:14:39 +0200 Subject: [PATCH 031/157] Stake remove --- bittensor_cli/src/commands/stake/stake.py | 274 ++++++++++++++++------ 1 file changed, 206 insertions(+), 68 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 11b91a6a..95b14635 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -8,7 +8,7 @@ from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError -from rich.prompt import Confirm, FloatPrompt +from rich.prompt import Confirm, FloatPrompt, Prompt from rich.table import Table, Column import typer @@ -28,6 +28,7 @@ render_tree, u16_normalized_float, validate_coldkey_presence, + format_error_message, ) if TYPE_CHECKING: @@ -1205,81 +1206,218 @@ async def unstake( final_hotkeys: list[tuple[str, str]] = [] final_amounts: list[Union[float, Balance]] = [] hotkey: tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58) - with suppress(ValueError): - with console.status( - f":satellite:Syncing with chain {subtensor}", spinner="earth" - ) as status: - print_verbose("Fetching stake", status) - block_hash = await subtensor.substrate.get_chain_head() - hotkey_stakes = await asyncio.gather( - *[ - subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=hotkey[1], - coldkey_ss58=wallet.coldkeypub.ss58_address, - block_hash=block_hash, - ) - for hotkey in hotkeys_to_unstake_from - ] + + # Get old staking balance. + table = Table( + title=f"[white]Unstake operation to Coldkey SS58: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", + width=console.width - 5, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + rows = [] + unstake_amount_balance = [] + current_stake_balances = [] + total_received_amount = Balance.from_tao(0) + current_wallet_balance: Balance = ( + await subtensor.get_balance(wallet.coldkeypub.ss58_address) + )[wallet.coldkeypub.ss58_address] + max_float_slippage = 0 + non_zero_netuids = [] + # TODO gather this all + for hotkey in hotkeys_to_unstake_from: + staking_address_name, staking_address_ss58 = hotkey + for netuid in netuids: + # Check that the subnet exists. + dynamic_info = await subtensor.get_subnet_dynamic_info(netuid) + if dynamic_info is None: + console.print(f"[red]Subnet: {netuid} does not exist.[/red]") + return False + + current_stake_balance: Balance = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58_address, + netuid=netuid, + ) ) - for hotkey, hotkey_stake in zip(hotkeys_to_unstake_from, hotkey_stakes): - unstake_amount_tao: float = amount - - if unstake_all: - unstake_amount_tao = hotkey_stake.tao - if keep_stake: - # Get the current stake of the hotkey from this coldkey. - unstake_amount_tao = hotkey_stake.tao - keep_stake - amount = unstake_amount_tao - if unstake_amount_tao < 0: - # Skip if max_stake is greater than current stake. - continue - else: - if unstake_amount_tao > hotkey_stake.tao: - # Skip if the specified amount is greater than the current stake. - continue + if current_stake_balance.tao == 0: + continue + non_zero_netuids.append(netuid) + current_stake_balances.append(current_stake_balance) - final_amounts.append(unstake_amount_tao) - final_hotkeys.append(hotkey) # add both the name and the ss58 address. + # Determine the amount we are staking. + if amount: + amount_to_unstake_as_balance = Balance.from_tao(amount) + elif unstake_all: + amount_to_unstake_as_balance = current_stake_balance + else: # TODO max_stake + if Confirm.ask( + f"Unstake all: [bold]{current_stake_balance}[/bold]" + f" from [bold]{staking_address_name}[/bold] on netuid: {netuid}?" + ): + amount_to_unstake_as_balance = current_stake_balance + else: + try: + amount = float( + Prompt.ask( + f"Enter amount to unstake in {Balance.get_unit(netuid)} from subnet: {netuid}" + ) + ) + amount_to_unstake_as_balance = Balance.from_tao(amount) + except ValueError: + err_console.print( + ":cross_mark:[red]Invalid amount Please use `--amount` with `--no_prompt`.[/red]" + ) + return False + unstake_amount_balance.append(amount_to_unstake_as_balance) - if len(final_hotkeys) == 0: - # No hotkeys to unstake from. - err_console.print( - "Not enough stake to unstake from any hotkeys or max_stake is more than current stake." + # Check enough to stake. + amount_to_unstake_as_balance.set_unit(netuid) + if amount_to_unstake_as_balance > current_stake_balance: + err_console.print( + f"[red]Not enough stake to remove[/red]:[bold white]\n stake balance:{current_stake_balance}" + f" < unstaking amount: {amount_to_unstake_as_balance}[/bold white]" + ) + return False + + received_amount, slippage = dynamic_info.alpha_to_tao_with_slippage( + amount_to_unstake_as_balance ) - return None + total_received_amount += received_amount + if dynamic_info.is_dynamic: + slippage_pct_float = ( + 100 * float(slippage) / float(slippage + received_amount) + if slippage + received_amount != 0 + else 0 + ) + slippage_pct = f"{slippage_pct_float:.4f} %" + else: + slippage_pct_float = 0 + slippage_pct = f"{slippage_pct_float}%" + max_float_slippage = max(max_float_slippage, slippage_pct_float) - # Ask to unstake - if prompt: - if not Confirm.ask( - f"Do you want to unstake from the following keys to {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: " - f"{f'{amount} {Balance.unit}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] + rows.append( + ( + str(netuid), + # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", + f"{staking_address_ss58}", + str(amount_to_unstake_as_balance), + str(float(dynamic_info.price)) + + f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", + str(received_amount), + str(slippage_pct), ) - ): - return None - if len(final_hotkeys) == 1: - # do regular unstake - await unstake_extrinsic( - subtensor, - wallet=wallet, - hotkey_ss58=final_hotkeys[0][1], - amount=None if unstake_all else final_amounts[0], - wait_for_inclusion=True, - prompt=prompt, ) - else: - await unstake_multiple_extrinsic( - subtensor, - wallet=wallet, - hotkey_ss58s=[hotkey_ss58 for _, hotkey_ss58 in final_hotkeys], - amounts=None if unstake_all else final_amounts, - wait_for_inclusion=True, - prompt=prompt, + + table.add_column("Netuid", justify="center", style="grey89") + table.add_column("Hotkey", justify="center", style="light_salmon3") + table.add_column( + f"Amount ({Balance.get_unit(1)})", justify="center", style="dark_sea_green" + ) + table.add_column( + f"Rate ({Balance.get_unit(0)}/{bt.Balance.get_unit(1)})", + justify="center", + style="light_goldenrod2", + ) + table.add_column( + f"Recieved ({Balance.get_unit(0)})", + justify="center", + style="light_slate_blue", + footer=f"{total_received_amount}", + ) + table.add_column("Slippage", justify="center", style="rgb(220,50,47)") + for row in rows: + table.add_row(*row) + bt.__console__.print(table) + message = "" + if max_float_slippage > 5: + message += f"-------------------------------------------------------------------------------------------------------------------\n" + message += f"[bold][yellow]WARNING:[/yellow]\tThe slippage on one of your operations is high: [bold red]{max_float_slippage} %[/bold red], this may result in a loss of funds.[/bold] \n" + message += f"-------------------------------------------------------------------------------------------------------------------\n" + console.print(message) + if prompt: + if not Confirm.ask("Would you like to continue?"): + return False + console.print( + """ +[bold white]Description[/bold white]: +The table displays information about the stake remove operation you are about to perform. +The columns are as follows: + - [bold white]Netuid[/bold white]: The netuid of the subnet you are unstaking from. + - [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are unstaking from. + - [bold white]Amount[/bold white]: The stake amount you are removing from this key. + - [bold white]Rate[/bold white]: The rate of exchange between TAO and the subnet's stake. + - [bold white]Received[/bold white]: The amount of free balance TAO you will receive on this subnet after slippage. + - [bold white]Slippage[/bold white]: The slippage percentage of the unstake operation. (0% if the subnet is not dynamic i.e. root). +""" + ) + + # Perform staking operation. + wallet.unlock_coldkey() + with console.status( + f"\n:satellite: Unstaking {amount_to_unstake_as_balance} from {staking_address_name} on netuid: {netuid} ..." + ): + for netuid_i, amount, current in list( + zip(non_zero_netuids, unstake_amount_balance, current_stake_balances) + ): + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": hotkey_ss58_address, + "netuid": netuid_i, + "amount_unstaked": amount.rao, + }, + ) + extrinsic = subtensor.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey ) + response = subtensor.substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=True, wait_for_finalization=False + ) + if not prompt: + console.print(":white_heavy_check_mark: [green]Sent[/green]") + else: + response.process_events() + if not response.is_success: + err_console.print( + f":cross_mark: [red]Failed[/red] with error: " + f"{format_error_message(response.error_message, subtensor.substrate)}" + ) + else: + new_balance_ = await subtensor.get_balance( + wallet.coldkeypub.ss58_address + ) + new_balance = new_balance_[wallet.coldkeypub.ss58_address] + new_stake = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58_address, + netuid=netuid_i, + ) + ).set_unit(netuid_i) + console.print( + f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + console.print( + f"Subnet: {netuid_i} Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): From 95df386f6064c3b5637abd0d20bc1dbdfcdb8bc2 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 21 Oct 2024 22:19:22 +0200 Subject: [PATCH 032/157] Comments. --- bittensor_cli/src/commands/stake/stake.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 95b14635..7a1ba3e6 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1274,6 +1274,7 @@ async def unstake( amount_to_unstake_as_balance = current_stake_balance else: try: + # TODO improve this amount = float( Prompt.ask( f"Enter amount to unstake in {Balance.get_unit(netuid)} from subnet: {netuid}" @@ -1282,7 +1283,7 @@ async def unstake( amount_to_unstake_as_balance = Balance.from_tao(amount) except ValueError: err_console.print( - ":cross_mark:[red]Invalid amount Please use `--amount` with `--no_prompt`.[/red]" + ":cross_mark:[red]Invalid amount Please use `--amount` with `--no-prompt`.[/red]" ) return False unstake_amount_balance.append(amount_to_unstake_as_balance) From 6f25103d761e4c7c433e3cbb565230ad9706808b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 21 Oct 2024 23:17:28 +0200 Subject: [PATCH 033/157] Stake Move --- bittensor_cli/cli.py | 37 +++ .../src/bittensor/subtensor_interface.py | 2 +- bittensor_cli/src/commands/stake/stake.py | 228 +++++++++++++++++- 3 files changed, 261 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index b9304619..547b8fee 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2698,6 +2698,43 @@ def stake_remove( ) ) + def stake_move( + self, + network=Options.network, + wallet_name=Options.wallet_name, + wallet_path=Options.wallet_path, + wallet_hotkey=Options.wallet_hotkey, + origin_netuid: int = typer.Option(help="Origin netuid", prompt=True), + destination_netuid: int = typer.Option(help="Destination netuid", prompt=True), + destination_hotkey: str = typer.Option( # TODO also accept name + help="Destination hotkey", prompt=True + ), + amount: Optional[float] = typer.Option(help="Amount", prompt=False), + stake_all: bool = typer.Option( + False, "--stake-all", "--all", help="Stake all", prompt=False + ), + prompt: bool = Options.prompt, + ): + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME], + validate=WV.WALLET_AND_HOTKEY, + ) + return self._run_command( + stake.move_stake( + subtensor=self.initialize_chain(network), + wallet=wallet, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + destination_hotkey=destination_hotkey, + amount=amount, + stake_all=stake_all, + prompt=prompt, + ) + ) + def stake_get_children( self, wallet_name: Optional[str] = Options.wallet_name, diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 8e76171b..253f6808 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1122,7 +1122,7 @@ async def get_delegates_by_netuid_light( async def get_subnet_dynamic_info( self, netuid: int, block_hash: Optional[str] = None - ) -> Optional["DynamicInfo"]: + ) -> "DynamicInfo": json = await self.substrate.rpc_request( method="subnetInfo_getDynamicInfo", params=[netuid, block_hash] ) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 7a1ba3e6..541fcded 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1377,7 +1377,7 @@ async def unstake( for netuid_i, amount, current in list( zip(non_zero_netuids, unstake_amount_balance, current_stake_balances) ): - call = subtensor.substrate.compose_call( + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="remove_stake", call_params={ @@ -1386,17 +1386,17 @@ async def unstake( "amount_unstaked": amount.rao, }, ) - extrinsic = subtensor.substrate.create_signed_extrinsic( + extrinsic = await subtensor.substrate.create_signed_extrinsic( call=call, keypair=wallet.coldkey ) - response = subtensor.substrate.submit_extrinsic( + response = await subtensor.substrate.submit_extrinsic( extrinsic, wait_for_inclusion=True, wait_for_finalization=False ) if not prompt: console.print(":white_heavy_check_mark: [green]Sent[/green]") else: - response.process_events() - if not response.is_success: + await response.process_events() + if not await response.is_success: err_console.print( f":cross_mark: [red]Failed[/red] with error: " f"{format_error_message(response.error_message, subtensor.substrate)}" @@ -1646,3 +1646,221 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): - [bold white]Locked[/bold white]: The total amount of stake locked (not able to be unstaked). """ ) + + +async def move_stake( + subtensor: "SubtensorInterface", + wallet: Wallet, + origin_netuid: int, + destination_netuid: int, + destination_hotkey: str, + amount: float, + stake_all: bool, + prompt: bool = True, +): + origin_hotkey_ss58 = wallet.hotkey.ss58_address + # Get the wallet stake balances. + origin_stake_balance: Balance = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=origin_hotkey_ss58, + netuid=origin_netuid, + ) + ).set_unit(origin_netuid) + + destination_stake_balance: Balance = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=destination_hotkey, + netuid=destination_netuid, + ) + ).set_unit(destination_netuid) + + # Determine the amount we are moving. + amount_to_move_as_balance = None + if amount: + amount_to_move_as_balance = Balance.from_tao(amount) + elif stake_all: + amount_to_move_as_balance = origin_stake_balance + else: # max_stake + # TODO improve this + if Confirm.ask(f"Move all: [bold]{origin_stake_balance}[/bold]?"): + amount_to_move_as_balance = origin_stake_balance + else: + try: + amount = float( + Prompt.ask( + f"Enter amount to move in {Balance.get_unit(origin_netuid)}" + ) + ) + amount_to_move_as_balance = Balance.from_tao(amount) + except ValueError: + err_console.print(f":cross_mark:[red]Invalid amount: {amount}[/red]") + return False + + # Check enough to move. + amount_to_move_as_balance.set_unit(origin_netuid) + if amount_to_move_as_balance > origin_stake_balance: + err_console.print( + f"[red]Not enough stake[/red]:[bold white]\n stake balance:{origin_stake_balance} < moving amount: {amount_to_move_as_balance}[/bold white]" + ) + return False + + # Slippage warning + if prompt: + if origin_netuid == destination_netuid: + received_amount_destination = amount_to_move_as_balance + slippage_pct_float = 0 + slippage_pct = f"{slippage_pct_float}%" + price = Balance.from_tao(1).set_unit(origin_netuid) + price_str = ( + str(float(price.tao)) + + f"{Balance.get_unit(origin_netuid)}/{Balance.get_unit(origin_netuid)}" + ) + else: + dynamic_origin, dynamic_destination = await asyncio.gather( + subtensor.get_subnet_dynamic_info(origin_netuid), + subtensor.get_subnet_dynamic_info(destination_netuid), + ) + price = float(dynamic_origin.price) * 1 / float(dynamic_destination.price) + received_amount_tao, slippage = dynamic_origin.alpha_to_tao_with_slippage( + amount_to_move_as_balance + ) + received_amount_destination, slippage = ( + dynamic_destination.tao_to_alpha_with_slippage(received_amount_tao) + ) + received_amount_destination.set_unit(destination_netuid) + slippage_pct_float = ( + 100 * float(slippage) / float(slippage + received_amount_destination) + if slippage + received_amount_destination != 0 + else 0 + ) + slippage_pct = f"{slippage_pct_float:.4f} %" + price_str = ( + str(float(price)) + + f"{Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)}" + ) + + table = Table( + title="[white]Move Stake", + width=console.width - 5, + safe_box=True, + padding=(0, 1), + collapse_padding=False, + pad_edge=True, + expand=True, + show_header=True, + show_footer=True, + show_edge=False, + show_lines=False, + leading=0, + style="none", + row_styles=None, + header_style="bold", + footer_style="bold", + border_style="rgb(7,54,66)", + title_style="bold magenta", + title_justify="center", + highlight=False, + ) + table.add_column("origin netuid", justify="center", style="rgb(133,153,0)") + table.add_column("origin hotkey", justify="center", style="rgb(38,139,210)") + table.add_column("dest netuid", justify="center", style="rgb(133,153,0)") + table.add_column("dest hotkey", justify="center", style="rgb(38,139,210)") + table.add_column( + f"amount ({Balance.get_unit(origin_netuid)})", + justify="center", + style="rgb(38,139,210)", + ) + table.add_column( + f"rate ({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})", + justify="center", + style="rgb(42,161,152)", + ) + table.add_column( + f"received ({Balance.get_unit(destination_netuid)})", + justify="center", + style="rgb(220,50,47)", + ) + table.add_column("slippage", justify="center", style="rgb(181,137,0)") + + table.add_row( + f"{Balance.get_unit(origin_netuid)}({origin_netuid})", + f"{origin_hotkey_ss58[:3]}...{origin_hotkey_ss58[-3:]}", + # TODO f-strings + Balance.get_unit(destination_netuid) + "(" + str(destination_netuid) + ")", + f"{destination_hotkey[:3]}...{destination_hotkey[-3:]}", + str(amount_to_move_as_balance), + price_str, + str(received_amount_destination.set_unit(destination_netuid)), + str(slippage_pct), + ) + + console.print(table) + message = "" + if slippage_pct_float > 5: + message += "\t-------------------------------------------------------------------------------------------------------------------\n" + message += f"\t[bold][yellow]WARNING:[/yellow]\tSlippage is high: [bold red]{slippage_pct}[/bold red], this may result in a loss of funds.[/bold] \n" + message += "\t-------------------------------------------------------------------------------------------------------------------\n" + console.print(message) + if not Confirm.ask("Would you like to continue?"): + return True + + # Perform staking operation. + wallet.unlock_coldkey() + with console.status( + f"\n:satellite: Moving {amount_to_move_as_balance} from {origin_hotkey_ss58} on netuid: {origin_netuid} to " + f"{destination_hotkey} on netuid: {destination_netuid} ..." + ): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="move_stake", + call_params={ + "origin_hotkey": origin_hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey, + "destination_netuid": destination_netuid, + "amount_moved": amount_to_move_as_balance.rao, + }, + ) + extrinsic = await subtensor.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = await subtensor.substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=True, wait_for_finalization=False + ) + if not prompt: + console.print(":white_heavy_check_mark: [green]Sent[/green]") + return True + else: + await response.process_events() + if not await response.is_success: + err_console.print( + f":cross_mark: [red]Failed[/red] with error:" + f" {format_error_message(response.error_message, subtensor.substrate)}" + ) + return + else: + new_origin_stake_balance: Balance = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=origin_hotkey_ss58, + netuid=origin_netuid, + ) + ).set_unit(origin_netuid) + new_destination_stake_balance: Balance = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=destination_hotkey, + netuid=destination_netuid, + ) + ).set_unit(destination_netuid) + console.print( + f"Origin Stake:\n [blue]{origin_stake_balance}[/blue] :arrow_right: " + f"[green]{new_origin_stake_balance}[/green]" + ) + console.print( + f"Destination Stake:\n [blue]{destination_stake_balance}[/blue] :arrow_right: " + f"[green]{new_destination_stake_balance}[/green]" + ) + return From 0560a58d2af763a76b32cfc2023210bff6944cb9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 21 Oct 2024 14:24:49 -0700 Subject: [PATCH 034/157] wallet balance --- .../src/bittensor/subtensor_interface.py | 51 +++++++++++++------ bittensor_cli/src/commands/wallets.py | 1 + 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 253f6808..182cfc1d 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -381,21 +381,42 @@ async def get_total_stake_for_coldkey( :return: {address: Balance objects} """ - calls = [ - ( - await self.substrate.create_storage_key( - "SubtensorModule", - "TotalColdkeyStake", - [address], - block_hash=block_hash, - ) - ) - for address in ss58_addresses - ] - batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) + sub_stakes = await self.get_stake_info_for_coldkeys( + ss58_addresses, block_hash=block_hash + ) + + # Token pricing info + dynamic_info = await self.get_all_subnet_dynamic_info() + results = {} - for item in batch_call: - results.update({item[0].params[0]: Balance.from_rao(item[1] or 0)}) + + for ss58, stake_info_list in sub_stakes.items(): + all_staked_tao = 0 + + for sub_stake in stake_info_list: + if sub_stake.stake.rao == 0: + continue + netuid = sub_stake.netuid + pool = dynamic_info[netuid] + + alpha_value = Balance.from_rao(int(sub_stake.stake.rao)).set_unit( + netuid + ) + + tao_locked = pool.tao_in + + issuance = pool.alpha_out if pool.is_dynamic else tao_locked + tao_ownership = 0 + + if alpha_value.tao > 0.00009 and issuance.tao != 0: + tao_ownership = Balance.from_tao( + (alpha_value.tao / issuance.tao) * tao_locked.tao + ) + + all_staked_tao += tao_ownership.rao + + results[ss58] = Balance.from_rao(all_staked_tao) + return results async def get_total_stake_for_hotkey( @@ -1230,7 +1251,7 @@ async def get_stake_info_for_coldkeys( hex_bytes_result = await self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", method="get_stake_info_for_coldkeys", - params=encoded_coldkeys, + params=[encoded_coldkeys], block_hash=block_hash, ) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 3faefd23..954bdb6c 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -253,6 +253,7 @@ async def wallet_balance( subtensor.get_total_stake_for_coldkey(*coldkeys, block_hash=block_hash), ) + total_free_balance = sum(free_balances.values()) total_staked_balance = sum(staked_balances.values()) From 83c044cd577cf1bb5b7abb4f5dfe20746f96060e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 21 Oct 2024 15:49:12 -0700 Subject: [PATCH 035/157] btcli sudo senate --- bittensor_cli/cli.py | 20 ++++++++ bittensor_cli/src/__init__.py | 1 + bittensor_cli/src/commands/sudo.py | 75 +++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 547b8fee..29ebc4dd 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -670,6 +670,9 @@ def __init__(self): self.sudo_app.command("get", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( self.sudo_get ) + self.sudo_app.command( + "senate", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"] + )(self.sudo_senate) # subnets commands self.subnets_app.command( @@ -3109,6 +3112,23 @@ def sudo_get( sudo.get_hyperparameters(self.initialize_chain(network), netuid) ) + def sudo_senate( + self, + network: Optional[list[str]] = Options.network, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + ): + """ + Shows the Senate members of the Bittensor's governance protocol. + + This command lists the delegates involved in the decision-making process of the Bittensor network, showing their names and wallet addresses. This information is crucial for understanding who holds governance roles within the network. + + EXAMPLE + [green]$[/green] btcli root senate + """ + self.verbosity_handler(quiet, verbose) + return self._run_command(sudo.get_senate(self.initialize_chain(network))) + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index b937220b..1caee07f 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -513,6 +513,7 @@ class WalletValidationTypes(Enum): }, "SUDO": { "CONFIG": "Subnet Configuration", + "GOVERNANCE": "Governance" }, "SUBNETS": { "INFO": "Subnet Information", diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 6ebbb0ec..0bf3199b 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -1,12 +1,12 @@ import asyncio -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Union, Optional from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError from rich import box from rich.table import Column, Table -from bittensor_cli.src import HYPERPARAMS +from bittensor_cli.src import HYPERPARAMS, DelegatesDetails from bittensor_cli.src.bittensor.chain_data import decode_account_id from bittensor_cli.src.bittensor.utils import ( console, @@ -234,3 +234,74 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int): console.print(table) return True + + +async def get_senate(subtensor: "SubtensorInterface"): + """View Bittensor's senate memebers""" + with console.status( + f":satellite: Syncing with chain: [white]{subtensor}[/white] ...", + spinner="aesthetic", + ) as status: + print_verbose("Fetching senate members", status) + senate_members = await _get_senate_members(subtensor) + + print_verbose("Fetching member details from Github and on-chain identities") + delegate_info: dict[ + str, DelegatesDetails + ] = await subtensor.get_delegate_identities() + + table = Table( + Column( + "[bold white]NAME", + style="bright_cyan", + no_wrap=True, + ), + Column( + "[bold white]ADDRESS", + style="bright_magenta", + no_wrap=True, + ), + title=f"[underline dark_orange]Senate[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n", + show_footer=True, + show_edge=False, + expand=False, + border_style="bright_black", + leading=True, + ) + + for ss58_address in senate_members: + table.add_row( + ( + delegate_info[ss58_address].display + if ss58_address in delegate_info + else "~" + ), + ss58_address, + ) + + return console.print(table) + + +async def _get_senate_members( + subtensor: "SubtensorInterface", block_hash: Optional[str] = None +) -> list[str]: + """ + Gets all members of the senate on the given subtensor's network + + :param subtensor: SubtensorInterface object to use for the query + + :return: list of the senate members' ss58 addresses + """ + senate_members = await subtensor.substrate.query( + module="SenateMembers", + storage_function="Members", + params=None, + block_hash=block_hash, + ) + try: + return [ + decode_account_id(i[x][0]) for i in senate_members for x in range(len(i)) + ] + except (IndexError, TypeError): + err_console.print("Unable to retrieve senate members.") + return [] From 6ab88518a86e326c49f328f8de263688ffcc4b06 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 21 Oct 2024 16:30:01 -0700 Subject: [PATCH 036/157] btcli root proposals --- bittensor_cli/cli.py | 22 +++- bittensor_cli/src/commands/sudo.py | 178 +++++++++++++++++++++++++---- 2 files changed, 178 insertions(+), 22 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 29ebc4dd..460c1bb8 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -673,6 +673,9 @@ def __init__(self): self.sudo_app.command( "senate", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"] )(self.sudo_senate) + self.sudo_app.command( + "proposals", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"] + )(self.sudo_proposals) # subnets commands self.subnets_app.command( @@ -3124,11 +3127,28 @@ def sudo_senate( This command lists the delegates involved in the decision-making process of the Bittensor network, showing their names and wallet addresses. This information is crucial for understanding who holds governance roles within the network. EXAMPLE - [green]$[/green] btcli root senate + [green]$[/green] btcli sudo senate """ self.verbosity_handler(quiet, verbose) return self._run_command(sudo.get_senate(self.initialize_chain(network))) + def sudo_proposals( + self, + network: Optional[list[str]] = Options.network, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + ): + """ + View active proposals for the senate in the Bittensor's governance protocol. + + This command displays the details of ongoing proposals, including proposal hashes, votes, thresholds, and proposal data. + + EXAMPLE + [green]$[/green] btcli sudo proposals + """ + self.verbosity_handler(quiet, verbose) + return self._run_command(sudo.proposals(self.initialize_chain(network))) + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 0bf3199b..1de277da 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -5,6 +5,7 @@ from bittensor_wallet.errors import KeyFileError from rich import box from rich.table import Column, Table +from scalecodec import GenericCall from bittensor_cli.src import HYPERPARAMS, DelegatesDetails from bittensor_cli.src.bittensor.chain_data import decode_account_id @@ -17,7 +18,10 @@ ) if TYPE_CHECKING: - from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface + from bittensor_cli.src.bittensor.subtensor_interface import ( + SubtensorInterface, + ProposalVoteData, + ) # helpers and extrinsics @@ -167,6 +171,110 @@ async def set_hyperparameter_extrinsic( return True +async def _get_senate_members( + subtensor: "SubtensorInterface", block_hash: Optional[str] = None +) -> list[str]: + """ + Gets all members of the senate on the given subtensor's network + + :param subtensor: SubtensorInterface object to use for the query + + :return: list of the senate members' ss58 addresses + """ + senate_members = await subtensor.substrate.query( + module="SenateMembers", + storage_function="Members", + params=None, + block_hash=block_hash, + ) + try: + return [ + decode_account_id(i[x][0]) for i in senate_members for x in range(len(i)) + ] + except (IndexError, TypeError): + err_console.print("Unable to retrieve senate members.") + return [] + + +async def _get_proposals( + subtensor: "SubtensorInterface", block_hash: str +) -> dict[str, tuple[dict, "ProposalVoteData"]]: + async def get_proposal_call_data(p_hash: str) -> Optional[GenericCall]: + proposal_data = await subtensor.substrate.query( + module="Triumvirate", + storage_function="ProposalOf", + block_hash=block_hash, + params=[p_hash], + ) + return proposal_data + + ph = await subtensor.substrate.query( + module="Triumvirate", + storage_function="Proposals", + params=None, + block_hash=block_hash, + ) + + try: + proposal_hashes: list[str] = [ + f"0x{bytes(ph[0][x][0]).hex()}" for x in range(len(ph[0])) + ] + except (IndexError, TypeError): + err_console.print("Unable to retrieve proposal vote data") + return {} + + call_data_, vote_data_ = await asyncio.gather( + asyncio.gather(*[get_proposal_call_data(h) for h in proposal_hashes]), + asyncio.gather(*[subtensor.get_vote_data(h) for h in proposal_hashes]), + ) + return { + proposal_hash: (cd, vd) + for cd, vd, proposal_hash in zip(call_data_, vote_data_, proposal_hashes) + } + + +def display_votes( + vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails] +) -> str: + vote_list = list() + + for address in vote_data.ayes: + vote_list.append( + "{}: {}".format( + delegate_info[address].display if address in delegate_info else address, + "[bold green]Aye[/bold green]", + ) + ) + + for address in vote_data.nays: + vote_list.append( + "{}: {}".format( + delegate_info[address].display if address in delegate_info else address, + "[bold red]Nay[/bold red]", + ) + ) + + return "\n".join(vote_list) + + +def format_call_data(call_data: dict) -> str: + # Extract the module and call details + module, call_details = next(iter(call_data.items())) + + # Extract the call function name and arguments + call_info = call_details[0] + call_function, call_args = next(iter(call_info.items())) + + # Extract the argument, handling tuple values + formatted_args = ", ".join( + str(arg[0]) if isinstance(arg, tuple) else str(arg) + for arg in call_args.values() + ) + + # Format the final output string + return f"{call_function}({formatted_args})" + + # commands @@ -282,26 +390,54 @@ async def get_senate(subtensor: "SubtensorInterface"): return console.print(table) -async def _get_senate_members( - subtensor: "SubtensorInterface", block_hash: Optional[str] = None -) -> list[str]: - """ - Gets all members of the senate on the given subtensor's network +async def proposals(subtensor: "SubtensorInterface"): + console.print( + ":satellite: Syncing with chain: [white]{}[/white] ...".format( + subtensor.network + ) + ) + print_verbose("Fetching senate members & proposals") + block_hash = await subtensor.substrate.get_chain_head() + senate_members, all_proposals = await asyncio.gather( + _get_senate_members(subtensor, block_hash), + _get_proposals(subtensor, block_hash), + ) - :param subtensor: SubtensorInterface object to use for the query + print_verbose("Fetching member information from Chain") + registered_delegate_info: dict[ + str, DelegatesDetails + ] = await subtensor.get_delegate_identities() - :return: list of the senate members' ss58 addresses - """ - senate_members = await subtensor.substrate.query( - module="SenateMembers", - storage_function="Members", - params=None, - block_hash=block_hash, + table = Table( + Column( + "[white]HASH", + style="light_goldenrod2", + no_wrap=True, + ), + Column("[white]THRESHOLD", style="rgb(42,161,152)"), + Column("[white]AYES", style="green"), + Column("[white]NAYS", style="red"), + Column( + "[white]VOTES", + style="rgb(50,163,219)", + ), + Column("[white]END", style="bright_cyan"), + Column("[white]CALLDATA", style="dark_sea_green"), + title=f"\n[dark_orange]Proposals\t\t\nActive Proposals: {len(all_proposals)}\t\tSenate Size: {len(senate_members)}\nNetwork: {subtensor.network}", + show_footer=True, + box=box.SIMPLE_HEAVY, + pad_edge=False, + width=None, + border_style="bright_black", ) - try: - return [ - decode_account_id(i[x][0]) for i in senate_members for x in range(len(i)) - ] - except (IndexError, TypeError): - err_console.print("Unable to retrieve senate members.") - return [] + for hash_, (call_data, vote_data) in all_proposals.items(): + table.add_row( + hash_, + str(vote_data.threshold), + str(len(vote_data.ayes)), + str(len(vote_data.nays)), + display_votes(vote_data, registered_delegate_info), + str(vote_data.end), + format_call_data(call_data), + ) + return console.print(table) From 4197bf9a6889b6200225da24b2f12feab882da22 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 21 Oct 2024 16:41:15 -0700 Subject: [PATCH 037/157] updated subnet creation --- bittensor_cli/src/commands/subnets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 1e7cda68..7e539a90 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -113,7 +113,10 @@ async def _find_event_attributes_in_extrinsic_receipt( call = await substrate.compose_call( call_module="SubtensorModule", call_function="register_network", - call_params={"immunity_period": 0, "reg_allowed": True}, + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "mechid": 1, + }, ) extrinsic = await substrate.create_signed_extrinsic( call=call, keypair=wallet.coldkey From 9e6f235467ebabb90f4e0b27caa82a20b503a58d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 21 Oct 2024 17:08:57 -0700 Subject: [PATCH 038/157] btcli sudo senate-vote --- bittensor_cli/cli.py | 54 ++++++++++ bittensor_cli/src/commands/sudo.py | 154 +++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 460c1bb8..c999dc96 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -676,6 +676,9 @@ def __init__(self): self.sudo_app.command( "proposals", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"] )(self.sudo_proposals) + self.sudo_app.command( + "senate-vote", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"] + )(self.sudo_senate_vote) # subnets commands self.subnets_app.command( @@ -747,6 +750,9 @@ def __init__(self): self.subnets_app.command("lock_cost", hidden=True)(self.subnets_lock_cost) self.subnets_app.command("pow_register", hidden=True)(self.subnets_pow_register) + # Sudo + self.sudo_app.command("senate_vote", hidden=True)(self.sudo_senate_vote) + def initialize_chain( self, network: Optional[list[str]] = None, @@ -3149,6 +3155,54 @@ def sudo_proposals( self.verbosity_handler(quiet, verbose) return self._run_command(sudo.proposals(self.initialize_chain(network))) + def sudo_senate_vote( + self, + network: Optional[list[str]] = Options.network, + wallet_name: Optional[str] = Options.wallet_name, + wallet_path: Optional[str] = Options.wallet_path, + wallet_hotkey: Optional[str] = Options.wallet_hotkey, + proposal: str = typer.Option( + None, + "--proposal", + "--proposal-hash", + prompt="Enter the proposal hash", + help="The hash of the proposal to vote on.", + ), + prompt: bool = Options.prompt, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + vote: bool = typer.Option( + None, + "--vote-aye/--vote-nay", + prompt="Enter y to vote Aye, or enter n to vote Nay", + help="The vote casted on the proposal", + ), + ): + """ + Cast a vote on an active proposal in Bittensor's governance protocol. + + This command is used by Senate members to vote on various proposals that shape the network's future. Use `btcli sudo proposals` to see the active proposals and their hashes. + + USAGE + The user must specify the hash of the proposal they want to vote on. The command then allows the Senate member to cast a 'Yes' or 'No' vote, contributing to the decision-making process on the proposal. This command is crucial for Senate members to exercise their voting rights on key proposals. It plays a vital role in the governance and evolution of the Bittensor network. + + EXAMPLE + [green]$[/green] btcli sudo senate_vote --proposal + """ + self.verbosity_handler(quiet, verbose) + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], + validate=WV.WALLET_AND_HOTKEY, + ) + return self._run_command( + sudo.senate_vote( + wallet, self.initialize_chain(network), proposal, vote, prompt + ) + ) + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 1de277da..7f7ef5cb 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -5,6 +5,7 @@ from bittensor_wallet.errors import KeyFileError from rich import box from rich.table import Column, Table +from rich.prompt import Confirm from scalecodec import GenericCall from bittensor_cli.src import HYPERPARAMS, DelegatesDetails @@ -275,6 +276,105 @@ def format_call_data(call_data: dict) -> str: return f"{call_function}({formatted_args})" +def _validate_proposal_hash(proposal_hash: str) -> bool: + if proposal_hash[0:2] != "0x" or len(proposal_hash) != 66: + return False + else: + return True + + +async def _is_senate_member(subtensor: "SubtensorInterface", hotkey_ss58: str) -> bool: + """ + Checks if a given neuron (identified by its hotkey SS58 address) is a member of the Bittensor senate. + The senate is a key governance body within the Bittensor network, responsible for overseeing and + approving various network operations and proposals. + + :param subtensor: SubtensorInterface object to use for the query + :param hotkey_ss58: The `SS58` address of the neuron's hotkey. + + :return: `True` if the neuron is a senate member at the given block, `False` otherwise. + + This function is crucial for understanding the governance dynamics of the Bittensor network and for + identifying the neurons that hold decision-making power within the network. + """ + + senate_members = await _get_senate_members(subtensor) + + if not hasattr(senate_members, "count"): + return False + + return senate_members.count(hotkey_ss58) > 0 + + +async def vote_senate_extrinsic( + subtensor: "SubtensorInterface", + wallet: Wallet, + proposal_hash: str, + proposal_idx: int, + vote: bool, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + prompt: bool = False, +) -> bool: + """Votes ayes or nays on proposals. + + :param subtensor: The SubtensorInterface object to use for the query + :param wallet: Bittensor wallet object, with coldkey and hotkey unlocked. + :param proposal_hash: The hash of the proposal for which voting data is requested. + :param proposal_idx: The index of the proposal to vote. + :param vote: Whether to vote aye or nay. + :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns + `False` if the extrinsic fails to enter the block within the timeout. + :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, + or returns `False` if the extrinsic fails to be finalized within the timeout. + :param prompt: If `True`, the call waits for confirmation from the user before proceeding. + + :return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for + finalization/inclusion, the response is `True`. + """ + + if prompt: + # Prompt user for confirmation. + if not Confirm.ask(f"Cast a vote of {vote}?"): + return False + + with console.status(":satellite: Casting vote..", spinner="aesthetic"): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="vote", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "proposal": proposal_hash, + "index": proposal_idx, + "approve": vote, + }, + ) + success, err_msg = await subtensor.sign_and_send_extrinsic( + call, wallet, wait_for_inclusion, wait_for_finalization + ) + if not success: + err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") + await asyncio.sleep(0.5) + return False + # Successful vote, final check for data + else: + if vote_data := await subtensor.get_vote_data(proposal_hash): + if ( + vote_data.ayes.count(wallet.hotkey.ss58_address) > 0 + or vote_data.nays.count(wallet.hotkey.ss58_address) > 0 + ): + console.print(":white_heavy_check_mark: [green]Vote cast.[/green]") + return True + else: + # hotkey not found in ayes/nays + err_console.print( + ":cross_mark: [red]Unknown error. Couldn't find vote.[/red]" + ) + return False + else: + return False + + # commands @@ -441,3 +541,57 @@ async def proposals(subtensor: "SubtensorInterface"): format_call_data(call_data), ) return console.print(table) + + +async def senate_vote( + wallet: Wallet, + subtensor: "SubtensorInterface", + proposal_hash: str, + vote: bool, + prompt: bool, +) -> bool: + """Vote in Bittensor's governance protocol proposals""" + + if not proposal_hash: + err_console.print( + "Aborting: Proposal hash not specified. View all proposals with the `proposals` command." + ) + return False + elif not _validate_proposal_hash(proposal_hash): + err_console.print( + "Aborting. Proposal hash is invalid. Proposal hashes should start with '0x' and be 32 bytes long" + ) + return False + + print_verbose(f"Fetching senate status of {wallet.hotkey_str}") + if not await _is_senate_member(subtensor, hotkey_ss58=wallet.hotkey.ss58_address): + err_console.print( + f"Aborting: Hotkey {wallet.hotkey.ss58_address} isn't a senate member." + ) + return False + + # Unlock the wallet. + try: + wallet.unlock_hotkey() + wallet.unlock_coldkey() + except KeyFileError: + return False + + console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}") + vote_data = await subtensor.get_vote_data(proposal_hash, reuse_block=True) + if not vote_data: + err_console.print(":cross_mark: [red]Failed[/red]: Proposal not found.") + return False + + success = await vote_senate_extrinsic( + subtensor=subtensor, + wallet=wallet, + proposal_hash=proposal_hash, + proposal_idx=vote_data.index, + vote=vote, + wait_for_inclusion=True, + wait_for_finalization=False, + prompt=prompt, + ) + + return success From c86d756b3c55496e286ff0c71dd5248b3209fced Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 22 Oct 2024 16:23:23 +0200 Subject: [PATCH 039/157] Get Child hotkeys --- .../src/commands/stake/children_hotkeys.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index c472bcb1..2d6e92fb 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -340,7 +340,7 @@ async def get_take(child: tuple) -> float: async def _render_table( parent_hotkey: str, - netuid_children_tuples: list[tuple[int, list[tuple[int, str]]]], + netuid_children_: list[tuple[int, list[tuple[int, str]]]], ): """ Retrieves and renders children hotkeys and their details for a given parent hotkey. @@ -363,10 +363,11 @@ async def _render_table( "Current Stake Weight", style="bold red", no_wrap=True, justify="right" ) - if not netuid_children_tuples: + if not netuid_children_: console.print(table) console.print( - f"[bold red]There are currently no child hotkeys with parent hotkey: {wallet.name} ({parent_hotkey}).[/bold red]" + f"[bold red]There are currently no child hotkeys with parent hotkey: " + f"{wallet.name} ({parent_hotkey}).[/bold red]" ) return @@ -374,15 +375,15 @@ async def _render_table( total_proportion = 0 total_stake_weight = 0 - netuid_children_tuples.sort( + netuid_children_.sort( key=lambda x: x[0] ) # Sort by netuid in ascending order - for index, (netuid, children_) in enumerate(netuid_children_tuples): + for index, (netuid_, children_) in enumerate(netuid_children_): # calculate totals total_proportion_per_netuid = 0 total_stake_weight_per_netuid = 0 - avg_take_per_netuid = 0 + avg_take_per_netuid = 0.0 hotkey_stake_dict = await subtensor.get_total_stake_for_hotkey( parent_hotkey @@ -427,7 +428,7 @@ async def _render_table( hotkey = Text(hotkey, style="italic red" if proportion == 0 else "") table.add_row( - str(netuid), + str(netuid_), hotkey, proportion_str, take_str, @@ -451,7 +452,7 @@ async def _render_table( total_stake_weight += total_stake_weight_per_netuid # Add a dividing line if there are more than one netuid - if len(netuid_children_tuples) > 1: + if len(netuid_children_) > 1: table.add_section() console.print(table) From 7575ac92e9993a27f03da03637bc244e92732c93 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 22 Oct 2024 16:29:34 +0200 Subject: [PATCH 040/157] Set children --- bittensor_cli/cli.py | 25 +----- bittensor_cli/src/__init__.py | 5 +- .../src/commands/stake/children_hotkeys.py | 85 ++----------------- bittensor_cli/src/commands/wallets.py | 1 - 4 files changed, 11 insertions(+), 105 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index c999dc96..18683551 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2814,18 +2814,8 @@ def stake_set_children( wallet_hotkey: str = Options.wallet_hotkey, wallet_path: str = Options.wallet_path, network: Optional[list[str]] = Options.network, - netuid: Optional[int] = typer.Option( - None, - help="The netuid of the subnet, (e.g. 4)", - prompt=False, - ), - all_netuids: bool = typer.Option( - False, - "--all-netuids", - "--all", - "--allnetuids", - help="When this flag is used it sets child hotkeys on all subnets.", - ), + netuid: Optional[int] = Options.netuid_not_req, + all_netuids: bool = Options.all_netuids, proportions: list[float] = typer.Option( [], "--proportions", @@ -2851,15 +2841,8 @@ def stake_set_children( [green]$[/green] btcli stake child set -c 5FCL3gmjtQV4xxxxuEPEFQVhyyyyqYgNwX7drFLw7MSdBnxP -c 5Hp5dxxxxtGg7pu8dN2btyyyyVA1vELmM9dy8KQv3LxV8PA7 --hotkey default --netuid 1 -p 0.3 -p 0.7 """ self.verbosity_handler(quiet, verbose) - if all_netuids and netuid: - err_console.print("Specify either a netuid or `--all`, not both.") - raise typer.Exit() - if all_netuids: - netuid = None - elif not netuid: - netuid = IntPrompt.ask( - "Enter a netuid (leave blank for all)", default=None, show_default=True - ) + netuid = get_optional_netuid(netuid, all_netuids) + children = list_prompt( children, str, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 1caee07f..a2234e7c 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -511,10 +511,7 @@ class WalletValidationTypes(Enum): "STAKE_MGMT": "Stake Management", "CHILD": "Child Hotkeys", }, - "SUDO": { - "CONFIG": "Subnet Configuration", - "GOVERNANCE": "Governance" - }, + "SUDO": {"CONFIG": "Subnet Configuration", "GOVERNANCE": "Governance"}, "SUBNETS": { "INFO": "Subnet Information", "CREATION": "Subnet Creation & Management", diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index 2d6e92fb..8d260e0a 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -375,9 +375,7 @@ async def _render_table( total_proportion = 0 total_stake_weight = 0 - netuid_children_.sort( - key=lambda x: x[0] - ) # Sort by netuid in ascending order + netuid_children_.sort(key=lambda x: x[0]) # Sort by netuid in ascending order for index, (netuid_, children_) in enumerate(netuid_children_): # calculate totals @@ -488,19 +486,20 @@ async def _render_table( return children -async def set_children_new( +async def set_children( wallet: Wallet, subtensor: "SubtensorInterface", children: list[str], proportions: list[float], - hotkey: str, - netuid: int, + netuid: Optional[int], wait_for_inclusion: bool = True, wait_for_finalization: bool = True, prompt: bool = True, ): """Set children hotkeys.""" # Validate children SS58 addresses + # TODO check to see if this should be allowed to be specified by user instead of pulling from wallet + hotkey = wallet.hotkey.ss58_address for child in children: if not is_valid_ss58_address(child): err_console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") @@ -516,7 +515,7 @@ async def set_children_new( f"Proposed sum of proportions is {total_proposed}." ) children_with_proportions = list(zip(proportions, children)) - if netuid: + if netuid is not None: success, message = await set_children_extrinsic( subtensor=subtensor, wallet=wallet, @@ -561,78 +560,6 @@ async def set_children_new( ) -async def set_children( - wallet: Wallet, - subtensor: "SubtensorInterface", - children: list[str], - proportions: list[float], - netuid: Optional[int] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -): - """Set children hotkeys.""" - # Validate children SS58 addresses - for child in children: - if not is_valid_ss58_address(child): - err_console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") - return - if child == wallet.hotkey.ss58_address: - err_console.print(":cross_mark:[red] Cannot set yourself as a child.[/red]") - return - - total_proposed = sum(proportions) - if total_proposed > 1: - raise ValueError( - f"Invalid proportion: The sum of all proportions cannot be greater than 1. " - f"Proposed sum of proportions is {total_proposed}." - ) - - children_with_proportions = list(zip(proportions, children)) - if netuid: - success, message = await set_children_extrinsic( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - hotkey=wallet.hotkey.ss58_address, - children_with_proportions=children_with_proportions, - prompt=True, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - # Result - if success: - if wait_for_inclusion and wait_for_finalization: - console.print("New Status:") - await get_children(wallet, subtensor, netuid) - console.print( - ":white_heavy_check_mark: [green]Set children hotkeys.[/green]" - ) - else: - console.print( - f":cross_mark:[red] Unable to set children hotkeys.[/red] {message}" - ) - else: - # set children on all subnets that parent is registered on - netuids = await subtensor.get_all_subnet_netuids() - for netuid in netuids: - if netuid == 0: # dont include root network - continue - console.print(f"Setting children on netuid {netuid}.") - await set_children_extrinsic( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - hotkey=wallet.hotkey.ss58_address, - children_with_proportions=children_with_proportions, - prompt=False, - wait_for_inclusion=True, - wait_for_finalization=False, - ) - console.print( - ":white_heavy_check_mark: [green]Sent set children request for all subnets.[/green]" - ) - - async def revoke_children( wallet: Wallet, subtensor: "SubtensorInterface", diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 954bdb6c..3faefd23 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -253,7 +253,6 @@ async def wallet_balance( subtensor.get_total_stake_for_coldkey(*coldkeys, block_hash=block_hash), ) - total_free_balance = sum(free_balances.values()) total_staked_balance = sum(staked_balances.values()) From ff61a9683aea83b3119d35967a1d4d4eadaaaf69 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 22 Oct 2024 15:46:46 -0700 Subject: [PATCH 041/157] btcli sudo set-take --- bittensor_cli/cli.py | 57 ++++++++ bittensor_cli/src/__init__.py | 6 +- .../src/bittensor/subtensor_interface.py | 30 +++++ bittensor_cli/src/commands/sudo.py | 127 ++++++++++++++++++ 4 files changed, 219 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 18683551..2e642cce 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -679,6 +679,9 @@ def __init__(self): self.sudo_app.command( "senate-vote", rich_help_panel=HELP_PANELS["SUDO"]["GOVERNANCE"] )(self.sudo_senate_vote) + self.sudo_app.command("set-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])( + self.sudo_set_take + ) # subnets commands self.subnets_app.command( @@ -3186,6 +3189,60 @@ def sudo_senate_vote( ) ) + def sudo_set_take( + self, + network: Optional[list[str]] = Options.network, + wallet_name: Optional[str] = Options.wallet_name, + wallet_path: Optional[str] = Options.wallet_path, + wallet_hotkey: Optional[str] = Options.wallet_hotkey, + take: float = typer.Option(None, help="The new take value."), + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + ): + """ + Allows users to change their delegate take percentage. + + This command can be used to update the delegate takes individually for every subnet. To run the command, the user must have a configured wallet with both hotkey and coldkey. + The command makes sure the new take value is within 0-18% range. + + EXAMPLE + [green]$[/green] btcli sudo set_take --wallet-name my_wallet --wallet-hotkey my_hotkey + """ + max_value = 0.18 + min_value = 0.00 + self.verbosity_handler(quiet, verbose) + + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], + validate=WV.WALLET_AND_HOTKEY, + ) + + current_take = self._run_command( + sudo.get_current_take(self.initialize_chain(network), wallet) + ) + console.print(f"Current take is [dark_orange]{current_take * 100.:.2f}%") + + if not take: + max_value_style = typer.style(f"Max: {max_value}", fg="magenta") + min_value_style = typer.style(f"Min: {min_value}", fg="magenta") + prompt_text = typer.style( + "Enter take value (0.18 for 18%)", fg="bright_cyan", bold=True + ) + take = FloatPrompt.ask(f"{prompt_text} {min_value_style} {max_value_style}") + + if not (min_value <= take <= max_value): + print_error( + f"Take value must be between {min_value} and {max_value}. Provided value: {take}" + ) + raise typer.Exit() + + return self._run_command( + sudo.set_take(wallet, self.initialize_chain(network), take) + ) + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index a2234e7c..3bf9a3c3 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -511,7 +511,11 @@ class WalletValidationTypes(Enum): "STAKE_MGMT": "Stake Management", "CHILD": "Child Hotkeys", }, - "SUDO": {"CONFIG": "Subnet Configuration", "GOVERNANCE": "Governance"}, + "SUDO": { + "CONFIG": "Subnet Configuration", + "GOVERNANCE": "Governance", + "TAKE": "Delegate take configuration" + }, "SUBNETS": { "INFO": "Subnet Information", "CREATION": "Subnet Creation & Management", diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 182cfc1d..3e195b65 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -10,6 +10,7 @@ from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset from substrateinterface.exceptions import SubstrateRequestException +from bittensor_cli.src.bittensor.utils import SS58_FORMAT, u16_normalized_float import typer from bittensor_cli.src.bittensor.async_substrate_interface import ( @@ -443,6 +444,35 @@ async def get_total_stake_for_hotkey( ) return {k: Balance.from_rao(r or 0) for (k, r) in results.items()} + async def current_take( + self, + hotkey_ss58: int, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> bool: + """ + Retrieves the delegate 'take' percentage for a neuron identified by its hotkey. The 'take' + represents the percentage of rewards that the delegate claims from its nominators' stakes. + + Args: + hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. + block (Optional[int], optional): The blockchain block number for the query. + + Returns: + Optional[float]: The delegate take percentage, None if not available. + + The delegate take is a critical parameter in the network's incentive structure, influencing + the distribution of rewards among neurons and their nominators. + """ + result = await self.substrate.query( + module="SubtensorModule", + storage_function="Delegates", + params=[hotkey_ss58], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + return u16_normalized_float(result) + async def get_netuids_for_hotkey( self, hotkey_ss58: str, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 7f7ef5cb..c8daf574 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -16,6 +16,7 @@ print_error, print_verbose, normalize_hyperparameters, + u16_normalized_float, ) if TYPE_CHECKING: @@ -375,6 +376,79 @@ async def vote_senate_extrinsic( return False +async def set_take_extrinsic( + subtensor: "SubtensorInterface", + wallet: Wallet, + delegate_ss58: str, + take: float = 0.0, +) -> bool: + """ + Set delegate hotkey take + + :param subtensor: SubtensorInterface (initialized) + :param wallet: The wallet containing the hotkey to be nominated. + :param delegate_ss58: Hotkey + :param take: Delegate take on subnet ID + + :return: `True` if the process is successful, `False` otherwise. + + This function is a key part of the decentralized governance mechanism of Bittensor, allowing for the + dynamic selection and participation of validators in the network's consensus process. + """ + + # Calculate u16 representation of the take + take_u16 = int(take * 0xFFFF) + + print_verbose("Checking current take") + # Check if the new take is greater or lower than existing take or if existing is set + current_take = await get_current_take(subtensor, wallet) + current_take_u16 = int(float(current_take) * 0xFFFF) + + if take_u16 == current_take_u16: + console.print("Nothing to do, take hasn't changed") + return True + + if current_take_u16 < take_u16: + console.print( + f"Current take is [dark_orange]{current_take * 100.:.2f}%[/dark_orange]. Increasing to [dark_orange]{take * 100:.2f}%." + ) + with console.status( + f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..." + ): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="increase_take", + call_params={ + "hotkey": delegate_ss58, + "take": take_u16, + }, + ) + success, err = await subtensor.sign_and_send_extrinsic(call, wallet) + + else: + console.print( + f"Current take is [dark_orange]{current_take * 100.:.2f}%[/dark_orange]. Decreasing to [dark_orange]{take * 100:.2f}%." + ) + with console.status( + f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..." + ): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="decrease_take", + call_params={ + "hotkey": delegate_ss58, + "take": take_u16, + }, + ) + success, err = await subtensor.sign_and_send_extrinsic(call, wallet) + + if not success: + err_console.print(err) + else: + console.print(":white_heavy_check_mark: [green]Finalized[/green]") + return success + + # commands @@ -595,3 +669,56 @@ async def senate_vote( ) return success + + +async def get_current_take(subtensor: "SubtensorInterface", wallet: Wallet): + current_take = await subtensor.current_take(wallet.hotkey.ss58_address) + return current_take + + +async def set_take( + wallet: Wallet, subtensor: "SubtensorInterface", take: float +) -> bool: + """Set delegate take.""" + + async def _do_set_take() -> bool: + if take > 0.18 or take < 0: + err_console.print("ERROR: Take value should not exceed 18% or be below 0%") + return False + + block_hash = await subtensor.substrate.get_chain_head() + netuids_registered = await subtensor.get_netuids_for_hotkey( + wallet.hotkey.ss58_address, block_hash=block_hash + ) + if not len(netuids_registered) > 0: + err_console.print( + f"Hotkey [dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] is not registered to any subnet. Please register using [dark_orange]`btcli subnets register`[/dark_orange] and try again." + ) + return False + + result: bool = await set_take_extrinsic( + subtensor=subtensor, + wallet=wallet, + delegate_ss58=wallet.hotkey.ss58_address, + take=take, + ) + + if not result: + err_console.print("Could not set the take") + return False + else: + new_take = await get_current_take(subtensor, wallet) + console.print(f"New take is [dark_orange]{new_take * 100.:.2f}%") + return True + + console.print(f"Setting take on [dark_orange]network: {subtensor.network}") + + try: + wallet.unlock_hotkey() + wallet.unlock_coldkey() + except KeyFileError: + return False + + result_ = await _do_set_take() + + return result_ From 588cadd7c43514a26dae0207ce5a0258428ba01b Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 22 Oct 2024 16:17:08 -0700 Subject: [PATCH 042/157] btcli sudo get-take --- bittensor_cli/cli.py | 41 ++++++++++++++++++- .../src/bittensor/subtensor_interface.py | 2 +- bittensor_cli/src/commands/sudo.py | 1 - 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 2e642cce..d8065829 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -682,6 +682,9 @@ def __init__(self): self.sudo_app.command("set-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])( self.sudo_set_take ) + self.sudo_app.command("get-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])( + self.sudo_get_take + ) # subnets commands self.subnets_app.command( @@ -755,6 +758,8 @@ def __init__(self): # Sudo self.sudo_app.command("senate_vote", hidden=True)(self.sudo_senate_vote) + self.sudo_app.command("get_take", hidden=True)(self.sudo_get_take) + self.sudo_app.command("set_take", hidden=True)(self.sudo_set_take) def initialize_chain( self, @@ -3202,11 +3207,11 @@ def sudo_set_take( """ Allows users to change their delegate take percentage. - This command can be used to update the delegate takes individually for every subnet. To run the command, the user must have a configured wallet with both hotkey and coldkey. + This command can be used to update the delegate takes. To run the command, the user must have a configured wallet with both hotkey and coldkey. The command makes sure the new take value is within 0-18% range. EXAMPLE - [green]$[/green] btcli sudo set_take --wallet-name my_wallet --wallet-hotkey my_hotkey + [green]$[/green] btcli sudo set-take --wallet-name my_wallet --wallet-hotkey my_hotkey """ max_value = 0.18 min_value = 0.00 @@ -3243,6 +3248,38 @@ def sudo_set_take( sudo.set_take(wallet, self.initialize_chain(network), take) ) + def sudo_get_take( + self, + network: Optional[list[str]] = Options.network, + wallet_name: Optional[str] = Options.wallet_name, + wallet_path: Optional[str] = Options.wallet_path, + wallet_hotkey: Optional[str] = Options.wallet_hotkey, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + ): + """ + Allows users to check their delegate take percentage. + + This command can be used to fetch the delegate take of your hotkey. + + EXAMPLE + [green]$[/green] btcli sudo get-take --wallet-name my_wallet --wallet-hotkey my_hotkey + """ + self.verbosity_handler(quiet, verbose) + + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], + validate=WV.WALLET_AND_HOTKEY, + ) + + current_take = self._run_command( + sudo.get_current_take(self.initialize_chain(network), wallet) + ) + console.print(f"Current take is [dark_orange]{current_take * 100.:.2f}%") + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 3e195b65..74fd7a78 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -10,7 +10,6 @@ from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset from substrateinterface.exceptions import SubstrateRequestException -from bittensor_cli.src.bittensor.utils import SS58_FORMAT, u16_normalized_float import typer from bittensor_cli.src.bittensor.async_substrate_interface import ( @@ -38,6 +37,7 @@ err_console, decode_hex_identity_dict, validate_chain_endpoint, + u16_normalized_float, ) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index c8daf574..641fd7ba 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -16,7 +16,6 @@ print_error, print_verbose, normalize_hyperparameters, - u16_normalized_float, ) if TYPE_CHECKING: From c2e4b6d49e2a46fc5092559494612bd47742e3d4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 23 Oct 2024 20:25:32 +0200 Subject: [PATCH 043/157] Scale decoding. --- bittensor_cli/src/__init__.py | 2 +- bittensor_cli/src/bittensor/chain_data.py | 95 +++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 3bf9a3c3..20cb5652 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -514,7 +514,7 @@ class WalletValidationTypes(Enum): "SUDO": { "CONFIG": "Subnet Configuration", "GOVERNANCE": "Governance", - "TAKE": "Delegate take configuration" + "TAKE": "Delegate take configuration", }, "SUBNETS": { "INFO": "Subnet Information", diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 9f359e92..347b9f09 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -1,8 +1,12 @@ from dataclasses import dataclass +from enum import Enum from typing import Optional, Any, Union import bt_decode import netaddr +from scalecodec import ScaleBytes +from scalecodec.base import RuntimeConfiguration +from scalecodec.type_registry import load_type_registry_preset from scalecodec.utils.ss58 import ss58_encode from bittensor_cli.src.bittensor.balances import Balance @@ -10,6 +14,63 @@ from bittensor_cli.src.bittensor.utils import SS58_FORMAT, u16_normalized_float +class ChainDataType(Enum): + NeuronInfo = 1 + SubnetInfoV2 = 2 + DelegateInfo = 3 + NeuronInfoLite = 4 + DelegatedInfo = 5 + StakeInfo = 6 + IPInfo = 7 + SubnetHyperparameters = 8 + SubstakeElements = 9 + DynamicPoolInfoV2 = 10 + DelegateInfoLite = 11 + DynamicInfo = 12 + ScheduledColdkeySwapInfo = 13 + SubnetInfo = 14 + SubnetState = 15 + + +def from_scale_encoding_using_type_string( + input_: Union[list[int], bytes, ScaleBytes], type_string: str +) -> Optional[dict]: + if isinstance(input_, ScaleBytes): + as_scale_bytes = input_ + else: + if isinstance(input_, list) and all([isinstance(i, int) for i in input_]): + vec_u8 = input_ + as_bytes = bytes(vec_u8) + elif isinstance(input_, bytes): + as_bytes = input_ + else: + raise TypeError("input must be a List[int], bytes, or ScaleBytes") + as_scale_bytes = ScaleBytes(as_bytes) + rpc_runtime_config = RuntimeConfiguration() + rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy")) + rpc_runtime_config.update_type_registry(custom_rpc_type_registry) + obj = rpc_runtime_config.create_scale_object(type_string, data=as_scale_bytes) + return obj.decode() + + +def from_scale_encoding( + input_: Union[list[int], bytes, ScaleBytes], + type_name: ChainDataType, + is_vec: bool = False, + is_option: bool = False, +) -> Optional[dict]: + type_string = type_name.name + if type_name == ChainDataType.DelegatedInfo: + # DelegatedInfo is a tuple of (DelegateInfo, Compact) + type_string = f"({ChainDataType.DelegateInfo.name}, Compact)" + if is_option: + type_string = f"Option<{type_string}>" + if is_vec: + type_string = f"Vec<{type_string}>" + + return from_scale_encoding_using_type_string(input_, type_string) + + def decode_account_id(account_id_bytes: tuple): # Convert the AccountId bytes to a Base64 string return ss58_encode(bytes(account_id_bytes).hex(), SS58_FORMAT) @@ -729,6 +790,40 @@ def list_from_vec_u8(cls, vec_u8: bytes) -> list["SubnetInfoV2"]: return decoded + @classmethod + def fix_decoded_values(cls, decoded: dict) -> "SubnetInfoV2": + """Returns a SubnetInfoV2 object from a decoded SubnetInfoV2 dictionary.""" + # init dynamic pool object + pool_info = decoded["dynamic_pool"] + if pool_info: + pool = DynamicPool( + True, + pool_info["netuid"], + pool_info["alpha_issuance"], + pool_info["alpha_outstanding"], + pool_info["alpha_reserve"], + pool_info["tao_reserve"], + pool_info["k"], + ) + else: + pool = DynamicPool(False, decoded["netuid"], 0, 0, 0, 0, 0) + + return SubnetInfoV2( + netuid=decoded["netuid"], + owner_ss58=ss58_encode(decoded["owner"], SS58_FORMAT), + max_allowed_validators=decoded["max_allowed_validators"], + scaling_law_power=decoded["scaling_law_power"], + subnetwork_n=decoded["subnetwork_n"], + max_n=decoded["max_allowed_uids"], + blocks_since_epoch=decoded["blocks_since_last_step"], + modality=decoded["network_modality"], + emission_value=decoded["emission_values"], + burn=Balance.from_rao(decoded["burn"]), + tao_locked=Balance.from_rao(decoded["tao_locked"]), + hyperparameters=decoded["hyperparameters"], + dynamic_pool=pool, + ) + @dataclass class DynamicInfo: From fc2bbd24f999cdbdb2c144fbaaf74cf5da46c986 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 23 Oct 2024 16:49:46 -0700 Subject: [PATCH 044/157] btcli s metagraph (awaiting confirmation for few fields) --- bittensor_cli/cli.py | 36 ++++++-- bittensor_cli/src/__init__.py | 4 +- bittensor_cli/src/bittensor/minigraph.py | 50 +++++++++-- bittensor_cli/src/commands/subnets.py | 110 +++++++++++++++++------ 4 files changed, 157 insertions(+), 43 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d8065829..2b0e6163 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -472,7 +472,9 @@ def __init__(self): "use_cache": True, "metagraph_cols": { "UID": True, - "STAKE": True, + "GLOBAL_STAKE": True, + "LOCAL_STAKE": True, + "STAKE_WEIGHT": True, "RANK": True, "TRUST": True, "CONSENSUS": True, @@ -838,15 +840,33 @@ def main_callback( """ Command line interface (CLI) for Bittensor. Uses the values in the configuration file. These values can be overriden by passing them explicitly in the command line. """ - # create config file if it does not exist - if not os.path.exists(self.config_path): + + # Load or create the config file + if os.path.exists(self.config_path): + with open(self.config_path, "r") as f: + config = safe_load(f) + else: directory_path = Path(self.config_base_path) directory_path.mkdir(exist_ok=True, parents=True) - with open(self.config_path, "w+") as f: - safe_dump(defaults.config.dictionary, f) - # check config - with open(self.config_path, "r") as f: - config = safe_load(f) + config = defaults.config.dictionary.copy() + with open(self.config_path, "w") as f: + safe_dump(config, f) + + # Update missing values + updated = False + for key, value in defaults.config.dictionary.items(): + if key not in config: + config[key] = value + updated = True + elif isinstance(value, dict): + for sub_key, sub_value in value.items(): + if sub_key not in config[key]: + config[key][sub_key] = sub_value + updated = True + if updated: + with open(self.config_path, "w") as f: + safe_dump(config, f) + for k, v in config.items(): if k in self.config.keys(): self.config[k] = v diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 20cb5652..a4c749fd 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -77,7 +77,9 @@ class config: "use_cache": True, "metagraph_cols": { "UID": True, - "STAKE": True, + "GLOBAL_STAKE": True, + "LOCAL_STAKE": True, + "STAKE_WEIGHT": True, "RANK": True, "TRUST": True, "CONSENSUS": True, diff --git a/bittensor_cli/src/bittensor/minigraph.py b/bittensor_cli/src/bittensor/minigraph.py index 3f2aac0f..3d652d6d 100644 --- a/bittensor_cli/src/bittensor/minigraph.py +++ b/bittensor_cli/src/bittensor/minigraph.py @@ -3,7 +3,7 @@ import numpy as np from numpy.typing import NDArray -from bittensor_cli.src.bittensor.chain_data import NeuronInfo +from bittensor_cli.src.bittensor.chain_data import NeuronInfo, SubnetState from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface from bittensor_cli.src.bittensor.utils import ( convert_root_weight_uids_and_vals_to_tensor, @@ -18,6 +18,7 @@ def __init__( netuid: int, neurons: list[NeuronInfo], subtensor: "SubtensorInterface", + subnet_state: "SubnetState", block: int, ): self.neurons = neurons @@ -62,12 +63,14 @@ def __init__( self.validator_trust = self._create_tensor( [neuron.validator_trust for neuron in self.neurons], dtype=np.float32 ) - self.total_stake = self._create_tensor( - [neuron.total_stake.tao for neuron in self.neurons], dtype=np.float32 - ) - self.stake = self._create_tensor( - [neuron.stake for neuron in self.neurons], dtype=np.float32 + + # Fetch stakes from subnet_state until we get updated data in NeuronInfo + global_stake_list, local_stake_list, stake_weights_list = self._process_stakes( + neurons, subnet_state ) + self.global_stake = self._create_tensor(global_stake_list, dtype=np.float32) + self.local_stake = self._create_tensor(local_stake_list, dtype=np.float32) + self.stake_weights = self._create_tensor(stake_weights_list, dtype=np.float32) async def __aenter__(self): if not self.weights: @@ -120,6 +123,41 @@ async def _set_weights_and_bonds(self): [neuron.bonds for neuron in self.neurons], "bonds" ) + def _process_stakes( + self, + neurons: list[NeuronInfo], + subnet_state: SubnetState, + ) -> tuple[list[float], list[float], list[float]]: + """ + Processes the global_stake, local_stake, and stake_weights based on the neuron's hotkey. + + Args: + neurons (List[NeuronInfo]): List of neurons. + subnet_state (SubnetState): The subnet state containing stake information. + + Returns: + tuple[list[float], list[float], list[float]]: Lists of global_stake, local_stake, and stake_weights. + """ + global_stake_list = [] + local_stake_list = [] + stake_weights_list = [] + hotkey_to_index = { + hotkey: idx for idx, hotkey in enumerate(subnet_state.hotkeys) + } + + for neuron in neurons: + idx = hotkey_to_index.get(neuron.hotkey) + if idx is not None: + global_stake_list.append(subnet_state.global_stake[idx].tao) + local_stake_list.append(subnet_state.local_stake[idx].tao) + stake_weights_list.append(subnet_state.stake_weight[idx]) + else: + global_stake_list.append(0.0) + local_stake_list.append(0.0) + stake_weights_list.append(0.0) + + return global_stake_list, local_stake_list, stake_weights_list + def _process_weights_or_bonds(self, data, attribute: str) -> NDArray: """ Processes the raw weights or bonds data and converts it into a structured tensor format. This method handles diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 7e539a90..75579694 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -752,6 +752,7 @@ async def register( ) +# TODO: Confirm emissions, incentive, Dividends are to be fetched from subnet_state or keep NeuronInfo async def metagraph_cmd( subtensor: Optional["SubtensorInterface"], netuid: Optional[int], @@ -788,15 +789,27 @@ async def metagraph_cmd( ), subtensor.substrate.get_block_number(block_hash=block_hash), ) + subnet_state: "SubnetState" = SubnetState.from_vec_u8( + ( + await subtensor.substrate.rpc_request( + method="subnetInfo_getSubnetState", params=[netuid, None] + ) + )["result"] + ) difficulty = int(difficulty_) total_issuance = Balance.from_rao(total_issuance_) metagraph = MiniGraph( - netuid=netuid, neurons=neurons, subtensor=subtensor, block=block + netuid=netuid, + neurons=neurons, + subtensor=subtensor, + subnet_state=subnet_state, + block=block, ) table_data = [] db_table = [] - total_stake = 0.0 + total_global_stake = 0.0 + total_local_stake = 0.0 total_rank = 0.0 total_validator_trust = 0.0 total_trust = 0.0 @@ -809,7 +822,9 @@ async def metagraph_cmd( ep = metagraph.axons[uid] row = [ str(neuron.uid), - "{:.5f}".format(metagraph.total_stake[uid]), + "{:.4f}".format(metagraph.global_stake[uid]), + "{:.4f}".format(metagraph.local_stake[uid]), + "{:.4f}".format(metagraph.stake_weights[uid]), "{:.5f}".format(metagraph.ranks[uid]), "{:.5f}".format(metagraph.trust[uid]), "{:.5f}".format(metagraph.consensus[uid]), @@ -830,7 +845,9 @@ async def metagraph_cmd( ] db_row = [ neuron.uid, - float(metagraph.total_stake[uid]), + float(metagraph.global_stake[uid]), + float(metagraph.local_stake[uid]), + float(metagraph.stake_weights[uid]), float(metagraph.ranks[uid]), float(metagraph.trust[uid]), float(metagraph.consensus[uid]), @@ -846,7 +863,8 @@ async def metagraph_cmd( ep.coldkey[:10], ] db_table.append(db_row) - total_stake += metagraph.total_stake[uid] + total_global_stake += metagraph.global_stake[uid] + total_local_stake += metagraph.local_stake[uid] total_rank += metagraph.ranks[uid] total_validator_trust += metagraph.validator_trust[uid] total_trust += metagraph.trust[uid] @@ -856,8 +874,9 @@ async def metagraph_cmd( total_emission += int(metagraph.emission[uid] * 1000000000) table_data.append(row) metadata_info = { - "stake": str(Balance.from_tao(total_stake)), - "total_stake": "\u03c4{:.5f}".format(total_stake), + "total_global_stake": "\u03c4 {:.5f}".format(total_global_stake), + "total_local_stake": f"{Balance.get_unit(netuid)} " + + "{:.5f}".format(total_local_stake), "rank": "{:.5f}".format(total_rank), "validator_trust": "{:.5f}".format(total_validator_trust), "trust": "{:.5f}".format(total_trust), @@ -881,7 +900,9 @@ async def metagraph_cmd( "metagraph", columns=[ ("UID", "INTEGER"), - ("STAKE", "REAL"), + ("GLOBAL_STAKE", "REAL"), + ("LOCAL_STAKE", "REAL"), + ("STAKE_WEIGHT", "REAL"), ("RANK", "REAL"), ("TRUST", "REAL"), ("CONSENSUS", "REAL"), @@ -925,11 +946,23 @@ async def metagraph_cmd( columns=[ {"title": "UID", "field": "UID"}, { - "title": "Stake", - "field": "STAKE", + "title": "Global Stake", + "field": "GLOBAL_STAKE", "formatter": "money", "formatterParams": {"symbol": "τ", "precision": 5}, }, + { + "title": "Local Stake", + "field": "LOCAL_STAKE", + "formatter": "money", + "formatterParams": {"symbol": f"{Balance.get_unit(netuid)}", "precision": 5}, + }, + { + "title": "Stake Weight", + "field": "STAKE_WEIGHT", + "formatter": "money", + "formatterParams": {"precision": 5}, + }, { "title": "Rank", "field": "RANK", @@ -993,19 +1026,40 @@ async def metagraph_cmd( ratio=0.75, ), ), - "STAKE": ( + "GLOBAL_STAKE": ( 1, Column( - "[bold white]STAKE(\u03c4)", - footer=metadata_info["total_stake"], + "[bold white]GLOBAL STAKE(\u03c4)", + footer=metadata_info["total_global_stake"], style="bright_cyan", justify="right", no_wrap=True, + ratio=1.6, + ), + ), + "LOCAL_STAKE": ( + 2, + Column( + f"[bold white]LOCAL STAKE({Balance.get_unit(netuid)})", + footer=metadata_info["total_local_stake"], + style="bright_green", + justify="right", + no_wrap=True, ratio=1.5, ), ), + "STAKE_WEIGHT": ( + 3, + Column( + f"[bold white]WEIGHT (\u03c4x{Balance.get_unit(netuid)})", + style="purple", + justify="right", + no_wrap=True, + ratio=1.3, + ), + ), "RANK": ( - 2, + 4, Column( "[bold white]RANK", footer=metadata_info["rank"], @@ -1016,7 +1070,7 @@ async def metagraph_cmd( ), ), "TRUST": ( - 3, + 5, Column( "[bold white]TRUST", footer=metadata_info["trust"], @@ -1027,7 +1081,7 @@ async def metagraph_cmd( ), ), "CONSENSUS": ( - 4, + 6, Column( "[bold white]CONSENSUS", footer=metadata_info["consensus"], @@ -1038,7 +1092,7 @@ async def metagraph_cmd( ), ), "INCENTIVE": ( - 5, + 7, Column( "[bold white]INCENTIVE", footer=metadata_info["incentive"], @@ -1049,7 +1103,7 @@ async def metagraph_cmd( ), ), "DIVIDENDS": ( - 6, + 8, Column( "[bold white]DIVIDENDS", footer=metadata_info["dividends"], @@ -1060,7 +1114,7 @@ async def metagraph_cmd( ), ), "EMISSION": ( - 7, + 9, Column( "[bold white]EMISSION(\u03c1)", footer=metadata_info["emission"], @@ -1071,7 +1125,7 @@ async def metagraph_cmd( ), ), "VTRUST": ( - 8, + 10, Column( "[bold white]VTRUST", footer=metadata_info["validator_trust"], @@ -1082,21 +1136,21 @@ async def metagraph_cmd( ), ), "VAL": ( - 9, + 11, Column( "[bold white]VAL", justify="center", style="bright_white", no_wrap=True, - ratio=0.4, + ratio=0.7, ), ), "UPDATED": ( - 10, + 12, Column("[bold white]UPDATED", justify="right", no_wrap=True, ratio=1), ), "ACTIVE": ( - 11, + 13, Column( "[bold white]ACTIVE", justify="center", @@ -1106,7 +1160,7 @@ async def metagraph_cmd( ), ), "AXON": ( - 12, + 14, Column( "[bold white]AXON", justify="left", @@ -1116,7 +1170,7 @@ async def metagraph_cmd( ), ), "HOTKEY": ( - 13, + 15, Column( "[bold white]HOTKEY", justify="center", @@ -1126,7 +1180,7 @@ async def metagraph_cmd( ), ), "COLDKEY": ( - 14, + 16, Column( "[bold white]COLDKEY", justify="center", @@ -1159,7 +1213,7 @@ async def metagraph_cmd( f"Net: [bright_cyan]{metadata_info['net']}[/bright_cyan], " f"Block: [bright_cyan]{metadata_info['block']}[/bright_cyan], " f"N: [bright_green]{metadata_info['N0']}[/bright_green]/[bright_red]{metadata_info['N1']}[/bright_red], " - f"Stake: [dark_orange]{metadata_info['stake']}[/dark_orange], " + f"Total Local Stake: [dark_orange]{metadata_info['total_local_stake']}[/dark_orange], " f"Issuance: [bright_blue]{metadata_info['issuance']}[/bright_blue], " f"Difficulty: [bright_cyan]{metadata_info['difficulty']}[/bright_cyan]\n" ), From 83690256f3a0c282c4d37e2c3b81ba5665041b1b Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 23 Oct 2024 17:20:18 -0700 Subject: [PATCH 045/157] Fixed mandatory netuid + help for s show --- bittensor_cli/cli.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 2b0e6163..c03c1ee4 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -159,12 +159,10 @@ class Options: "-n", help="Set the netuid(s) to exclude. Separate multiple netuids with a comma, for example: `-n 0,1,2`.", ) - netuid = ( - typer.Option( - None, - help="The netuid of the subnet in the root network, (e.g. 1).", - prompt=True, - ), + netuid = typer.Option( + None, + help="The netuid of the subnet in the root network, (e.g. 1).", + prompt=True, ) netuid_not_req = typer.Option( None, @@ -710,6 +708,9 @@ def __init__(self): self.subnets_app.command( "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] )(self.subnets_metagraph) + self.subnets_app.command( + "show", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] + )(self.subnets_show) # weights commands self.weights_app.command( @@ -3354,6 +3355,13 @@ def subnets_show( verbose: bool = Options.verbose, prompt: bool = Options.prompt, ): + """ + Displays detailed information about a subnet including participants and their state. + + EXAMPLE + + [green]$[/green] btcli subnets list + """ self.verbosity_handler(quiet, verbose) subtensor = self.initialize_chain(network) return self._run_command( From bb508a540024db8c3ed3e501c93af0e7122df37d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 23 Oct 2024 22:47:15 -0700 Subject: [PATCH 046/157] btcli st list fixed --- bittensor_cli/cli.py | 3 ++ bittensor_cli/src/bittensor/chain_data.py | 5 ++ bittensor_cli/src/commands/stake/stake.py | 64 +++++++++++------------ 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index c03c1ee4..ec75bcba 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -645,6 +645,9 @@ def __init__(self): self.stake_app.command( "remove", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] )(self.stake_remove) + self.stake_app.command( + "list", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] + )(self.stake_list) # stake-children commands children_app = typer.Typer() diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 347b9f09..c2236119 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -1462,7 +1462,12 @@ def decode(result: list[int]) -> list[dict]: "type_mapping": [ ["hotkey", "AccountId"], ["coldkey", "AccountId"], + ["netuid", "Compact"], ["stake", "Compact"], + ["locked", "Compact"], + ["emission", "Compact"], + ["drain", "Compact"], + ["is_registered", "bool"], ], }, "DynamicInfo": { diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 541fcded..e7bff212 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1612,40 +1612,40 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): hotkeys_to_substakes[hotkey] = [] hotkeys_to_substakes[hotkey].append(substake) - # Iterate over each hotkey and make a table - all_hotkeys_total_global_tao = Balance(0) - all_hotkeys_total_tao_value = Balance(0) - for hotkey in hotkeys_to_substakes.keys(): - stake, value = table_substakes(hotkey, hotkeys_to_substakes[hotkey]) - all_hotkeys_total_global_tao += stake - all_hotkeys_total_tao_value += value - - console.print("\n\n") - console.print( - f"Wallet:\n" - f" Coldkey SS58: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n" - f" Free Balance: [aquamarine3]{balance}[/aquamarine3]\n" - f" Total TAO ({Balance.unit}): [aquamarine3]{all_hotkeys_total_global_tao}[/aquamarine3]\n" - f" Total Value ({Balance.unit}): [aquamarine3]{all_hotkeys_total_tao_value}[/aquamarine3]" - ) - console.print( - """ + # Iterate over each hotkey and make a table + all_hotkeys_total_global_tao = Balance(0) + all_hotkeys_total_tao_value = Balance(0) + for hotkey in hotkeys_to_substakes.keys(): + stake, value = table_substakes(hotkey, hotkeys_to_substakes[hotkey]) + all_hotkeys_total_global_tao += stake + all_hotkeys_total_tao_value += value + + console.print("\n\n") + console.print( + f"Wallet:\n" + f" Coldkey SS58: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n" + f" Free Balance: [aquamarine3]{balance}[/aquamarine3]\n" + f" Total TAO ({Balance.unit}): [aquamarine3]{all_hotkeys_total_global_tao}[/aquamarine3]\n" + f" Total Value ({Balance.unit}): [aquamarine3]{all_hotkeys_total_tao_value}[/aquamarine3]" + ) + console.print( + """ [bold white]Description[/bold white]: - Each table displays information about your coldkey's staking accounts with a hotkey. - The header of the table displays the hotkey and the footer displays the total stake and total value of all your staking accounts. - The columns of the table are as follows: - - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). - - [bold white]Symbol[/bold white]: The symbol representing the subnet stake's unit. - - [bold white]TAO[/bold white]: The hotkey's TAO balance on this subnet. This is this hotkey's proportion of total TAO staked into the subnet divided by the hotkey's share of outstanding stake. - - [bold white]Stake[/bold white]: The hotkey's stake balance in subnets staking unit. - - [bold white]Rate[/bold white]: The rate of exchange between the subnet's staking unit and the subnet's TAO. - - [bold white]Value[/bold white]: The price of the hotkey's stake in TAO computed via the exchange rate. - - [bold white]Swap[/bold white]: The amount of TAO received when unstaking all of the hotkey's stake (with slippage). - - [bold white]Registered[/bold white]: Whether the hotkey is registered on this subnet. - - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. - - [bold white]Locked[/bold white]: The total amount of stake locked (not able to be unstaked). +Each table displays information about your coldkey's staking accounts with a hotkey. +The header of the table displays the hotkey and the footer displays the total stake and total value of all your staking accounts. +The columns of the table are as follows: + - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). + - [bold white]Symbol[/bold white]: The symbol representing the subnet stake's unit. + - [bold white]TAO[/bold white]: The hotkey's TAO balance on this subnet. This is this hotkey's proportion of total TAO staked into the subnet divided by the hotkey's share of outstanding stake. + - [bold white]Stake[/bold white]: The hotkey's stake balance in subnets staking unit. + - [bold white]Rate[/bold white]: The rate of exchange between the subnet's staking unit and the subnet's TAO. + - [bold white]Value[/bold white]: The price of the hotkey's stake in TAO computed via the exchange rate. + - [bold white]Swap[/bold white]: The amount of TAO received when unstaking all of the hotkey's stake (with slippage). + - [bold white]Registered[/bold white]: Whether the hotkey is registered on this subnet. + - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. + - [bold white]Locked[/bold white]: The total amount of stake locked (not able to be unstaked). """ - ) + ) async def move_stake( From f8e51aa1e4e8676150bb07308bbcb78e8acee14a Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 23 Oct 2024 23:17:36 -0700 Subject: [PATCH 047/157] st move [wip] --- bittensor_cli/cli.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ec75bcba..da78ce70 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -648,6 +648,9 @@ def __init__(self): self.stake_app.command( "list", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] )(self.stake_list) + self.stake_app.command( + "move", rich_help_panel=HELP_PANELS["STAKE"]["STAKE_MGMT"] + )(self.stake_move) # stake-children commands children_app = typer.Typer() @@ -2750,22 +2753,36 @@ def stake_move( wallet_hotkey=Options.wallet_hotkey, origin_netuid: int = typer.Option(help="Origin netuid", prompt=True), destination_netuid: int = typer.Option(help="Destination netuid", prompt=True), - destination_hotkey: str = typer.Option( # TODO also accept name - help="Destination hotkey", prompt=True + destination_hotkey: Optional[str] = typer.Option( + None, help="Destination hotkey", prompt=False + ), + amount: float = typer.Option( + 0.0, + "--amount", + help="The amount of TAO to stake", + prompt=True, ), - amount: Optional[float] = typer.Option(help="Amount", prompt=False), stake_all: bool = typer.Option( False, "--stake-all", "--all", help="Stake all", prompt=False ), prompt: bool = Options.prompt, ): + # TODO: Improve logic of moving stake (dest hotkey) + ask_for = ( + [WO.NAME, WO.PATH] if destination_hotkey else [WO.NAME, WO.HOTKEY, WO.PATH] + ) + validate = WV.WALLET if destination_hotkey else WV.WALLET_AND_HOTKEY + wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME], - validate=WV.WALLET_AND_HOTKEY, + ask_for=ask_for, + validate=validate, ) + if not destination_hotkey: + destination_hotkey = wallet.hotkey.ss58_address + return self._run_command( stake.move_stake( subtensor=self.initialize_chain(network), From 1ed2bb0a36064a3db89eb3bc0d303a8dfe157967 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 15:53:29 +0200 Subject: [PATCH 048/157] Improve error handling. --- bittensor_cli/cli.py | 2 +- .../src/bittensor/subtensor_interface.py | 6 +++--- bittensor_cli/src/commands/stake/stake.py | 18 ++++++++++++++---- bittensor_cli/src/commands/subnets.py | 5 ++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index da78ce70..d8bd7d81 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -290,7 +290,7 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in if netuid is None and all_netuids is True: return None elif netuid is None and all_netuids is False: - return typer.prompt( + return Prompt.ask( "Enter the netuid to use. Leave blank for all netuids.", default=None, show_default=False, diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 74fd7a78..e1b9fee6 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1244,14 +1244,14 @@ async def multi_get_stake_for_coldkey_and_hotkey_on_netuid( results: dict[str, dict[int, "Balance"]] = { hk_ss58: {} for hk_ss58 in hotkey_ss58s } - for idx, item in enumerate(batch_call): + for idx, (_, val) in enumerate(batch_call): hotkey_idx = idx // len(netuids) netuid_idx = idx % len(netuids) hotkey_ss58 = hotkey_ss58s[hotkey_idx] netuid = netuids[netuid_idx] value = ( - Balance.from_rao(item).set_unit(netuid) - if item is not None + Balance.from_rao(val).set_unit(netuid) + if val is not None else Balance(0).set_unit(netuid) ) results[hotkey_ss58][netuid] = value diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index e7bff212..21d42345 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -11,6 +11,7 @@ from rich.prompt import Confirm, FloatPrompt, Prompt from rich.table import Table, Column import typer +from substrateinterface.exceptions import SubstrateRequestException from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import StakeInfo @@ -1069,6 +1070,9 @@ async def stake_add( return False async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): + failure_prelude = ( + f":cross_mark: [red]Failed[/red] to stake {amount} on Netuid {netuid_i}" + ) call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="add_stake", @@ -1081,9 +1085,15 @@ async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): extrinsic = await subtensor.substrate.create_signed_extrinsic( call=call, keypair=wallet.coldkey ) - response = await subtensor.substrate.submit_extrinsic( - extrinsic, wait_for_inclusion=True, wait_for_finalization=False - ) + try: + response = await subtensor.substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=True, wait_for_finalization=False + ) + except SubstrateRequestException as e: + err_console.print( + f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}" + ) + return if not prompt: # TODO verbose? console.print( f":white_heavy_check_mark: [green]Submitted {amount_} to {netuid_i}[/green]" @@ -1092,7 +1102,7 @@ async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): await response.process_events() if not await response.is_success: err_console.print( - f":cross_mark: [red]Failed[/red] with error: {response.error_message}" + f"\n{failure_prelude} with error: {response.error_message}" ) else: new_balance_, new_stake_ = await asyncio.gather( diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 75579694..6a21c09f 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -955,7 +955,10 @@ async def metagraph_cmd( "title": "Local Stake", "field": "LOCAL_STAKE", "formatter": "money", - "formatterParams": {"symbol": f"{Balance.get_unit(netuid)}", "precision": 5}, + "formatterParams": { + "symbol": f"{Balance.get_unit(netuid)}", + "precision": 5, + }, }, { "title": "Stake Weight", From b2bd25cbdc484911b1f78b823dc2c0a428a5dff5 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 17:34:01 +0200 Subject: [PATCH 049/157] [WIP] Stake Add --- bittensor_cli/src/commands/stake/stake.py | 50 ++++++++++++++--------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 21d42345..6a783aab 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -3,6 +3,7 @@ import json import sqlite3 from contextlib import suppress +from functools import partial from typing import TYPE_CHECKING, Optional, Sequence, Union, cast @@ -1069,7 +1070,10 @@ async def stake_add( if not Confirm.ask("Would you like to continue?"): return False - async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): + async def send_extrinsic( + netuid_i, amount_, current, staking_address_ss58, status=None + ): + err_out = partial(print_error, status=status) failure_prelude = ( f":cross_mark: [red]Failed[/red] to stake {amount} on Netuid {netuid_i}" ) @@ -1090,7 +1094,7 @@ async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): extrinsic, wait_for_inclusion=True, wait_for_finalization=False ) except SubstrateRequestException as e: - err_console.print( + err_out( f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}" ) return @@ -1101,8 +1105,8 @@ async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): else: await response.process_events() if not await response.is_success: - err_console.print( - f"\n{failure_prelude} with error: {response.error_message}" + err_out( + f"\n{failure_prelude} with error: {format_error_message(await response.error_message, subtensor.substrate)}" ) else: new_balance_, new_stake_ = await asyncio.gather( @@ -1124,23 +1128,31 @@ async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): # Perform staking operation. wallet.unlock_coldkey() - with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ..."): - extrinsics_coroutines = [ - send_extrinsic(ni, am, curr, staking_address) - for (ni, am, curr) in zip( - netuids, stake_amount_balance, current_stake_balances - ) - for _, staking_address in hotkeys_to_stake_to - ] - if len(extrinsics_coroutines) == 1: - await asyncio.gather(*extrinsics_coroutines) - else: + extrinsics_coroutines = [ + send_extrinsic(ni, am, curr, staking_address) + for i, (ni, am, curr) in enumerate( + zip(netuids, stake_amount_balance, current_stake_balances) + ) + for _, staking_address in hotkeys_to_stake_to + ] + if len(extrinsics_coroutines) == 1: + with console.status( + f"\n:satellite: Staking on netuid(s): {netuids} ..." + ) as status: + await extrinsics_coroutines[0] + else: + with console.status(":satellite: Checking transaction rate limit ..."): tx_rate_limit_blocks = await subtensor.substrate.query( module="SubtensorModule", storage_function="TxRateLimit" ) - if tx_rate_limit_blocks > 0: - for item in extrinsics_coroutines: - await item + netuid_hk_pairs = [(ni, hk) for ni in netuids for hk in hotkeys_to_stake_to] + for item, kp in zip(extrinsics_coroutines, netuid_hk_pairs): + ni, hk = kp + with console.status( + f"\n:satellite: Staking on netuid {ni} with hotkey {hk}... ..." + ): + await item + if tx_rate_limit_blocks > 0: with console.status( f":hourglass: [yellow]Waiting for tx rate limit:" f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]" @@ -1148,8 +1160,6 @@ async def send_extrinsic(netuid_i, amount_, current, staking_address_ss58): await asyncio.sleep( tx_rate_limit_blocks * 12 ) # 12 sec per block - else: - await asyncio.gather(*extrinsics_coroutines) async def unstake( From 1ced08dec514d3da4f5860e894e28287cf15a314 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 24 Oct 2024 09:39:02 -0700 Subject: [PATCH 050/157] fix netuid from str to int --- bittensor_cli/src/commands/stake/stake.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 6a783aab..b47bd931 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1177,7 +1177,7 @@ async def unstake( ): """Unstake token of amount from hotkey(s).""" netuids = ( - [netuid] if netuid is not None else await subtensor.get_all_subnet_netuids() + [int(netuid)] if netuid is not None else await subtensor.get_all_subnet_netuids() ) # Get the hotkey_names (if any) and the hotkey_ss58s. hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = [] @@ -1352,7 +1352,7 @@ async def unstake( f"Amount ({Balance.get_unit(1)})", justify="center", style="dark_sea_green" ) table.add_column( - f"Rate ({Balance.get_unit(0)}/{bt.Balance.get_unit(1)})", + f"Rate ({Balance.get_unit(0)}/{Balance.get_unit(1)})", justify="center", style="light_goldenrod2", ) @@ -1365,7 +1365,7 @@ async def unstake( table.add_column("Slippage", justify="center", style="rgb(220,50,47)") for row in rows: table.add_row(*row) - bt.__console__.print(table) + console.print(table) message = "" if max_float_slippage > 5: message += f"-------------------------------------------------------------------------------------------------------------------\n" From f682bdddfcca416bfa85683ca189d4268edb1b21 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 24 Oct 2024 09:49:12 -0700 Subject: [PATCH 051/157] for `stake_add` fix netuid from str to int --- bittensor_cli/src/commands/stake/stake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index b47bd931..90eee19c 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -860,7 +860,7 @@ async def stake_add( """ netuids = ( - [netuid] if netuid is not None else await subtensor.get_all_subnet_netuids() + [int(netuid)] if netuid is not None else await subtensor.get_all_subnet_netuids() ) # Init the table. table = Table( From b3e774cf703d89ff80ccf1a22facffd1af8e21b7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 18:56:07 +0200 Subject: [PATCH 052/157] Convert netuid ask to int. --- bittensor_cli/cli.py | 6 +++++- bittensor_cli/src/commands/stake/stake.py | 18 +++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d8bd7d81..b158692f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -290,11 +290,15 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in if netuid is None and all_netuids is True: return None elif netuid is None and all_netuids is False: - return Prompt.ask( + answer = Prompt.ask( "Enter the netuid to use. Leave blank for all netuids.", default=None, show_default=False, ) + if answer is None: + return None + else: + return int(answer) else: return netuid diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 6a783aab..7f4f16dc 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1138,7 +1138,7 @@ async def send_extrinsic( if len(extrinsics_coroutines) == 1: with console.status( f"\n:satellite: Staking on netuid(s): {netuids} ..." - ) as status: + ): await extrinsics_coroutines[0] else: with console.status(":satellite: Checking transaction rate limit ..."): @@ -1152,14 +1152,14 @@ async def send_extrinsic( f"\n:satellite: Staking on netuid {ni} with hotkey {hk}... ..." ): await item - if tx_rate_limit_blocks > 0: - with console.status( - f":hourglass: [yellow]Waiting for tx rate limit:" - f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]" - ): - await asyncio.sleep( - tx_rate_limit_blocks * 12 - ) # 12 sec per block + if tx_rate_limit_blocks > 0: + with console.status( + f":hourglass: [yellow]Waiting for tx rate limit:" + f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]" + ): + await asyncio.sleep( + tx_rate_limit_blocks * 12 + ) # 12 sec per block async def unstake( From 3470d3ace9f48160f680f2176dcdfa45ea609e8f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 19:19:56 +0200 Subject: [PATCH 053/157] Correctly use staking address in unstake. --- bittensor_cli/src/commands/stake/stake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index c35bfa80..dfdefb09 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1272,7 +1272,7 @@ async def unstake( current_stake_balance: Balance = ( await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58_address, + hotkey_ss58=staking_address_ss58, netuid=netuid, ) ) From a2e30073845d7a9ddf2af9466f4b07e748a3d006 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 19:50:52 +0200 Subject: [PATCH 054/157] Correctly use staking address for name in all parts of unstake. --- bittensor_cli/src/commands/stake/stake.py | 104 +++++++++++----------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index dfdefb09..59760ac5 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -860,7 +860,9 @@ async def stake_add( """ netuids = ( - [int(netuid)] if netuid is not None else await subtensor.get_all_subnet_netuids() + [int(netuid)] + if netuid is not None + else await subtensor.get_all_subnet_netuids() ) # Init the table. table = Table( @@ -1136,9 +1138,7 @@ async def send_extrinsic( for _, staking_address in hotkeys_to_stake_to ] if len(extrinsics_coroutines) == 1: - with console.status( - f"\n:satellite: Staking on netuid(s): {netuids} ..." - ): + with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ..."): await extrinsics_coroutines[0] else: with console.status(":satellite: Checking transaction rate limit ..."): @@ -1157,9 +1157,7 @@ async def send_extrinsic( f":hourglass: [yellow]Waiting for tx rate limit:" f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]" ): - await asyncio.sleep( - tx_rate_limit_blocks * 12 - ) # 12 sec per block + await asyncio.sleep(tx_rate_limit_blocks * 12) # 12 sec per block async def unstake( @@ -1177,7 +1175,9 @@ async def unstake( ): """Unstake token of amount from hotkey(s).""" netuids = ( - [int(netuid)] if netuid is not None else await subtensor.get_all_subnet_netuids() + [int(netuid)] + if netuid is not None + else await subtensor.get_all_subnet_netuids() ) # Get the hotkey_names (if any) and the hotkey_ss58s. hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = [] @@ -1394,51 +1394,53 @@ async def unstake( with console.status( f"\n:satellite: Unstaking {amount_to_unstake_as_balance} from {staking_address_name} on netuid: {netuid} ..." ): - for netuid_i, amount, current in list( - zip(non_zero_netuids, unstake_amount_balance, current_stake_balances) - ): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": hotkey_ss58_address, - "netuid": netuid_i, - "amount_unstaked": amount.rao, - }, - ) - extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = await subtensor.substrate.submit_extrinsic( - extrinsic, wait_for_inclusion=True, wait_for_finalization=False - ) - if not prompt: - console.print(":white_heavy_check_mark: [green]Sent[/green]") - else: - await response.process_events() - if not await response.is_success: - err_console.print( - f":cross_mark: [red]Failed[/red] with error: " - f"{format_error_message(response.error_message, subtensor.substrate)}" - ) + for hotkey in hotkeys_to_unstake_from: + staking_address_name, staking_address_ss58 = hotkey + for netuid_i, amount, current in list( + zip(non_zero_netuids, unstake_amount_balance, current_stake_balances) + ): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": staking_address_ss58, + "netuid": netuid_i, + "amount_unstaked": amount.rao, + }, + ) + extrinsic = await subtensor.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = await subtensor.substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=True, wait_for_finalization=False + ) + if not prompt: + console.print(":white_heavy_check_mark: [green]Sent[/green]") else: - new_balance_ = await subtensor.get_balance( - wallet.coldkeypub.ss58_address - ) - new_balance = new_balance_[wallet.coldkeypub.ss58_address] - new_stake = ( - await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58_address, - netuid=netuid_i, + await response.process_events() + if not await response.is_success: + err_console.print( + f":cross_mark: [red]Failed[/red] with error: " + f"{format_error_message(response.error_message, subtensor.substrate)}" + ) + else: + new_balance_ = await subtensor.get_balance( + wallet.coldkeypub.ss58_address + ) + new_balance = new_balance_[wallet.coldkeypub.ss58_address] + new_stake = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=staking_address_ss58, + netuid=netuid_i, + ) + ).set_unit(netuid_i) + console.print( + f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + console.print( + f"Subnet: {netuid_i} Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" ) - ).set_unit(netuid_i) - console.print( - f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - console.print( - f"Subnet: {netuid_i} Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" - ) async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): From 7f0b5ae60b0fb3cc145f0b5077af3367f5a1f020 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 24 Oct 2024 11:26:00 -0700 Subject: [PATCH 055/157] Shows free balance in staking, fixes hotkey display in table --- bittensor_cli/cli.py | 31 +++++++++++++++++++---- bittensor_cli/src/commands/stake/stake.py | 2 +- bittensor_cli/src/commands/wallets.py | 1 + 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index b158692f..9597336f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -291,7 +291,7 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in return None elif netuid is None and all_netuids is False: answer = Prompt.ask( - "Enter the netuid to use. Leave blank for all netuids.", + "[green]Enter the netuid to use. Leave blank for all netuids", default=None, show_default=False, ) @@ -2496,9 +2496,6 @@ def stake_add( ) raise typer.Exit() - if not stake_all and not amount and not max_stake: - amount = FloatPrompt.ask("[blue bold]Amount to stake (TAO τ)[/blue bold]") - if stake_all and not amount: if not Confirm.ask("Stake all the available TAO tokens?", default=False): raise typer.Exit() @@ -2518,7 +2515,7 @@ def stake_add( if not wallet_hotkey and not all_hotkeys and not include_hotkeys: hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to [Press Enter to use config values]", ) if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 @@ -2570,6 +2567,30 @@ def stake_add( else: excluded_hotkeys = [] + # TODO: Ask amount for each subnet explicitly if more than one + if not stake_all and not amount and not max_stake: + if netuid is not None: + free_balance, staked_balance = self._run_command( + wallets.wallet_balance( + wallet, self.initialize_chain(network), False, None + ) + ) + if free_balance == Balance.from_tao(0): + print_error("You dont have any balance to stake.") + raise typer.Exit() + amount = FloatPrompt.ask( + "[dark_orange]Amount to stake (TAO τ)[/dark_orange]" + ) + if Balance.from_tao(amount) > free_balance: + print_error( + f"You dont have enough balance to stake. Current free Balance: {free_balance}." + ) + raise typer.Exit() + else: + amount = FloatPrompt.ask( + "[dark_orange]Amount to stake to each netuid (TAO τ)[/dark_orange]" + ) + return self._run_command( stake.stake_add( wallet, diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 59760ac5..56268daa 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1022,7 +1022,7 @@ async def stake_add( ( str(netuid), # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", - f"{hotkey}", + f"{hotkey[1]}", str(amount_to_stake_as_balance), str(1 / float(dynamic_info.price)) + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 3faefd23..daf17c25 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -318,6 +318,7 @@ async def wallet_balance( ) console.print(Padding(table, (0, 0, 0, 4))) await subtensor.substrate.close() + return total_free_balance, total_staked_balance async def get_wallet_transfers(wallet_address: str) -> list[dict]: From d9ed6ad237918bf4b367df2dadbe74a7c583c11f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 24 Oct 2024 11:26:12 -0700 Subject: [PATCH 056/157] ruff --- bittensor_cli/cli.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 9597336f..c94b3661 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2569,27 +2569,27 @@ def stake_add( # TODO: Ask amount for each subnet explicitly if more than one if not stake_all and not amount and not max_stake: - if netuid is not None: - free_balance, staked_balance = self._run_command( - wallets.wallet_balance( - wallet, self.initialize_chain(network), False, None - ) + free_balance, staked_balance = self._run_command( + wallets.wallet_balance( + wallet, self.initialize_chain(network), False, None ) - if free_balance == Balance.from_tao(0): - print_error("You dont have any balance to stake.") - raise typer.Exit() + ) + if free_balance == Balance.from_tao(0): + print_error("You dont have any balance to stake.") + raise typer.Exit() + if netuid is not None: amount = FloatPrompt.ask( "[dark_orange]Amount to stake (TAO τ)[/dark_orange]" ) - if Balance.from_tao(amount) > free_balance: - print_error( - f"You dont have enough balance to stake. Current free Balance: {free_balance}." - ) - raise typer.Exit() else: amount = FloatPrompt.ask( "[dark_orange]Amount to stake to each netuid (TAO τ)[/dark_orange]" ) + if Balance.from_tao(amount) > free_balance: + print_error( + f"You dont have enough balance to stake. Current free Balance: {free_balance}." + ) + raise typer.Exit() return self._run_command( stake.stake_add( From cc201e0cc21bcc02164fa4331d735376a163ccfc Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 21:33:47 +0200 Subject: [PATCH 057/157] Changed get_all_subnet_dynamic_info to use Runtime API --- bittensor_cli/src/__init__.py | 4 ++++ bittensor_cli/src/bittensor/chain_data.py | 6 ++++-- bittensor_cli/src/bittensor/subtensor_interface.py | 11 ++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index a4c749fd..3ab18fe2 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -278,6 +278,10 @@ class WalletValidationTypes(Enum): "params": [], "type": "Vec", }, + "get_all_dynamic_info": { + "params": [], + "type": "Vec", + }, } }, "SubnetRegistrationRuntimeApi": { diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index c2236119..bee31608 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -44,7 +44,9 @@ def from_scale_encoding_using_type_string( elif isinstance(input_, bytes): as_bytes = input_ else: - raise TypeError("input must be a List[int], bytes, or ScaleBytes") + raise TypeError( + f"input must be a list[int], bytes, or ScaleBytes, not {type(input_)}" + ) as_scale_bytes = ScaleBytes(as_bytes) rpc_runtime_config = RuntimeConfiguration() rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy")) @@ -853,7 +855,7 @@ def from_vec_u8(cls, vec_u8: list[int]) -> Optional["DynamicInfo"]: return DynamicInfo.fix_decoded_values(decoded) @classmethod - def list_from_vec_u8(cls, vec_u8: list[int]) -> list["DynamicInfo"]: + def list_from_vec_u8(cls, vec_u8: Union[list[int], bytes]) -> list["DynamicInfo"]: decoded = from_scale_encoding( vec_u8, ChainDataType.DynamicInfo, is_vec=True, is_option=True ) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index e1b9fee6..73259ba9 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -385,15 +385,12 @@ async def get_total_stake_for_coldkey( sub_stakes = await self.get_stake_info_for_coldkeys( ss58_addresses, block_hash=block_hash ) - # Token pricing info dynamic_info = await self.get_all_subnet_dynamic_info() results = {} - for ss58, stake_info_list in sub_stakes.items(): all_staked_tao = 0 - for sub_stake in stake_info_list: if sub_stake.stake.rao == 0: continue @@ -417,7 +414,6 @@ async def get_total_stake_for_coldkey( all_staked_tao += tao_ownership.rao results[ss58] = Balance.from_rao(all_staked_tao) - return results async def get_total_stake_for_hotkey( @@ -1296,8 +1292,9 @@ async def get_stake_info_for_coldkeys( return StakeInfo.list_of_tuple_from_vec_u8(bytes_result) # type: ignore async def get_all_subnet_dynamic_info(self) -> list["DynamicInfo"]: - json = await self.substrate.rpc_request( - method="subnetInfo_getAllDynamicInfo", params=[None] + query = await self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_all_dynamic_info", ) - subnets = DynamicInfo.list_from_vec_u8(json["result"]) + subnets = DynamicInfo.list_from_vec_u8(bytes.fromhex(query.decode()[2:])) return subnets From 2580614e37ca3efdb5fa70a2b7d8971690649bc4 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 16:40:32 -0400 Subject: [PATCH 058/157] add runtime apis to reg --- bittensor_cli/src/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 3ab18fe2..32ca0e0f 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -278,10 +278,27 @@ class WalletValidationTypes(Enum): "params": [], "type": "Vec", }, + "get_subnet_info_v2": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + }, + "get_subnets_info_v2": { + "params": [], + "type": "Vec", + }, "get_all_dynamic_info": { "params": [], "type": "Vec", }, + "get_dynamic_info": { + "params": [{"name": "netuid", "type": "u16"}], + "type": "Vec", + }, } }, "SubnetRegistrationRuntimeApi": { From 80db62252635b9c23f8106f843d978087def55fa Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 16:43:01 -0400 Subject: [PATCH 059/157] use runtime api for neuron --- .../src/bittensor/subtensor_interface.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 73259ba9..02c3271f 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -730,17 +730,23 @@ async def neuron_for_uid( """ if uid is None: return NeuronInfo.get_null_neuron() - - params = [netuid, uid, block_hash] if block_hash else [netuid, uid] - json_body = await self.substrate.rpc_request( - method="neuronInfo_getNeuron", - params=params, # custom rpc method + + hex_bytes_result = await self.query_runtime_api( + runtime_api="NeuronInfoRuntimeApi", + method="get_neuron", + params=[netuid, uid], + block_hash=block_hash, ) - if not (result := json_body.get("result", None)): + + if not (result := hex_bytes_result): return NeuronInfo.get_null_neuron() - bytes_result = bytes(result) + if result.startswith("0x"): + bytes_result = bytes.fromhex(result[2:]) + else: + bytes_result = bytes.fromhex(result) + return NeuronInfo.from_vec_u8(bytes_result) async def get_delegated( From f3c7bd6dd845ca19a908b456566c73d01603228e Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 16:44:06 -0400 Subject: [PATCH 060/157] get delegated runtime api --- .../src/bittensor/subtensor_interface.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 02c3271f..de035864 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -775,15 +775,23 @@ async def get_delegated( else (self.substrate.last_block_hash if reuse_block else None) ) encoded_coldkey = ss58_to_vec_u8(coldkey_ss58) - json_body = await self.substrate.rpc_request( - method="delegateInfo_getDelegated", - params=([block_hash, encoded_coldkey] if block_hash else [encoded_coldkey]), + + hex_bytes_result = await self.query_runtime_api( + runtime_api="DelegateInfoRuntimeApi", + method="get_delegated", + params=[encoded_coldkey], + block_hash=block_hash, ) - if not (result := json_body.get("result")): + if not (result := hex_bytes_result): return [] - return DelegateInfo.delegated_list_from_vec_u8(bytes(result)) + if result.startswith("0x"): + bytes_result = bytes.fromhex(result[2:]) + else: + bytes_result = bytes.fromhex(result) + + return DelegateInfo.delegated_list_from_vec_u8(bytes_result) async def query_identity( self, From e03d4796da9d45310f7af94a3166885cef16ea9c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 16:45:26 -0400 Subject: [PATCH 061/157] add todo --- bittensor_cli/src/bittensor/subtensor_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index de035864..e5d72647 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1167,7 +1167,7 @@ async def get_delegates_by_netuid_light( A list of DelegateInfo objects detailing each delegate's characteristics. """ - + # TODO (Ben): doesn't exist params = [netuid] if not block_hash else [netuid, block_hash] json_body = await self.substrate.rpc_request( method="delegateInfo_getDelegatesLight", # custom rpc method From 1368bd6f007596b0850dc72b83885871991ebe36 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Oct 2024 23:10:22 +0200 Subject: [PATCH 062/157] Update balance to show non-Tao on the right side of the number. --- bittensor_cli/src/bittensor/balances.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/balances.py b/bittensor_cli/src/bittensor/balances.py index bc935f01..4a795531 100644 --- a/bittensor_cli/src/bittensor/balances.py +++ b/bittensor_cli/src/bittensor/balances.py @@ -73,7 +73,10 @@ def __str__(self): """ Returns the Balance object as a string in the format "symbolvalue", where the value is in tao. """ - return f"{self.unit}{float(self.tao):,.9f}" + if self.unit == UNITS[0]: + return f"{self.unit} {float(self.tao):,.4f}" + else: + return f"{float(self.tao):,.4f} {self.unit}\u200e" def __rich__(self): return "[green]{}[/green][green]{}[/green][green].[/green][dim green]{}[/dim green]".format( From eafad578b29fc7908d8878719592e012463e0360 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 17:42:41 -0400 Subject: [PATCH 063/157] use runtime api for get subnet dynamic info --- .../src/bittensor/subtensor_interface.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index e5d72647..28561e54 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1184,10 +1184,22 @@ async def get_delegates_by_netuid_light( async def get_subnet_dynamic_info( self, netuid: int, block_hash: Optional[str] = None ) -> "DynamicInfo": - json = await self.substrate.rpc_request( - method="subnetInfo_getDynamicInfo", params=[netuid, block_hash] + hex_bytes_result = await self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_dynamic_info", + params=[netuid], + block_hash=block_hash, ) - subnets = DynamicInfo.from_vec_u8(json["result"]) + + if hex_bytes_result is None: + return None + + if hex_bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(hex_bytes_result[2:]) + else: + bytes_result = bytes.fromhex(hex_bytes_result) + + subnets = DynamicInfo.from_vec_u8(bytes_result) return subnets async def get_stake_for_coldkey_and_hotkey_on_netuid( From 39fbaf242337dbe0b18bc6633097c3dbe2f6ccbc Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 17:44:43 -0400 Subject: [PATCH 064/157] replace with runtime api --- .../src/bittensor/extrinsics/registration.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index 112ce4fd..86bad3c9 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -493,17 +493,23 @@ async def get_neuron_for_pubkey_and_subnet(): ) if uid is None: return NeuronInfo.get_null_neuron() + - params = [netuid, uid] - json_body = await subtensor.substrate.rpc_request( - method="neuronInfo_getNeuron", - params=params, + hex_bytes_result = await subtensor.substrate.query_runtime_api( + runtime_api="NeuronInfoRuntimeApi", + method="get_neuron", + params=[netuid, uid], ) - if not (result := json_body.get("result", None)): + if not (result := hex_bytes_result): return NeuronInfo.get_null_neuron() + + if result.startswith("0x"): + bytes_result = bytes.fromhex(result[2:]) + else: + bytes_result = bytes.fromhex(result) - return NeuronInfo.from_vec_u8(bytes(result)) + return NeuronInfo.from_vec_u8(bytes_result) print_verbose("Checking subnet status") if not await subtensor.subnet_exists(netuid): From 292434d570080e101a5aa64ea8f30e3cfa1dd2e6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 17:46:16 -0400 Subject: [PATCH 065/157] use runtime api here also --- bittensor_cli/src/commands/wallets.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index daf17c25..1b0fe474 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1114,17 +1114,12 @@ async def _fetch_neuron_for_netuid( """ async def neurons_lite_for_uid(uid: int) -> dict[Any, Any]: - call_definition = TYPE_REGISTRY["runtime_api"]["NeuronInfoRuntimeApi"][ - "methods" - ]["get_neurons_lite"] - data = await subtensor.encode_params( - call_definition=call_definition, params=[uid] - ) block_hash = subtensor.substrate.last_block_hash - hex_bytes_result = await subtensor.substrate.rpc_request( - method="state_call", - params=["NeuronInfoRuntimeApi_get_neurons_lite", data, block_hash], - reuse_block_hash=True, + hex_bytes_result = await subtensor.query_runtime_api( + runtime_api="NeuronInfoRuntimeApi", + method="get_neurons_lite", + params=[uid], + block_hash=block_hash, ) return hex_bytes_result From 6087513c3c766ed00f3ece6999f3527abe7af88e Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 17:50:13 -0400 Subject: [PATCH 066/157] replace with runtime api --- bittensor_cli/src/commands/subnets.py | 57 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 6a21c09f..266f2fb7 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -269,16 +269,22 @@ async def subnets_list( async def show(subtensor: "SubtensorInterface", netuid: int, prompt: bool = True): async def show_root(): all_subnets = await subtensor.get_all_subnet_dynamic_info() - root_state: "SubnetState" = SubnetState.from_vec_u8( - ( - await subtensor.substrate.rpc_request( - method="subnetInfo_getSubnetState", params=[0, None] - ) - )["result"] + + hex_bytes_result = await subtensor.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_state", + params=[0], ) - if root_state is None: + if (bytes_result := hex_bytes_result) is None: err_console.print("The root subnet does not exist") return + + if bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(bytes_result[2:]) + + root_state: "SubnetState" = SubnetState.from_vec_u8( + bytes_result + ) if len(root_state.hotkeys) == 0: err_console.print( "The root-subnet is currently empty with 0 UIDs registered." @@ -373,12 +379,20 @@ async def show_root(): async def show_subnet(netuid_: int): subnet_info = await subtensor.get_subnet_dynamic_info(netuid_) + hex_bytes_result = await subtensor.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_state", + params=[netuid_], + ) + if (bytes_result := hex_bytes_result) is None: + err_console.print(f"Subnet {netuid_} does not exist") + return + + if bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(bytes_result[2:]) + subnet_state: "SubnetState" = SubnetState.from_vec_u8( - ( - await subtensor.substrate.rpc_request( - method="subnetInfo_getSubnetState", params=[netuid_, None] - ) - )["result"] + bytes_result ) if subnet_info is None: err_console.print(f"Subnet {netuid_} does not exist") @@ -789,12 +803,21 @@ async def metagraph_cmd( ), subtensor.substrate.get_block_number(block_hash=block_hash), ) + + hex_bytes_result = await subtensor.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_state", + params=[netuid], + ) + if not (bytes_result := hex_bytes_result): + err_console.print(f"Subnet {netuid} does not exist") + return + + if bytes_result.startswith("0x"): + bytes_result = bytes.fromhex(bytes_result[2:]) + subnet_state: "SubnetState" = SubnetState.from_vec_u8( - ( - await subtensor.substrate.rpc_request( - method="subnetInfo_getSubnetState", params=[netuid, None] - ) - )["result"] + bytes_result ) difficulty = int(difficulty_) From 8642fa269faedc4d3bfbbb2ae4683208a2707242 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 00:04:14 +0200 Subject: [PATCH 067/157] Column footers --- bittensor_cli/src/commands/subnets.py | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 6a21c09f..05f12ab6 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -492,24 +492,28 @@ async def show_subnet(netuid_: int): for idx in range(len(subnet_state.emission)) ] ) + tao_sum = Balance(0) + stake_sum = Balance(0) for idx, hk in enumerate(subnet_state.hotkeys): hotkey_block_emission = ( subnet_state.emission[idx].tao / emission_sum if emission_sum != 0 else 0 ) + tao_sum += subnet_state.global_stake[idx] + stake_sum += subnet_state.local_stake[idx] rows.append( ( - str(idx), - str(subnet_state.global_stake[idx]), - f"{subnet_state.local_stake[idx].tao:.4f} {subnet_info.symbol}", - f"{subnet_state.stake_weight[idx]:.4f}", + str(idx), # UID + str(subnet_state.global_stake[idx]), # TAO + f"{subnet_state.local_stake[idx].tao:.4f} {subnet_info.symbol}", # Stake + f"{subnet_state.stake_weight[idx]:.4f}", # Weight # str(subnet_state.dividends[idx]), - f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", - str(subnet_state.incentives[idx]), - f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", - f"{subnet_state.hotkeys[idx]}", - f"{subnet_state.coldkeys[idx]}", + f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", # Dividends + str(subnet_state.incentives[idx]), # Incentive + f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", # Emission + f"{subnet_state.hotkeys[idx]}", # Hotkey + f"{subnet_state.coldkeys[idx]}", # Coldkey ) ) # Add columns to the table @@ -519,12 +523,14 @@ async def show_subnet(netuid_: int): style="medium_purple", no_wrap=True, justify="right", + footer=str(tao_sum), ) table.add_column( f"Stake({Balance.get_unit(netuid_)})", style="green", no_wrap=True, justify="right", + footer=f"{stake_sum.set_unit(subnet_info.netuid)}", ) table.add_column( f"Weight({Balance.get_unit(0)}•{Balance.get_unit(netuid_)})", @@ -543,6 +549,7 @@ async def show_subnet(netuid_: int): style="aquamarine3", no_wrap=True, justify="center", + footer=str(Balance.from_tao(emission_sum).set_unit(subnet_info.netuid)), ) table.add_column( "Hotkey", style="light_salmon3", no_wrap=True, justify="center" @@ -554,8 +561,8 @@ async def show_subnet(netuid_: int): table.add_row(*row) # Print the table - # bt.__console__.print("\n\n\n") - # bt.__console__.print(subnet_info_table) + # console.print("\n\n\n") + # console.print(subnet_info_table) console.print("\n\n") console.print(table) console.print("\n") From db27dc2bfa7dbe522ba74ef1ac33b78dcfc39203 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 19:57:30 -0400 Subject: [PATCH 068/157] add subnet state to reg --- bittensor_cli/src/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 32ca0e0f..9df23756 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -299,6 +299,10 @@ class WalletValidationTypes(Enum): "params": [{"name": "netuid", "type": "u16"}], "type": "Vec", }, + "get_subnet_state": { + "params": [{"name": "netuid", "type": "u16"}], + "type": "Vec", + }, } }, "SubnetRegistrationRuntimeApi": { From 0f13d4051bd5c10ab5bc8bd6c584d3c24ff46d73 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 24 Oct 2024 17:11:35 -0700 Subject: [PATCH 069/157] Updated btcli s list --- bittensor_cli/cli.py | 9 ++ .../src/bittensor/subtensor_interface.py | 10 ++ bittensor_cli/src/commands/subnets.py | 110 +++++++----------- 3 files changed, 64 insertions(+), 65 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index c94b3661..f20e82f4 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2792,6 +2792,15 @@ def stake_move( ), prompt: bool = Options.prompt, ): + """ + Move Staked TAO to a hotkey from one subnet to another. + + THe move commands converts the origin subnet's dTao to Tao, and then converts Tao to destination subnet's dTao. + + EXAMPLE + + [green]$[/green] btcli stake move + """ # TODO: Improve logic of moving stake (dest hotkey) ask_for = ( [WO.NAME, WO.PATH] if destination_hotkey else [WO.NAME, WO.HOTKEY, WO.PATH] diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 73259ba9..da5fdf92 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -38,6 +38,7 @@ decode_hex_identity_dict, validate_chain_endpoint, u16_normalized_float, + u64_normalized_float, ) @@ -1298,3 +1299,12 @@ async def get_all_subnet_dynamic_info(self) -> list["DynamicInfo"]: ) subnets = DynamicInfo.list_from_vec_u8(bytes.fromhex(query.decode()[2:])) return subnets + + async def get_global_weight(self, netuid: int, block_hash: Optional[str] = None): + global_weight = await self.substrate.query( + module="SubtensorModule", + storage_function="GlobalWeight", + params=[netuid], + block_hash=block_hash, + ) + return u64_normalized_float(global_weight) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 05f12ab6..aa4929a9 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -158,88 +158,66 @@ async def subnets_list( ): """List all subnet netuids in the network.""" # TODO add reuse-last and html-output and no-cache + async def fetch_global_weight(netuid): + try: + return netuid, await subtensor.get_global_weight(netuid) + except Exception as e: + print(f"Error fetching global weight for netuid {netuid}: {e}") + return netuid, None - # Initialize variables to store aggregated data rows = [] + subnets = await subtensor.get_all_subnet_dynamic_info() + netuids = [subnet.netuid for subnet in subnets] + + global_weight_tasks = [fetch_global_weight(netuid) for netuid in netuids] + global_weights = await asyncio.gather(*global_weight_tasks) + + global_weight_dict = {netuid: weight for netuid, weight in global_weights} for subnet in subnets: + netuid = subnet.netuid + global_weight = global_weight_dict.get(netuid) symbol = f"{subnet.symbol}\u200e" + + if netuid == 0: + emission_tao = 0.0 + else: + emission_tao = subnet.emission.tao + rows.append( ( - str(subnet.netuid), + str(netuid), f"[light_goldenrod1]{subnet.symbol}[light_goldenrod1]", - f"τ {subnet.emission.tao:.4f}", - # f"P( τ {subnet.tao_in.tao:,.4f},", + f"τ {emission_tao:.4f}", f"τ {subnet.tao_in.tao:,.4f}", - # f"{subnet.alpha_in.tao:,.4f} {subnet.symbol} )", f"{subnet.alpha_out.tao:,.4f} {symbol}", f"{subnet.price.tao:.4f} τ/{symbol}", - str(subnet.blocks_since_last_step) + "/" + str(subnet.tempo), - # f"{subnet.owner_locked}" + "/" + f"{subnet.total_locked}", - # f"{subnet.owner[:3]}...{subnet.owner[-3:]}", + f"{subnet.blocks_since_last_step}/{subnet.tempo}", + f"{global_weight:.4f}" if global_weight is not None else "N/A", ) ) + total_emissions = sum(float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0) - # TODO make this a reusable function - # Define table properties - console_width = console.width - 5 table = Table( - title="Subnet Info", - width=console_width, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, + title=f"[underline dark_orange]Subnets[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, - ) - table.title = f"[white]Subnets - {subtensor.network}\n" - - # Add columns to the table - # price_total = f"τ{total_price.tao:.2f}/{bt.Balance.from_rao(dynamic_emission).tao:.2f}" - # above_price_threshold = total_price.tao > bt.Balance.from_rao(dynamic_emission).tao - - table.add_column("Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center") - table.add_column("Symbol", style="rgb(211,54,130)", no_wrap=True, justify="center") - table.add_column( - f"Emission ({Balance.get_unit(0)})", - style="rgb(38,139,210)", - no_wrap=True, - justify="right", - ) - table.add_column( - f"TAO({Balance.get_unit(0)})", - style="medium_purple", - no_wrap=True, - justify="right", - ) - # table.add_column(f"{bt.Balance.get_unit(1)})", style="rgb(42,161,152)", no_wrap=True, justify="left") - table.add_column( - f"Stake({Balance.get_unit(1)})", style="green", no_wrap=True, justify="right" - ) - table.add_column( - f"Rate ({Balance.get_unit(1)}/{Balance.get_unit(0)})", - style="light_goldenrod2", - no_wrap=True, - justify="center", - ) - table.add_column( - "Tempo (k/n)", style="light_salmon3", no_wrap=True, justify="center" + show_lines=False, + pad_edge=True, ) - # table.add_column(f"Locked ({bt.Balance.get_unit(1)})", style="rgb(38,139,210)", no_wrap=True, justify="center") - # table.add_column("Owner", style="rgb(38,139,210)", no_wrap=True, justify="center") + + table.add_column("[bold white]NETUID", style="white", justify="center") + table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") + table.add_column(f"[bold white]EMISSION ({Balance.get_unit(0)})", style="light_goldenrod2", justify="right", footer=f"τ {total_emissions:.4f}") + table.add_column(f"[bold white]TAO ({Balance.get_unit(0)})", style="rgb(42,161,152)", justify="right") + table.add_column(f"[bold white]STAKE ({Balance.get_unit(1)})", style="light_salmon3", justify="right") + table.add_column(f"[bold white]RATE ({Balance.get_unit(1)}/{Balance.get_unit(0)})", style="medium_purple", justify="right") + table.add_column("[bold white]Tempo (k/n)", style="bright_magenta", justify="right", overflow="fold") + table.add_column("[bold white]Global weight (γ)", style="green", justify="right") # Sort rows by subnet.emission.tao, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) @@ -250,6 +228,8 @@ async def subnets_list( # Print the table console.print(table) + + # TODO: Add description for global weights console.print( """ [bold white]Description[/bold white]: @@ -288,12 +268,12 @@ async def show_root(): console_width = console.width - 5 table = Table( title="[white]Root Network", - width=console_width, + # width=console_width, safe_box=True, padding=(0, 1), collapse_padding=False, pad_edge=True, - expand=True, + # expand=True, show_header=True, show_footer=True, show_edge=False, From 7b78e19ef0cf34294449f0d7c942f3633dac8570 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 20:17:39 -0400 Subject: [PATCH 070/157] fix st list alignment --- bittensor_cli/src/commands/stake/stake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 56268daa..668e85ab 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1478,7 +1478,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): for substake_ in substakes: netuid = substake_.netuid pool = dynamic_info[netuid] - symbol = f"{Balance.get_unit(netuid)}" + symbol = f"{Balance.get_unit(netuid)}\u200e" # TODO: what is this price var for? price = ( "{:.4f}{}".format( From d4978cff9b0935f4b9ea746e84ebbeacbafaa2f1 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 20:29:39 -0400 Subject: [PATCH 071/157] modify query_multiple --- bittensor_cli/src/bittensor/async_substrate_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index 60ec9dce..a6baf541 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -4,7 +4,7 @@ from collections import defaultdict from dataclasses import dataclass from hashlib import blake2b -from typing import Optional, Any, Union, Callable, Awaitable, cast +from typing import Optional, Any, Union, Callable, Awaitable, cast, Iterable from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15 from async_property import async_property @@ -1792,7 +1792,7 @@ async def query_multiple( runtime = await self.init_runtime(block_hash=block_hash) preprocessed: tuple[Preprocessed] = await asyncio.gather( *[ - self._preprocess([x], block_hash, storage_function, module) + self._preprocess([x] if not isinstance(x, Iterable) else list(x), block_hash, storage_function, module) for x in params ] ) From 877df05cbc4552bb2efce542dbeeca665b65c012 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 20:29:48 -0400 Subject: [PATCH 072/157] fix total stake for hotkey --- bittensor_cli/src/bittensor/subtensor_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 28561e54..5de331d5 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -431,10 +431,11 @@ async def get_total_stake_for_hotkey( :return: {address: Balance objects} """ + netuids = await self.get_all_subnet_netuids(block_hash=block_hash) results = await self.substrate.query_multiple( - params=[s for s in ss58_addresses], + params=[p for p in zip(ss58_addresses, netuids)], module="SubtensorModule", - storage_function="TotalHotkeyStake", + storage_function="TotalHotkeyAlpha", block_hash=block_hash, reuse_block_hash=reuse_block, ) From 5b77906570313724a72f23c2ce90bbe8cad3d310 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 24 Oct 2024 20:34:33 -0400 Subject: [PATCH 073/157] unwrap tuple --- bittensor_cli/src/bittensor/subtensor_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 5de331d5..500d281a 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -432,14 +432,14 @@ async def get_total_stake_for_hotkey( :return: {address: Balance objects} """ netuids = await self.get_all_subnet_netuids(block_hash=block_hash) - results = await self.substrate.query_multiple( + results: dict[tuple[str, int], int] = await self.substrate.query_multiple( params=[p for p in zip(ss58_addresses, netuids)], module="SubtensorModule", storage_function="TotalHotkeyAlpha", block_hash=block_hash, reuse_block_hash=reuse_block, ) - return {k: Balance.from_rao(r or 0) for (k, r) in results.items()} + return {k[0]: Balance.from_rao(r or 0) for (k, r) in results.items()} async def current_take( self, From 6c5de3cbbae3ce833746cb981b55959499e60fcc Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 24 Oct 2024 17:37:59 -0700 Subject: [PATCH 074/157] Improve fetching global weights --- .../src/bittensor/subtensor_interface.py | 12 +++-- bittensor_cli/src/commands/subnets.py | 53 ++++++++++++------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index da5fdf92..258097e4 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1300,11 +1300,15 @@ async def get_all_subnet_dynamic_info(self) -> list["DynamicInfo"]: subnets = DynamicInfo.list_from_vec_u8(bytes.fromhex(query.decode()[2:])) return subnets - async def get_global_weight(self, netuid: int, block_hash: Optional[str] = None): - global_weight = await self.substrate.query( + async def get_global_weights( + self, netuids: list[int], block_hash: Optional[str] = None + ): + result = await self.substrate.query_multiple( module="SubtensorModule", storage_function="GlobalWeight", - params=[netuid], + params=[netuid for netuid in netuids], block_hash=block_hash, ) - return u64_normalized_float(global_weight) + return { + netuid: u64_normalized_float(weight) for (netuid, weight) in result.items() + } diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index aa4929a9..743aab19 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -158,25 +158,16 @@ async def subnets_list( ): """List all subnet netuids in the network.""" # TODO add reuse-last and html-output and no-cache - async def fetch_global_weight(netuid): - try: - return netuid, await subtensor.get_global_weight(netuid) - except Exception as e: - print(f"Error fetching global weight for netuid {netuid}: {e}") - return netuid, None - rows = [] subnets = await subtensor.get_all_subnet_dynamic_info() - netuids = [subnet.netuid for subnet in subnets] - - global_weight_tasks = [fetch_global_weight(netuid) for netuid in netuids] - global_weights = await asyncio.gather(*global_weight_tasks) + global_weights = await subtensor.get_global_weights( + [subnet.netuid for subnet in subnets] + ) - global_weight_dict = {netuid: weight for netuid, weight in global_weights} for subnet in subnets: netuid = subnet.netuid - global_weight = global_weight_dict.get(netuid) + global_weight = global_weights.get(netuid) symbol = f"{subnet.symbol}\u200e" if netuid == 0: @@ -196,7 +187,9 @@ async def fetch_global_weight(netuid): f"{global_weight:.4f}" if global_weight is not None else "N/A", ) ) - total_emissions = sum(float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0) + total_emissions = sum( + float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 + ) table = Table( title=f"[underline dark_orange]Subnets[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", @@ -212,11 +205,33 @@ async def fetch_global_weight(netuid): table.add_column("[bold white]NETUID", style="white", justify="center") table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") - table.add_column(f"[bold white]EMISSION ({Balance.get_unit(0)})", style="light_goldenrod2", justify="right", footer=f"τ {total_emissions:.4f}") - table.add_column(f"[bold white]TAO ({Balance.get_unit(0)})", style="rgb(42,161,152)", justify="right") - table.add_column(f"[bold white]STAKE ({Balance.get_unit(1)})", style="light_salmon3", justify="right") - table.add_column(f"[bold white]RATE ({Balance.get_unit(1)}/{Balance.get_unit(0)})", style="medium_purple", justify="right") - table.add_column("[bold white]Tempo (k/n)", style="bright_magenta", justify="right", overflow="fold") + table.add_column( + f"[bold white]EMISSION ({Balance.get_unit(0)})", + style="light_goldenrod2", + justify="right", + footer=f"τ {total_emissions:.4f}", + ) + table.add_column( + f"[bold white]TAO ({Balance.get_unit(0)})", + style="rgb(42,161,152)", + justify="right", + ) + table.add_column( + f"[bold white]STAKE ({Balance.get_unit(1)})", + style="light_salmon3", + justify="right", + ) + table.add_column( + f"[bold white]RATE ({Balance.get_unit(1)}/{Balance.get_unit(0)})", + style="medium_purple", + justify="right", + ) + table.add_column( + "[bold white]Tempo (k/n)", + style="bright_magenta", + justify="right", + overflow="fold", + ) table.add_column("[bold white]Global weight (γ)", style="green", justify="right") # Sort rows by subnet.emission.tao, keeping the first subnet in the first position From 47d350f2097f9a402f39f6c1caac42e0cae565b9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 24 Oct 2024 18:13:45 -0700 Subject: [PATCH 075/157] Improves subnets show --- bittensor_cli/src/commands/subnets.py | 183 ++++++-------------------- 1 file changed, 42 insertions(+), 141 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 743aab19..a27d5c36 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -280,54 +280,44 @@ async def show_root(): ) return - console_width = console.width - 5 table = Table( - title="[white]Root Network", - # width=console_width, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - # expand=True, - show_header=True, + title=f"[underline dark_orange]Root Network[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, + show_lines=False, + pad_edge=True, ) - # Add columns to the table + table.add_column("[bold white]Position", style="white", justify="center") table.add_column( - "Position", style="rgb(253,246,227)", no_wrap=True, justify="center" + f"[bold white] TAO ({Balance.get_unit(0)})", + style="medium_purple", + justify="center", ) table.add_column( - f"TAO ({Balance.get_unit(0)})", - style="medium_purple", - no_wrap=True, + f"[bold white]Stake ({Balance.get_unit(0)})", + style="rgb(42,161,152)", justify="center", ) table.add_column( - f"Stake ({Balance.get_unit(0)})", - style="dark_sea_green", - no_wrap=True, + f"[bold white]Emission ({Balance.get_unit(0)}/block)", + style="light_goldenrod2", justify="center", ) table.add_column( - f"Emission ({Balance.get_unit(0)}/block)", - style="rgb(42,161,152)", - no_wrap=True, + "[bold white]Hotkey", + style="bright_magenta", justify="center", ) table.add_column( - "Hotkey", style="light_salmon3", no_wrap=True, justify="center" + "[bold white]Coldkey", + style="bright_magenta", + justify="center", ) + sorted_hotkeys = sorted( enumerate(root_state.hotkeys), key=lambda x: root_state.global_stake[x[0]], @@ -349,21 +339,22 @@ async def show_root(): str(root_state.local_stake[idx]), str(total_emission_per_block), f"{root_state.hotkeys[idx]}", + f"{root_state.coldkeys[idx]}", ) # Print the table console.print(table) console.print( """ - Description: - The table displays the root subnet participants and their metrics. - The columns are as follows: - - Position: The sorted position of the hotkey by total TAO. - - TAO: The sum of all TAO balances for this hotkey accross all subnets. - - Stake: The stake balance of this hotkey on root (measured in TAO). - - Emission: The emission accrued to this hotkey across all subnets every block measured in TAO. - - Hotkey: The hotkey ss58 address. - """ +Description: + The table displays the root subnet participants and their metrics. + The columns are as follows: + - Position: The sorted position of the hotkey by total TAO. + - TAO: The sum of all TAO balances for this hotkey accross all subnets. + - Stake: The stake balance of this hotkey on root (measured in TAO). + - Emission: The emission accrued to this hotkey across all subnets every block measured in TAO. + - Hotkey: The hotkey ss58 address. +""" ) async def show_subnet(netuid_: int): @@ -385,101 +376,17 @@ async def show_subnet(netuid_: int): return # Define table properties - console_width = console.width - 5 table = Table( - title=f"[white]Subnet {netuid_} Metagraph", - width=console_width, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, + title=f"[underline dark_orange]Subnet {netuid_}[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, - ) - subnet_info_table = Table( - width=console_width, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, - show_footer=False, - show_edge=False, show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", - title_justify="center", - highlight=False, - ) - - subnet_info_table.add_column( - "Index", style="grey89", no_wrap=True, justify="center" - ) - subnet_info_table.add_column( - "Symbol", style="rgb(211,54,130)", no_wrap=True, justify="center" - ) - subnet_info_table.add_column( - f"Emission ({Balance.get_unit(0)})", - style="rgb(38,139,210)", - no_wrap=True, - justify="center", - ) - subnet_info_table.add_column( - f"P({Balance.get_unit(0)},", - style="rgb(108,113,196)", - no_wrap=True, - justify="right", - ) - subnet_info_table.add_column( - f"{Balance.get_unit(1)})", - style="rgb(42,161,152)", - no_wrap=True, - justify="left", - ) - subnet_info_table.add_column( - f"{Balance.get_unit(1)}", - style="rgb(133,153,0)", - no_wrap=True, - justify="center", - ) - subnet_info_table.add_column( - f"Rate ({Balance.get_unit(1)}/{Balance.get_unit(0)})", - style="rgb(181,137,0)", - no_wrap=True, - justify="center", - ) - subnet_info_table.add_column( - "Tempo", style="rgb(38,139,210)", no_wrap=True, justify="center" - ) - subnet_info_table.add_row( - str(netuid_), - f"[light_goldenrod1]{str(subnet_info.symbol)}[light_goldenrod1]", - f"τ{subnet_info.emission.tao:.4f}", - f"P( τ{subnet_info.tao_in.tao:,.4f},", - f"{subnet_info.alpha_in.tao:,.4f}{subnet_info.symbol} )", - f"{subnet_info.alpha_out.tao:,.4f}{subnet_info.symbol}", - f"{subnet_info.price.tao:.4f}τ/{subnet_info.symbol}", - str(subnet_info.blocks_since_last_step) + "/" + str(subnet_info.tempo), + pad_edge=True, ) - rows = [] emission_sum = sum( [ @@ -522,7 +429,7 @@ async def show_subnet(netuid_: int): ) table.add_column( f"Stake({Balance.get_unit(netuid_)})", - style="green", + style="rgb(42,161,152)", no_wrap=True, justify="right", footer=f"{stake_sum.set_unit(subnet_info.netuid)}", @@ -533,36 +440,30 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="center", ) - table.add_column( - "Dividends", style="rgb(181,137,0)", no_wrap=True, justify="center" - ) - table.add_column( - "Incentive", style="rgb(220,50,47)", no_wrap=True, justify="center" - ) + table.add_column("Dividends", style="#8787d7", no_wrap=True, justify="center") + table.add_column("Incentive", style="#5fd7ff", no_wrap=True, justify="center") table.add_column( f"Emission ({Balance.get_unit(netuid_)})", - style="aquamarine3", + style="light_goldenrod2", no_wrap=True, justify="center", footer=str(Balance.from_tao(emission_sum).set_unit(subnet_info.netuid)), ) table.add_column( - "Hotkey", style="light_salmon3", no_wrap=True, justify="center" + "Hotkey", style="bright_magenta", no_wrap=True, justify="center" ) table.add_column( - "Coldkey", style="bold dark_green", no_wrap=True, justify="center" + "Coldkey", style="bright_magenta", no_wrap=True, justify="center" ) for row in rows: table.add_row(*row) # Print the table - # console.print("\n\n\n") - # console.print(subnet_info_table) console.print("\n\n") console.print(table) console.print("\n") console.print( - f"Subnet: {netuid_}:\n Owner: [light_salmon3]{subnet_info.owner}[/light_salmon3]\n Total Locked: [green]{subnet_info.total_locked}[/green]\n Owner Locked: [green]{subnet_info.owner_locked}[/green]" + f"Subnet: {netuid_}:\n Owner: [bold bright_magenta]{subnet_info.owner}[/bold bright_magenta]\n Total Locked: [green]{subnet_info.total_locked}[/green]\n Owner Locked: [green]{subnet_info.owner_locked}[/green]" ) console.print( """ From 82adb4940320a60618aa63962240a2d03a5804d3 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 24 Oct 2024 18:39:47 -0700 Subject: [PATCH 076/157] Default to rao network --- bittensor_cli/src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 3ab18fe2..3c26ea8f 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -97,7 +97,7 @@ class config: } class subtensor: - network = "finney" + network = "rao" chain_endpoint = None _mock = False From 4ae70084223fffab2af6ac21d8aa714ac20a9cf7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 14:49:03 +0200 Subject: [PATCH 077/157] Mostly fix child hotkeys get. Still issue with 'Storage function "SubtensorModule.ChildkeyTake" not found' --- bittensor_cli/cli.py | 2 +- .../bittensor/async_substrate_interface.py | 17 +++- .../src/bittensor/extrinsics/registration.py | 3 +- .../src/bittensor/subtensor_interface.py | 42 ++++++++-- .../src/commands/stake/children_hotkeys.py | 83 +++++++------------ bittensor_cli/src/commands/subnets.py | 20 ++--- 6 files changed, 87 insertions(+), 80 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index f20e82f4..54220f78 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2,6 +2,7 @@ import asyncio import binascii import curses +from contextlib import suppress from functools import partial import os.path import re @@ -832,7 +833,6 @@ async def _run(): raise typer.Exit() except SubstrateRequestException as e: err_console.print(str(e)) - asyncio.create_task(cmd).cancel() raise typer.Exit() if sys.version_info < (3, 10): diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index a6baf541..47fc26ee 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -3,7 +3,7 @@ import random from collections import defaultdict from dataclasses import dataclass -from hashlib import blake2b +from hashlib import blake2b, sha256 from typing import Optional, Any, Union, Callable, Awaitable, cast, Iterable from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15 @@ -1603,7 +1603,6 @@ async def _make_rpc_request( result_handler: Optional[ResultHandler] = None, ) -> RequestManager.RequestResults: request_manager = RequestManager(payloads) - subscription_added = False async with self.ws as ws: @@ -1786,13 +1785,23 @@ async def query_multiple( # By allowing for specifying the block hash, users, if they have multiple query types they want # to do, can simply query the block hash first, and then pass multiple query_subtensor calls # into an asyncio.gather, with the specified block hash + if len(params) != len(set(params)): + raise SubstrateRequestException( + "You are attempting to query multiple values, but you have duplicates." + ) + block_hash = await self._get_current_block_hash(block_hash, reuse_block_hash) if block_hash: self.last_block_hash = block_hash runtime = await self.init_runtime(block_hash=block_hash) preprocessed: tuple[Preprocessed] = await asyncio.gather( *[ - self._preprocess([x] if not isinstance(x, Iterable) else list(x), block_hash, storage_function, module) + self._preprocess( + [x] if not isinstance(x, Iterable) else list(x), + block_hash, + storage_function, + module, + ) for x in params ] ) @@ -1800,10 +1809,10 @@ async def query_multiple( self.make_payload(item.queryable, item.method, item.params) for item in preprocessed ] + # These will always be the same throughout the preprocessed list, so we just grab the first one value_scale_type = preprocessed[0].value_scale_type storage_item = preprocessed[0].storage_item - responses = await self._make_rpc_request( all_info, value_scale_type, storage_item, runtime ) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index 86bad3c9..5cfead70 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -493,7 +493,6 @@ async def get_neuron_for_pubkey_and_subnet(): ) if uid is None: return NeuronInfo.get_null_neuron() - hex_bytes_result = await subtensor.substrate.query_runtime_api( runtime_api="NeuronInfoRuntimeApi", @@ -503,7 +502,7 @@ async def get_neuron_for_pubkey_and_subnet(): if not (result := hex_bytes_result): return NeuronInfo.get_null_neuron() - + if result.startswith("0x"): bytes_result = bytes.fromhex(result[2:]) else: diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 09801dbf..76847ead 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -420,27 +420,52 @@ async def get_total_stake_for_coldkey( async def get_total_stake_for_hotkey( self, *ss58_addresses, + netuids: Optional[list[int]] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, Balance]: + ) -> dict[str, dict[int, "Balance"]]: """ Returns the total stake held on a hotkey. :param ss58_addresses: The SS58 address(es) of the hotkey(s) + :param netuids: The netuids to retrieve the stake from. If not specified, will use all subnets. :param block_hash: The hash of the block number to retrieve the stake from. :param reuse_block: Whether to reuse the last-used block hash when retrieving info. - :return: {address: Balance objects} + :return: + { + hotkey_ss58_1: { + netuid_1: netuid1_stake, + netuid_2: netuid2_stake, + ... + }, + hotkey_ss58_2: { + netuid_1: netuid1_stake, + netuid_2: netuid2_stake, + ... + }, + ... + } """ - netuids = await self.get_all_subnet_netuids(block_hash=block_hash) - results: dict[tuple[str, int], int] = await self.substrate.query_multiple( - params=[p for p in zip(ss58_addresses, netuids)], + netuids = netuids or await self.get_all_subnet_netuids(block_hash=block_hash) + query: dict[tuple[str, int], int] = await self.substrate.query_multiple( + params=[(ss58, netuid) for ss58 in ss58_addresses for netuid in netuids], module="SubtensorModule", storage_function="TotalHotkeyAlpha", block_hash=block_hash, reuse_block_hash=reuse_block, ) - return {k[0]: Balance.from_rao(r or 0) for (k, r) in results.items()} + results: dict[str, dict[int, "Balance"]] = { + hk_ss58: {} for hk_ss58 in ss58_addresses + } + for idx, (_, val) in enumerate(query): + hotkey_ss58 = ss58_addresses[idx // len(netuids)] + netuid = netuids[idx % len(netuids)] + value = (Balance.from_rao(val) if val is not None else Balance(0)).set_unit( + netuid + ) + results[hotkey_ss58][netuid] = value + return results async def current_take( self, @@ -732,7 +757,7 @@ async def neuron_for_uid( """ if uid is None: return NeuronInfo.get_null_neuron() - + hex_bytes_result = await self.query_runtime_api( runtime_api="NeuronInfoRuntimeApi", method="get_neuron", @@ -740,7 +765,6 @@ async def neuron_for_uid( block_hash=block_hash, ) - if not (result := hex_bytes_result): return NeuronInfo.get_null_neuron() @@ -1194,7 +1218,7 @@ async def get_subnet_dynamic_info( ) if hex_bytes_result is None: - return None + return None if hex_bytes_result.startswith("0x"): bytes_result = bytes.fromhex(hex_bytes_result[2:]) diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index 8d260e0a..e250926a 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -288,38 +288,6 @@ async def get_children( - If netuid is not specified, generates and prints a summary table of all child hotkeys across all subnets. """ - async def get_total_stake_for_hk(hotkey: str, parent: bool = False): - """ - Fetches and displays the total stake for a specified hotkey from the Subtensor blockchain network. - If `parent` is True, it prints the hotkey and its corresponding stake. - - Parameters: - - hotkey (str): The hotkey for which the stake needs to be fetched. - - parent (bool, optional): A flag to indicate whether the hotkey is the parent key. Defaults to False. - - Returns: - - Balance: The total stake associated with the specified hotkey. - """ - _result = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="TotalHotkeyStake", - params=[hotkey], - reuse_block_hash=True, - ) - stake = ( - Balance.from_rao(_result.value) - if getattr(_result, "value", None) - else Balance(0) - ) - if parent: - console.print( - f"\nYour Hotkey: [bright_magenta]{hotkey}[/bright_magenta] | Total Stake: [dark_orange]{stake}t[/dark_orange]\n", - end="", - no_wrap=True, - ) - - return stake - async def get_take(child: tuple) -> float: """ Get the take value for a given subtensor, hotkey, and netuid. @@ -367,7 +335,7 @@ async def _render_table( console.print(table) console.print( f"[bold red]There are currently no child hotkeys with parent hotkey: " - f"{wallet.name} ({parent_hotkey}).[/bold red]" + f"{wallet.name} | {wallet.hotkey_str} ({parent_hotkey}).[/bold red]" ) return @@ -376,6 +344,24 @@ async def _render_table( total_stake_weight = 0 netuid_children_.sort(key=lambda x: x[0]) # Sort by netuid in ascending order + unique_keys = set( + [parent_hotkey] + + [s for _, child_list in netuid_children_ for _, s in child_list] + ) + hotkey_stake_dict = await subtensor.get_total_stake_for_hotkey( + *unique_keys, + netuids=[n[0] for n in netuid_children_], + ) + parent_total = sum(hotkey_stake_dict[parent_hotkey].values()) + print("NETUID", netuid) + insert_text = ( + " " + if netuid is None + else f" on netuids: {', '.join(str(n[0]) for n in netuid_children_)} " + ) + console.print( + f"The total stake of parent hotkey '{parent_hotkey}'{insert_text}is {parent_total}." + ) for index, (netuid_, children_) in enumerate(netuid_children_): # calculate totals @@ -383,29 +369,26 @@ async def _render_table( total_stake_weight_per_netuid = 0 avg_take_per_netuid = 0.0 - hotkey_stake_dict = await subtensor.get_total_stake_for_hotkey( - parent_hotkey - ) - hotkey_stake = hotkey_stake_dict.get(parent_hotkey, Balance(0)) + hotkey_stake: dict[int, Balance] = hotkey_stake_dict[parent_hotkey] children_info = [] - child_stakes = await asyncio.gather( - *[get_total_stake_for_hk(c[1]) for c in children_] - ) child_takes = await asyncio.gather(*[get_take(c) for c in children_]) - for child, child_stake, child_take in zip( - children_, child_stakes, child_takes - ): + for child, child_take in zip(children_, child_takes): proportion = child[0] child_hotkey = child[1] # add to totals avg_take_per_netuid += child_take - proportion = u64_to_float(proportion) + converted_proportion = u64_to_float(proportion) children_info.append( - (proportion, child_hotkey, child_stake, child_take) + ( + converted_proportion, + child_hotkey, + hotkey_stake_dict[child_hotkey][netuid_], + child_take, + ) ) children_info.sort( @@ -459,17 +442,16 @@ async def _render_table( if netuid is None: # get all netuids netuids = await subtensor.get_all_subnet_netuids() - await get_total_stake_for_hk(wallet.hotkey.ss58_address, True) netuid_children_tuples = [] - for netuid in netuids: + for netuid_ in netuids: success, children, err_mg = await subtensor.get_children( - wallet.hotkey.ss58_address, netuid + wallet.hotkey.ss58_address, netuid_ ) if children: - netuid_children_tuples.append((netuid, children)) + netuid_children_tuples.append((netuid_, children)) if not success: err_console.print( - f"Failed to get children from subtensor {netuid}: {err_mg}" + f"Failed to get children from subtensor {netuid_}: {err_mg}" ) await _render_table(wallet.hotkey.ss58_address, netuid_children_tuples) else: @@ -478,7 +460,6 @@ async def _render_table( ) if not success: err_console.print(f"Failed to get children from subtensor: {err_mg}") - await get_total_stake_for_hk(wallet.hotkey.ss58_address, True) if children: netuid_children_tuples = [(netuid, children)] await _render_table(wallet.hotkey.ss58_address, netuid_children_tuples) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 0e502f1e..d2eea0ce 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -273,13 +273,11 @@ async def show_root(): if (bytes_result := hex_bytes_result) is None: err_console.print("The root subnet does not exist") return - + if bytes_result.startswith("0x"): bytes_result = bytes.fromhex(bytes_result[2:]) - root_state: "SubnetState" = SubnetState.from_vec_u8( - bytes_result - ) + root_state: "SubnetState" = SubnetState.from_vec_u8(bytes_result) if len(root_state.hotkeys) == 0: err_console.print( "The root-subnet is currently empty with 0 UIDs registered." @@ -373,13 +371,11 @@ async def show_subnet(netuid_: int): if (bytes_result := hex_bytes_result) is None: err_console.print(f"Subnet {netuid_} does not exist") return - + if bytes_result.startswith("0x"): bytes_result = bytes.fromhex(bytes_result[2:]) - - subnet_state: "SubnetState" = SubnetState.from_vec_u8( - bytes_result - ) + + subnet_state: "SubnetState" = SubnetState.from_vec_u8(bytes_result) if subnet_info is None: err_console.print(f"Subnet {netuid_} does not exist") return @@ -715,13 +711,11 @@ async def metagraph_cmd( if not (bytes_result := hex_bytes_result): err_console.print(f"Subnet {netuid} does not exist") return - + if bytes_result.startswith("0x"): bytes_result = bytes.fromhex(bytes_result[2:]) - subnet_state: "SubnetState" = SubnetState.from_vec_u8( - bytes_result - ) + subnet_state: "SubnetState" = SubnetState.from_vec_u8(bytes_result) difficulty = int(difficulty_) total_issuance = Balance.from_rao(total_issuance_) From 85fd45133513a690e5c96fab7d1d352a999b5825 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 15:01:51 +0200 Subject: [PATCH 078/157] Removed unused imports --- bittensor_cli/cli.py | 1 - bittensor_cli/src/bittensor/async_substrate_interface.py | 2 +- bittensor_cli/src/commands/stake/stake.py | 8 ++------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 54220f78..dda2d9d7 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2,7 +2,6 @@ import asyncio import binascii import curses -from contextlib import suppress from functools import partial import os.path import re diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index 47fc26ee..ebe0e9f5 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -3,7 +3,7 @@ import random from collections import defaultdict from dataclasses import dataclass -from hashlib import blake2b, sha256 +from hashlib import blake2b from typing import Optional, Any, Union, Callable, Awaitable, cast, Iterable from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15 diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 668e85ab..078f6e9c 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1,8 +1,4 @@ import asyncio -import copy -import json -import sqlite3 -from contextlib import suppress from functools import partial from typing import TYPE_CHECKING, Optional, Sequence, Union, cast @@ -10,13 +6,13 @@ from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError from rich.prompt import Confirm, FloatPrompt, Prompt -from rich.table import Table, Column -import typer +from rich.table import Table from substrateinterface.exceptions import SubstrateRequestException from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import StakeInfo from bittensor_cli.src.bittensor.utils import ( + # TODO add back in caching console, create_table, err_console, From 3dded35314ad0a1169e928e5d370aa10f36ec2b1 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 17:57:19 +0200 Subject: [PATCH 079/157] Child hotkey get fixed. --- .../src/commands/stake/children_hotkeys.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index e250926a..52d30dd1 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -288,7 +288,7 @@ async def get_children( - If netuid is not specified, generates and prints a summary table of all child hotkeys across all subnets. """ - async def get_take(child: tuple) -> float: + async def get_take(child: tuple, netuid__: int) -> float: """ Get the take value for a given subtensor, hotkey, and netuid. @@ -299,7 +299,7 @@ async def get_take(child: tuple) -> float: """ child_hotkey = child[1] take_u16 = await get_childkey_take( - subtensor=subtensor, hotkey=child_hotkey, netuid=netuid + subtensor=subtensor, hotkey=child_hotkey, netuid=netuid__ ) if take_u16: return u16_to_float(take_u16) @@ -353,7 +353,6 @@ async def _render_table( netuids=[n[0] for n in netuid_children_], ) parent_total = sum(hotkey_stake_dict[parent_hotkey].values()) - print("NETUID", netuid) insert_text = ( " " if netuid is None @@ -372,7 +371,9 @@ async def _render_table( hotkey_stake: dict[int, Balance] = hotkey_stake_dict[parent_hotkey] children_info = [] - child_takes = await asyncio.gather(*[get_take(c) for c in children_]) + child_takes = await asyncio.gather( + *[get_take(c, netuid_) for c in children_] + ) for child, child_take in zip(children_, child_takes): proportion = child[0] child_hotkey = child[1] @@ -395,9 +396,11 @@ async def _render_table( key=lambda x: x[0], reverse=True ) # sorting by proportion (highest first) - for proportion, hotkey, stake, child_take in children_info: - proportion_percent = proportion * 100 # Proportion in percent - proportion_tao = hotkey_stake.tao * proportion # Proportion in TAO + for proportion_, hotkey, stake, child_take in children_info: + proportion_percent = proportion_ * 100 # Proportion in percent + proportion_tao = ( + hotkey_stake[netuid_].tao * proportion_ + ) # Proportion in TAO total_proportion_per_netuid += proportion_percent @@ -407,7 +410,7 @@ async def _render_table( total_stake_weight_per_netuid += stake_weight take_str = f"{child_take * 100:.3f}%" - hotkey = Text(hotkey, style="italic red" if proportion == 0 else "") + hotkey = Text(hotkey, style="italic red" if proportion_ == 0 else "") table.add_row( str(netuid_), hotkey, From 1e85564ab5959c6d18d42c98bdca4198c9facaa0 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 25 Oct 2024 08:59:29 -0700 Subject: [PATCH 080/157] ui tweaks --- bittensor_cli/src/commands/stake/stake.py | 35 ++++++++--------------- bittensor_cli/src/commands/subnets.py | 2 +- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 078f6e9c..76186a37 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1497,7 +1497,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): else 0 ) slippage_percentage = ( - f"[dark_red]{slippage_percentage_:.3f}%[/dark_red]" + f"[salmon1]{slippage_percentage_:.3f}%[/salmon1]" ) else: slippage_percentage = "0.000%" @@ -1543,47 +1543,36 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") table = Table( - title=f"[white]hotkey:[/white] [light_salmon3]{name}[/light_salmon3]\n", - width=console.width - 5, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, + title=f"\n[dark_orange]Hotkey: {name}[/dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, + show_lines=False, + pad_edge=True, ) table.add_column("[white]Netuid", footer_style="overline white", style="grey89") table.add_column( "[white]Symbol", footer_style="white", style="light_goldenrod1", - justify="right", + justify="center", width=5, no_wrap=True, ) table.add_column( f"[white]TAO({Balance.unit})", - style="aquamarine3", + style="medium_purple", justify="right", footer=f"{total_global_tao}", ) table.add_column( f"[white]Stake({Balance.get_unit(1)})", footer_style="overline white", - style="green", - justify="right", + style="rgb(42,161,152)", + justify="center", ) table.add_column( f"[white]Rate({Balance.unit}/{Balance.get_unit(1)})", @@ -1608,7 +1597,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): table.add_column("[white]Registered", style="red", justify="right") table.add_column( f"[white]Emission({Balance.get_unit(1)}/block)", - style="aquamarine3", + style="light_goldenrod2", justify="right", ) table.add_column( diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index d2eea0ce..03807851 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -232,7 +232,7 @@ async def subnets_list( justify="right", overflow="fold", ) - table.add_column("[bold white]Global weight (γ)", style="green", justify="right") + table.add_column("[bold white]Global weight (γ)", style="green", justify="center") # Sort rows by subnet.emission.tao, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) From bfa33ee1fb00973fca2cc871f12ad446a2fc5a6f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 18:08:15 +0200 Subject: [PATCH 081/157] Stake fix --- bittensor_cli/src/commands/stake/stake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 76186a37..e2d939d8 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1020,7 +1020,7 @@ async def stake_add( # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", f"{hotkey[1]}", str(amount_to_stake_as_balance), - str(1 / float(dynamic_info.price)) + str(1 / float(dynamic_info.price) or 1) + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", str(received_amount.set_unit(netuid)), str(slippage_pct), From 8a40a8d011d581e44cb4faf27c1dbcd39487a585 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 18:10:04 +0200 Subject: [PATCH 082/157] Balance fix --- bittensor_cli/src/bittensor/subtensor_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 76847ead..0fa1fc9d 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -405,7 +405,7 @@ async def get_total_stake_for_coldkey( tao_locked = pool.tao_in issuance = pool.alpha_out if pool.is_dynamic else tao_locked - tao_ownership = 0 + tao_ownership = Balance(0) if alpha_value.tao > 0.00009 and issuance.tao != 0: tao_ownership = Balance.from_tao( From d19b9f2b8e7d11f498281e712354980f641e536a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 18:16:56 +0200 Subject: [PATCH 083/157] Parentheses matter --- bittensor_cli/src/commands/stake/stake.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index e2d939d8..489c53c4 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1020,7 +1020,7 @@ async def stake_add( # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", f"{hotkey[1]}", str(amount_to_stake_as_balance), - str(1 / float(dynamic_info.price) or 1) + str(1 / (float(dynamic_info.price) or 1)) + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", str(received_amount.set_unit(netuid)), str(slippage_pct), @@ -1496,9 +1496,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): if slippage + swapped_tao_value != 0 else 0 ) - slippage_percentage = ( - f"[salmon1]{slippage_percentage_:.3f}%[/salmon1]" - ) + slippage_percentage = f"[salmon1]{slippage_percentage_:.3f}%[/salmon1]" else: slippage_percentage = "0.000%" tao_locked = pool.tao_in From 09721c415a3ff2d47de57dc5fba07f7b140f95b8 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Oct 2024 18:25:43 +0200 Subject: [PATCH 084/157] Error-handling for wallet unlock --- bittensor_cli/src/commands/stake/stake.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 489c53c4..53be50ad 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1125,7 +1125,11 @@ async def send_extrinsic( ) # Perform staking operation. - wallet.unlock_coldkey() + try: + wallet.unlock_coldkey() + except KeyFileError: + err_console.print("Error decrypting coldkey (possibly incorrect password)") + return False extrinsics_coroutines = [ send_extrinsic(ni, am, curr, staking_address) for i, (ni, am, curr) in enumerate( @@ -1386,7 +1390,11 @@ async def unstake( ) # Perform staking operation. - wallet.unlock_coldkey() + try: + wallet.unlock_coldkey() + except KeyFileError: + err_console.print("Error decrypting coldkey (possibly incorrect password)") + return False with console.status( f"\n:satellite: Unstaking {amount_to_unstake_as_balance} from {staking_address_name} on netuid: {netuid} ..." ): @@ -1812,7 +1820,11 @@ async def move_stake( return True # Perform staking operation. - wallet.unlock_coldkey() + try: + wallet.unlock_coldkey() + except KeyFileError: + err_console.print("Error decrypting coldkey (possibly incorrect password)") + return False with console.status( f"\n:satellite: Moving {amount_to_move_as_balance} from {origin_hotkey_ss58} on netuid: {origin_netuid} to " f"{destination_hotkey} on netuid: {destination_netuid} ..." From 0c55df876cc7d09e9cc5b72466ad5de881019c50 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 25 Oct 2024 09:29:24 -0700 Subject: [PATCH 085/157] Remove lock in st list --- bittensor_cli/src/commands/stake/stake.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 53be50ad..9db63a39 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1492,7 +1492,6 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): else (f" 1.0000 τ/{symbol} ") ) alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid) - locked_value = Balance.from_rao(int(substake_.locked.rao)).set_unit(netuid) tao_value = pool.alpha_to_tao(alpha_value) total_tao_value += tao_value swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage( @@ -1544,7 +1543,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): str(Balance.from_tao(per_block_emission).set_unit(netuid)) if substake_.is_registered else "[dark_red]N/A[/dark_red]", # emission per block. - f"[light_slate_blue]{locked_value}[/light_slate_blue]", # Locked value + # f"[light_slate_blue]{locked_value}[/light_slate_blue]", # Locked value ] ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") @@ -1606,12 +1605,6 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): style="light_goldenrod2", justify="right", ) - table.add_column( - f"[white]Locked({Balance.get_unit(1)})", - footer_style="overline white", - style="green", - justify="right", - ) for row in rows: table.add_row(*row) console.print(table) From 667da15432cad93bff10ad5c042d915b4648cb7e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 25 Oct 2024 09:48:56 -0700 Subject: [PATCH 086/157] Fix emission, dividents disply in st show --- bittensor_cli/src/commands/subnets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 03807851..51295d05 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -341,7 +341,7 @@ async def show_root(): str((pos + 1)), str(root_state.global_stake[idx]), str(root_state.local_stake[idx]), - str(total_emission_per_block), + f"{str(total_emission_per_block):.4f}", f"{root_state.hotkeys[idx]}", f"{root_state.coldkeys[idx]}", ) @@ -421,9 +421,9 @@ async def show_subnet(netuid_: int): f"{subnet_state.local_stake[idx].tao:.4f} {subnet_info.symbol}", # Stake f"{subnet_state.stake_weight[idx]:.4f}", # Weight # str(subnet_state.dividends[idx]), - f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", # Dividends + f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f} {subnet_info.symbol}", # Dividents str(subnet_state.incentives[idx]), # Incentive - f"{str(Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao)} {subnet_info.symbol}", # Emission + f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f} {subnet_info.symbol}", # Emission f"{subnet_state.hotkeys[idx]}", # Hotkey f"{subnet_state.coldkeys[idx]}", # Coldkey ) From 4b58e89b33c6594e369bfd9b7869b69930065af5 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 25 Oct 2024 16:34:16 -0700 Subject: [PATCH 087/157] Revamped btcli stake remove --- bittensor_cli/cli.py | 11 +- bittensor_cli/src/commands/stake/stake.py | 349 +++++++++++++--------- 2 files changed, 214 insertions(+), 146 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index dda2d9d7..7ac8d4bc 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -291,12 +291,14 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in return None elif netuid is None and all_netuids is False: answer = Prompt.ask( - "[green]Enter the netuid to use. Leave blank for all netuids", + "[green]Enter the netuid to use.[/green] Leave blank for all netuids", default=None, show_default=False, ) if answer is None: return None + if answer.lower() == "all": + return None else: return int(answer) else: @@ -2688,8 +2690,9 @@ def stake_remove( ) raise typer.Exit() - if not unstake_all and not amount and not keep_stake: - amount = FloatPrompt.ask("[blue bold]Amount to unstake (TAO τ)[/blue bold]") + # TODO: We are prompting for amount for each subnet later - confirm this to be removed + # if not unstake_all and not amount and not keep_stake: + # amount = FloatPrompt.ask("[blue bold]Amount to unstake (TAO τ)[/blue bold]") if unstake_all and not amount: if not Confirm.ask("Unstake all staked TAO tokens?", default=False): @@ -2702,7 +2705,7 @@ def stake_remove( and not include_hotkeys ): hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from" + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from. [Press Enter to use config values]" ) if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 9db63a39..2aee12b6 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1160,6 +1160,7 @@ async def send_extrinsic( await asyncio.sleep(tx_rate_limit_blocks * 12) # 12 sec per block +# TODO: Decouple stuff from here. Works but is a mess async def unstake( wallet: Wallet, subtensor: "SubtensorInterface", @@ -1183,29 +1184,28 @@ async def unstake( hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = [] if hotkey_ss58_address: print_verbose(f"Unstaking from ss58 ({hotkey_ss58_address})") - # Unstake to specific hotkey. + # Unstake from specific hotkey. hotkeys_to_unstake_from = [(None, hotkey_ss58_address)] elif all_hotkeys: print_verbose("Unstaking from all hotkeys") - # Unstake to all hotkeys. + # Unstake from all hotkeys. all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) # Exclude hotkeys that are specified. hotkeys_to_unstake_from = [ (wallet.hotkey_str, wallet.hotkey.ss58_address) for wallet in all_hotkeys_ if wallet.hotkey_str not in exclude_hotkeys - ] # definitely wallets - + ] elif include_hotkeys: print_verbose("Unstaking from included hotkeys") - # Unstake to specific hotkeys. + # Unstake from specific hotkeys. for hotkey_ss58_or_hotkey_name in include_hotkeys: if is_valid_ss58_address(hotkey_ss58_or_hotkey_name): # If the hotkey is a valid ss58 address, we add it to the list. hotkeys_to_unstake_from.append((None, hotkey_ss58_or_hotkey_name)) else: # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. - # We then get the hotkey from the wallet and add it to the list. + # We then get the hotkey from the wallet and add it to the list. wallet_ = Wallet( name=wallet.name, path=wallet.path, @@ -1216,58 +1216,41 @@ async def unstake( ) else: # Only cli.config.wallet.hotkey is specified. - # so we stake to that single hotkey. + # So we unstake from that single hotkey. print_verbose( f"Unstaking from wallet: ({wallet.name}) from hotkey: ({wallet.hotkey_str})" ) assert wallet.hotkey is not None - hotkeys_to_unstake_from = [(None, wallet.hotkey.ss58_address)] - - final_hotkeys: list[tuple[str, str]] = [] - final_amounts: list[Union[float, Balance]] = [] - hotkey: tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58) + hotkeys_to_unstake_from = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] - # Get old staking balance. - table = Table( - title=f"[white]Unstake operation to Coldkey SS58: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", - width=console.width - 5, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, - show_footer=True, - show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", - title_justify="center", - highlight=False, - ) - rows = [] - unstake_amount_balance = [] - current_stake_balances = [] + # Prepare lists to store unstaking data per subnet + unstake_operations = [] total_received_amount = Balance.from_tao(0) current_wallet_balance: Balance = ( await subtensor.get_balance(wallet.coldkeypub.ss58_address) )[wallet.coldkeypub.ss58_address] max_float_slippage = 0 - non_zero_netuids = [] - # TODO gather this all + + # Iterate over hotkeys and netuids to collect unstake operations for hotkey in hotkeys_to_unstake_from: staking_address_name, staking_address_ss58 = hotkey + initial_amount = amount + skip_remaining_subnets = False # Flag to check if user wants to quit + + if len(netuids) > 1: + console.print( + "[green]Tip: Enter 'q' any time to stop going over remaining subnets and process current unstakes." + ) + for netuid in netuids: + if skip_remaining_subnets: + break # Exit the loop over netuids + # Check that the subnet exists. dynamic_info = await subtensor.get_subnet_dynamic_info(netuid) if dynamic_info is None: console.print(f"[red]Subnet: {netuid} does not exist.[/red]") - return False + continue # Skip to the next subnet current_stake_balance: Balance = ( await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( @@ -1277,45 +1260,87 @@ async def unstake( ) ) if current_stake_balance.tao == 0: - continue - non_zero_netuids.append(netuid) - current_stake_balances.append(current_stake_balance) + continue # No stake to unstake - # Determine the amount we are staking. - if amount: - amount_to_unstake_as_balance = Balance.from_tao(amount) + # Determine the amount we are unstaking. + if initial_amount: + amount_to_unstake_as_balance = Balance.from_tao(initial_amount) elif unstake_all: amount_to_unstake_as_balance = current_stake_balance - else: # TODO max_stake - if Confirm.ask( - f"Unstake all: [bold]{current_stake_balance}[/bold]" - f" from [bold]{staking_address_name}[/bold] on netuid: {netuid}?" - ): - amount_to_unstake_as_balance = current_stake_balance - else: - try: - # TODO improve this - amount = float( - Prompt.ask( - f"Enter amount to unstake in {Balance.get_unit(netuid)} from subnet: {netuid}" + else: + # Prompt the user for each subnet + while True: + response = Prompt.ask( + f"Unstake all: [dark_orange]{current_stake_balance}[/dark_orange]" + f" from [bright_magenta]{staking_address_name if staking_address_name else staking_address_ss58}[/bright_magenta] on netuid: [dark_orange]{netuid}? [y/n/q]", + choices=["y", "n", "q"], + default="n", + show_choices=True, + ).lower() + + if response.lower() == "q": + skip_remaining_subnets = True + break # Exit the loop over netuids + + elif response.lower() == "y": + amount_to_unstake_as_balance = current_stake_balance + break # Proceed with unstake operation + elif response.lower() == "n": + while True: + amount_input = Prompt.ask( + f"Enter amount to unstake in [dark_orange]{Balance.get_unit(netuid)}[/dark_orange] from subnet: [dark_orange]{netuid}[/dark_orange] (Max: [dark_orange]{current_stake_balance}[/dark_orange])" ) + if amount_input.lower() == "q": + skip_remaining_subnets = True + break # Exit the loop over netuids + + try: + amount_value = float(amount_input) + if amount_value < 0 or amount_value == 0: + console.print( + "[red]Amount cannot be negative or zero.[/red]" + ) + continue # Re-prompt + + amount_to_unstake_as_balance = Balance.from_tao( + amount_value + ) + amount_to_unstake_as_balance.set_unit(netuid) + if amount_to_unstake_as_balance > current_stake_balance: + console.print( + f"[red]Amount exceeds current stake balance of {current_stake_balance}.[/red]" + ) + continue # Re-prompt + + break # Valid amount entered + + except ValueError: + console.print( + "[red]Invalid input. Please enter a numeric value or 'q' to quit.[/red]" + ) + + if skip_remaining_subnets: + break # Exit the loop over netuids + + break # Exit the response loop + + else: + console.print( + "[red]Invalid input. Please enter 'y', 'n', or 'q'.[/red]" ) - amount_to_unstake_as_balance = Balance.from_tao(amount) - except ValueError: - err_console.print( - ":cross_mark:[red]Invalid amount Please use `--amount` with `--no-prompt`.[/red]" - ) - return False - unstake_amount_balance.append(amount_to_unstake_as_balance) + continue # Re-prompt - # Check enough to stake. + if skip_remaining_subnets: + break # Exit the loop over netuids + + # Check enough stake to remove. amount_to_unstake_as_balance.set_unit(netuid) if amount_to_unstake_as_balance > current_stake_balance: err_console.print( - f"[red]Not enough stake to remove[/red]:[bold white]\n stake balance:{current_stake_balance}" - f" < unstaking amount: {amount_to_unstake_as_balance}[/bold white]" + f"[red]Not enough stake to remove[/red]:\n Stake balance: [dark_orange]{current_stake_balance}[/dark_orange]" + f" < Unstaking amount: [dark_orange]{amount_to_unstake_as_balance}[/dark_orange]" ) - return False + continue # Skip to the next subnet received_amount, slippage = dynamic_info.alpha_to_tao_with_slippage( amount_to_unstake_as_balance @@ -1333,21 +1358,40 @@ async def unstake( slippage_pct = f"{slippage_pct_float}%" max_float_slippage = max(max_float_slippage, slippage_pct_float) - rows.append( - ( - str(netuid), - # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}", - f"{staking_address_ss58}", - str(amount_to_unstake_as_balance), - str(float(dynamic_info.price)) - + f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", - str(received_amount), - str(slippage_pct), - ) + unstake_operations.append( + { + "netuid": netuid, + "hotkey_name": staking_address_name + if staking_address_name + else staking_address_ss58, + "hotkey_ss58": staking_address_ss58, + "amount_to_unstake": amount_to_unstake_as_balance, + "current_stake_balance": current_stake_balance, + "received_amount": received_amount, + "slippage_pct": slippage_pct, + "slippage_pct_float": slippage_pct_float, + "dynamic_info": dynamic_info, + } ) + if not unstake_operations: + console.print("[red]No unstake operations to perform.[/red]") + return False + + # Build the table + table = Table( + title=f"\n[dark_orange]Unstaking to: \nWallet: [light_goldenrod2]{wallet.name}[/light_goldenrod2], Coldkey ss58: [light_goldenrod2]{wallet.coldkeypub.ss58_address}[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style="light_salmon3") + table.add_column("Hotkey", justify="center", style="bright_magenta") table.add_column( f"Amount ({Balance.get_unit(1)})", justify="center", style="dark_sea_green" ) @@ -1357,24 +1401,35 @@ async def unstake( style="light_goldenrod2", ) table.add_column( - f"Recieved ({Balance.get_unit(0)})", + f"Received ({Balance.get_unit(0)})", justify="center", style="light_slate_blue", footer=f"{total_received_amount}", ) table.add_column("Slippage", justify="center", style="rgb(220,50,47)") - for row in rows: - table.add_row(*row) + + for op in unstake_operations: + dynamic_info = op["dynamic_info"] + table.add_row( + str(op["netuid"]), + op["hotkey_name"], + str(op["amount_to_unstake"]), + str(float(dynamic_info.price)) + + f"({Balance.get_unit(0)}/{Balance.get_unit(op['netuid'])})", + str(op["received_amount"]), + op["slippage_pct"], + ) + console.print(table) - message = "" + if max_float_slippage > 5: - message += f"-------------------------------------------------------------------------------------------------------------------\n" - message += f"[bold][yellow]WARNING:[/yellow]\tThe slippage on one of your operations is high: [bold red]{max_float_slippage} %[/bold red], this may result in a loss of funds.[/bold] \n" - message += f"-------------------------------------------------------------------------------------------------------------------\n" - console.print(message) - if prompt: - if not Confirm.ask("Would you like to continue?"): - return False + console.print( + "\n" + f"-------------------------------------------------------------------------------------------------------------------\n" + f"[bold][yellow]WARNING:[/yellow] The slippage on one of your operations is high: [bold red]{max_float_slippage}%[/bold red], this may result in a loss of funds.[/bold] \n" + f"-------------------------------------------------------------------------------------------------------------------\n" + ) + console.print( """ [bold white]Description[/bold white]: @@ -1388,63 +1443,73 @@ async def unstake( - [bold white]Slippage[/bold white]: The slippage percentage of the unstake operation. (0% if the subnet is not dynamic i.e. root). """ ) + if prompt: + if not Confirm.ask("Would you like to continue?"): + return False - # Perform staking operation. + # Perform unstaking operations try: wallet.unlock_coldkey() except KeyFileError: err_console.print("Error decrypting coldkey (possibly incorrect password)") return False - with console.status( - f"\n:satellite: Unstaking {amount_to_unstake_as_balance} from {staking_address_name} on netuid: {netuid} ..." - ): - for hotkey in hotkeys_to_unstake_from: - staking_address_name, staking_address_ss58 = hotkey - for netuid_i, amount, current in list( - zip(non_zero_netuids, unstake_amount_balance, current_stake_balances) - ): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": staking_address_ss58, - "netuid": netuid_i, - "amount_unstaked": amount.rao, - }, - ) - extrinsic = await subtensor.substrate.create_signed_extrinsic( - call=call, keypair=wallet.coldkey - ) - response = await subtensor.substrate.submit_extrinsic( - extrinsic, wait_for_inclusion=True, wait_for_finalization=False - ) - if not prompt: - console.print(":white_heavy_check_mark: [green]Sent[/green]") + + with console.status(f"\n:satellite: Performing unstaking operations...") as status: + for op in unstake_operations: + netuid_i = op["netuid"] + staking_address_name = op["hotkey_name"] + staking_address_ss58 = op["hotkey_ss58"] + amount = op["amount_to_unstake"] + current_stake_balance = op["current_stake_balance"] + + status.update( + f"\n:satellite: Unstaking {amount} from {staking_address_name} on netuid: {netuid_i} ..." + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": staking_address_ss58, + "netuid": netuid_i, + "amount_unstaked": amount.rao, + }, + ) + extrinsic = await subtensor.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = await subtensor.substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=True, wait_for_finalization=False + ) + if not prompt: + console.print(":white_heavy_check_mark: [green]Sent[/green]") + else: + await response.process_events() + if not await response.is_success: + print_error( + f":cross_mark: [red]Failed[/red] with error: " + f"{format_error_message(await response.error_message, subtensor.substrate)}", + status, + ) else: - await response.process_events() - if not await response.is_success: - err_console.print( - f":cross_mark: [red]Failed[/red] with error: " - f"{format_error_message(response.error_message, subtensor.substrate)}" - ) - else: - new_balance_ = await subtensor.get_balance( - wallet.coldkeypub.ss58_address - ) - new_balance = new_balance_[wallet.coldkeypub.ss58_address] - new_stake = ( - await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=staking_address_ss58, - netuid=netuid_i, - ) - ).set_unit(netuid_i) - console.print( - f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - console.print( - f"Subnet: {netuid_i} Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" + new_balance_ = await subtensor.get_balance( + wallet.coldkeypub.ss58_address + ) + new_balance = new_balance_[wallet.coldkeypub.ss58_address] + new_stake = ( + await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=staking_address_ss58, + netuid=netuid_i, ) + ).set_unit(netuid_i) + console.print( + f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + console.print( + f"Subnet: {netuid_i} Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + console.print("[green]Unstaking operations completed.[/green]") async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): From fd5f801e7ceff914ec3e1a899f824dfc5cc0839d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 12:03:28 -0700 Subject: [PATCH 088/157] Updates localnet entry point --- bittensor_cli/src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index e36d76b5..9e46757b 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -10,7 +10,7 @@ class Constants: archive_entrypoint = "wss://archive.chain.opentensor.ai:443" rao_entrypoint = "wss://rao.chain.opentensor.ai:443/" dev_entrypoint = "wss://dev.chain.opentensor.ai:443 " - local_entrypoint = "ws://127.0.0.1:9444" + local_entrypoint = "ws://127.0.0.1:9944" network_map = { "finney": finney_entrypoint, "test": finney_test_entrypoint, From 642103d1c554967efc56e4193a19924cda783dfd Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 12:09:16 -0700 Subject: [PATCH 089/157] Updated stake slippage table --- bittensor_cli/src/commands/stake/stake.py | 28 +++++++---------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 2aee12b6..04a8b3cf 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -862,27 +862,15 @@ async def stake_add( ) # Init the table. table = Table( - title="[white]Staking operation from Coldkey SS58[/white]: " - f"[bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n", - width=console.width - 5, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, + title=f"\n[dark_orange]Staking to: \nWallet: [light_goldenrod2]{wallet.name}[/light_goldenrod2], Coldkey ss58: [light_goldenrod2]{wallet.coldkeypub.ss58_address}[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, + show_lines=False, + pad_edge=True, ) # Determine the amount we are staking. @@ -1027,7 +1015,7 @@ async def stake_add( ) ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style="light_salmon3") + table.add_column("Hotkey", justify="center", style="bright_magenta") table.add_column( f"Amount ({Balance.get_unit(0)})", justify="center", style="dark_sea_green" ) @@ -1048,7 +1036,7 @@ async def stake_add( message = "" if max_slippage > 5: message += "-------------------------------------------------------------------------------------------------------------------\n" - message += f"[bold][yellow]WARNING:[/yellow]\tThe slippage on one of your operations is high: [bold red]{max_slippage} %[/bold red], this may result in a loss of funds.[/bold] \n" + message += f"[bold][yellow]WARNING:[/yellow] The slippage on one of your operations is high: [bold red]{max_slippage} %[/bold red], this may result in a loss of funds.[/bold] \n" message += "-------------------------------------------------------------------------------------------------------------------\n" console.print(message) console.print( From 35b5aee47a724126743d6cf93c46416c5b0342ad Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 12:46:16 -0700 Subject: [PATCH 090/157] Updates s create table + ui tweaks in stake/unstake --- bittensor_cli/src/commands/stake/stake.py | 4 +-- bittensor_cli/src/commands/subnets.py | 33 +++++++---------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 04a8b3cf..4480fb7f 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1109,7 +1109,7 @@ async def send_extrinsic( f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) console.print( - f"Subnet: {netuid_i} Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" + f"Subnet: [dark_orange]{netuid_i}[/dark_orange] Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" ) # Perform staking operation. @@ -1495,7 +1495,7 @@ async def unstake( f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) console.print( - f"Subnet: {netuid_i} Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [green]{new_stake}[/green]" + f"Subnet: [dark_orange]{netuid_i}[/dark_orange] Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [green]{new_stake}[/green]" ) console.print("[green]Unstaking operations completed.[/green]") diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 51295d05..ff067136 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -597,30 +597,17 @@ async def register( if prompt: # TODO make this a reusable function, also used in subnets list # Show creation table. - console_width = console.width - 5 table = Table( - title="Subnet Info", - width=console_width, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, + title=f"\n[white]Register to netuid [dark_orange]{netuid}[/dark_orange]\nNetwork: [dark_orange]{subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, + show_lines=False, + pad_edge=True, ) - table.title = f"[white]Register - {subtensor.network}\n" table.add_column( "Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center" ) @@ -629,15 +616,15 @@ async def register( ) table.add_column( f"Cost ({Balance.get_unit(0)})", - style="rgb(38,139,210)", + style="light_goldenrod2", no_wrap=True, - justify="right", + justify="center", ) table.add_column( - "Hotkey", style="light_salmon3", no_wrap=True, justify="center" + "Hotkey", style="bright_magenta", no_wrap=True, justify="center" ) table.add_column( - "Coldkey", style="bold dark_green", no_wrap=True, justify="center" + "Coldkey", style="bold bright_magenta", no_wrap=True, justify="center" ) table.add_row( str(netuid), From 757cc7fde1fa7b8f8680e66541d0d98484d44c60 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 28 Oct 2024 22:59:52 +0200 Subject: [PATCH 091/157] Fixed NeuronInfoLite decoding. --- bittensor_cli/src/commands/wallets.py | 40 ++++----------------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 1b0fe474..f3382a53 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1139,25 +1139,6 @@ async def _fetch_all_neurons( ) -def _partial_decode(args): - """ - Helper function for passing to ProcessPoolExecutor that decodes scale bytes based on a set return type and - rpc type registry, passing this back to the Executor with its specified netuid for easier mapping - - :param args: (return type, scale bytes object, custom rpc type registry, netuid) - - :return: (original netuid, decoded object) - """ - return_type, as_scale_bytes, custom_rpc_type_registry_, netuid_ = args - decoded = decode_scale_bytes(return_type, as_scale_bytes, custom_rpc_type_registry_) - if decoded.startswith("0x"): - bytes_result = bytes.fromhex(decoded[2:]) - else: - bytes_result = bytes.fromhex(decoded) - - return netuid_, NeuronInfoLite.list_from_vec_u8(bytes_result) - - def _process_neurons_for_netuids( netuids_with_all_neurons_hex_bytes: list[tuple[int, list[ScaleBytes]]], ) -> list[tuple[int, list[NeuronInfoLite]]]: @@ -1167,22 +1148,10 @@ def _process_neurons_for_netuids( :param netuids_with_all_neurons_hex_bytes: netuids with hex-bytes neurons :return: netuids mapped to decoded neurons """ - - def make_map(res_): - netuid_, json_result = res_ - hex_bytes_result = json_result["result"] - as_scale_bytes = scalecodec.ScaleBytes(hex_bytes_result) - return [return_type, as_scale_bytes, custom_rpc_type_registry, netuid_] - - return_type = TYPE_REGISTRY["runtime_api"]["NeuronInfoRuntimeApi"]["methods"][ - "get_neurons_lite" - ]["type"] - - preprocessed = [make_map(r) for r in netuids_with_all_neurons_hex_bytes] - with ProcessPoolExecutor() as executor: - results = list(executor.map(_partial_decode, preprocessed)) - - all_results = [(netuid, result) for netuid, result in results] + all_results = [ + (netuid, NeuronInfoLite.list_from_vec_u8(bytes.fromhex(result[2:]))) + for netuid, result in netuids_with_all_neurons_hex_bytes + ] return all_results @@ -1190,6 +1159,7 @@ async def _get_neurons_for_netuids( subtensor: SubtensorInterface, netuids: list[int], hot_wallets: list[str] ) -> list[tuple[int, list["NeuronInfoLite"], Optional[str]]]: all_neurons_hex_bytes = await _fetch_all_neurons(netuids, subtensor) + print(all_neurons_hex_bytes) all_processed_neurons = _process_neurons_for_netuids(all_neurons_hex_bytes) return [ From 13b54077bff5d9f48d9ebb37e5d10da7c891e9b5 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 14:04:49 -0700 Subject: [PATCH 092/157] Removes print statement --- bittensor_cli/src/commands/wallets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index f3382a53..bc3b4e69 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1159,7 +1159,6 @@ async def _get_neurons_for_netuids( subtensor: SubtensorInterface, netuids: list[int], hot_wallets: list[str] ) -> list[tuple[int, list["NeuronInfoLite"], Optional[str]]]: all_neurons_hex_bytes = await _fetch_all_neurons(netuids, subtensor) - print(all_neurons_hex_bytes) all_processed_neurons = _process_neurons_for_netuids(all_neurons_hex_bytes) return [ From e61c094b04e5163c2c1336175fe067df279397bf Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 14:12:43 -0700 Subject: [PATCH 093/157] Fixed stake move + ui --- bittensor_cli/src/commands/stake/stake.py | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 4480fb7f..c5687301 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1702,7 +1702,6 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): - [bold white]Swap[/bold white]: The amount of TAO received when unstaking all of the hotkey's stake (with slippage). - [bold white]Registered[/bold white]: Whether the hotkey is registered on this subnet. - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. - - [bold white]Locked[/bold white]: The total amount of stake locked (not able to be unstaked). """ ) @@ -1781,7 +1780,7 @@ async def move_stake( subtensor.get_subnet_dynamic_info(origin_netuid), subtensor.get_subnet_dynamic_info(destination_netuid), ) - price = float(dynamic_origin.price) * 1 / float(dynamic_destination.price) + price = float(dynamic_origin.price) * 1 / (float(dynamic_destination.price) or 1) received_amount_tao, slippage = dynamic_origin.alpha_to_tao_with_slippage( amount_to_move_as_balance ) @@ -1822,26 +1821,37 @@ async def move_stake( title_justify="center", highlight=False, ) - table.add_column("origin netuid", justify="center", style="rgb(133,153,0)") - table.add_column("origin hotkey", justify="center", style="rgb(38,139,210)") - table.add_column("dest netuid", justify="center", style="rgb(133,153,0)") - table.add_column("dest hotkey", justify="center", style="rgb(38,139,210)") + table = Table( + title=f"\n[dark_orange]Moving stake from: [light_goldenrod2]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})[/light_goldenrod2] to: [light_goldenrod2]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) + table.add_column("origin netuid", justify="center", style="green") + table.add_column("origin hotkey", justify="center", style="bright_magenta") + table.add_column("dest netuid", justify="center", style="green") + table.add_column("dest hotkey", justify="center", style="bright_magenta") table.add_column( f"amount ({Balance.get_unit(origin_netuid)})", justify="center", - style="rgb(38,139,210)", + style="medium_purple", ) table.add_column( f"rate ({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})", justify="center", - style="rgb(42,161,152)", + style="cyan", ) table.add_column( f"received ({Balance.get_unit(destination_netuid)})", justify="center", style="rgb(220,50,47)", ) - table.add_column("slippage", justify="center", style="rgb(181,137,0)") + table.add_column("slippage", justify="center", style="salmon1") table.add_row( f"{Balance.get_unit(origin_netuid)}({origin_netuid})", @@ -1883,7 +1893,7 @@ async def move_stake( "origin_netuid": origin_netuid, "destination_hotkey": destination_hotkey, "destination_netuid": destination_netuid, - "amount_moved": amount_to_move_as_balance.rao, + "alpha_amount": amount_to_move_as_balance.rao, }, ) extrinsic = await subtensor.substrate.create_signed_extrinsic( From 3d7d46ddf46254830a593220259fd897ecc8795c Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 14:15:20 -0700 Subject: [PATCH 094/157] Removes extra table initialization --- bittensor_cli/src/commands/stake/stake.py | 40 +++++++---------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index c5687301..7c76ab8e 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1780,7 +1780,11 @@ async def move_stake( subtensor.get_subnet_dynamic_info(origin_netuid), subtensor.get_subnet_dynamic_info(destination_netuid), ) - price = float(dynamic_origin.price) * 1 / (float(dynamic_destination.price) or 1) + price = ( + float(dynamic_origin.price) + * 1 + / (float(dynamic_destination.price) or 1) + ) received_amount_tao, slippage = dynamic_origin.alpha_to_tao_with_slippage( amount_to_move_as_balance ) @@ -1800,38 +1804,16 @@ async def move_stake( ) table = Table( - title="[white]Move Stake", - width=console.width - 5, - safe_box=True, - padding=(0, 1), - collapse_padding=False, - pad_edge=True, - expand=True, - show_header=True, + title=f"\n[dark_orange]Moving stake from: [light_goldenrod2]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})[/light_goldenrod2] to: [light_goldenrod2]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", show_footer=True, show_edge=False, - show_lines=False, - leading=0, - style="none", - row_styles=None, - header_style="bold", - footer_style="bold", - border_style="rgb(7,54,66)", - title_style="bold magenta", + header_style="bold white", + border_style="bright_black", + style="bold", title_justify="center", - highlight=False, + show_lines=False, + pad_edge=True, ) - table = Table( - title=f"\n[dark_orange]Moving stake from: [light_goldenrod2]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})[/light_goldenrod2] to: [light_goldenrod2]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, - ) table.add_column("origin netuid", justify="center", style="green") table.add_column("origin hotkey", justify="center", style="bright_magenta") table.add_column("dest netuid", justify="center", style="green") From 73ddea0bcc9c6384f45af1dce0a83e43717b8d63 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 28 Oct 2024 23:17:32 +0200 Subject: [PATCH 095/157] Registration fix. --- bittensor_cli/src/bittensor/extrinsics/registration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index 5cfead70..b45b3a5a 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -494,7 +494,7 @@ async def get_neuron_for_pubkey_and_subnet(): if uid is None: return NeuronInfo.get_null_neuron() - hex_bytes_result = await subtensor.substrate.query_runtime_api( + hex_bytes_result = await subtensor.query_runtime_api( runtime_api="NeuronInfoRuntimeApi", method="get_neuron", params=[netuid, uid], From f8b302940fa85581b646ca0775efffcd48b02b3e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 28 Oct 2024 15:08:59 -0700 Subject: [PATCH 096/157] btcli stake move improved --- bittensor_cli/cli.py | 4 ++-- bittensor_cli/src/commands/stake/stake.py | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 7ac8d4bc..efdc643b 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2784,10 +2784,10 @@ def stake_move( None, help="Destination hotkey", prompt=False ), amount: float = typer.Option( - 0.0, + None, "--amount", help="The amount of TAO to stake", - prompt=True, + prompt=False, ), stake_all: bool = typer.Option( False, "--stake-all", "--all", help="Stake all", prompt=False diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 7c76ab8e..ae4076b6 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -2,6 +2,7 @@ from functools import partial from typing import TYPE_CHECKING, Optional, Sequence, Union, cast +import typer from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError @@ -1734,6 +1735,19 @@ async def move_stake( ) ).set_unit(destination_netuid) + if origin_stake_balance == Balance.from_tao(0).set_unit(origin_netuid): + print_error( + f"Your balance is [dark_orange]0[/dark_orange] in Netuid: [dark_orange]{origin_netuid}[/dark_orange]" + ) + raise typer.Exit() + + console.print( + f"\nOrigin netuid: [dark_orange]{origin_netuid}[/dark_orange], Origin stake: [dark_orange]{origin_stake_balance}" + ) + console.print( + f"Destination netuid: [dark_orange]{destination_netuid}[/dark_orange], Destination stake: [dark_orange]{destination_stake_balance}\n" + ) + # Determine the amount we are moving. amount_to_move_as_balance = None if amount: @@ -1742,25 +1756,25 @@ async def move_stake( amount_to_move_as_balance = origin_stake_balance else: # max_stake # TODO improve this - if Confirm.ask(f"Move all: [bold]{origin_stake_balance}[/bold]?"): + if Confirm.ask(f"Move all: [dark_orange]{origin_stake_balance}[/dark_orange]?"): amount_to_move_as_balance = origin_stake_balance else: try: amount = float( Prompt.ask( - f"Enter amount to move in {Balance.get_unit(origin_netuid)}" + f"Enter amount to move in [dark_orange]{Balance.get_unit(origin_netuid)}" ) ) amount_to_move_as_balance = Balance.from_tao(amount) except ValueError: - err_console.print(f":cross_mark:[red]Invalid amount: {amount}[/red]") + print_error(f":cross_mark: Invalid amount: {amount}") return False # Check enough to move. amount_to_move_as_balance.set_unit(origin_netuid) if amount_to_move_as_balance > origin_stake_balance: err_console.print( - f"[red]Not enough stake[/red]:[bold white]\n stake balance:{origin_stake_balance} < moving amount: {amount_to_move_as_balance}[/bold white]" + f"[red]Not enough stake[/red]:\n Stake balance:[dark_orange]{origin_stake_balance}[/dark_orange] < Moving amount: [dark_orange]{amount_to_move_as_balance}[/dark_orange]" ) return False From af134ea52c44523b214b9e2cd51d6cb5061fe409 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 29 Oct 2024 13:47:20 +0200 Subject: [PATCH 097/157] Fixed bug where format_error_message was not receiving a substrate object in transfer --- bittensor_cli/src/bittensor/extrinsics/transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/transfer.py b/bittensor_cli/src/bittensor/extrinsics/transfer.py index 620c20d7..9d0d5a72 100644 --- a/bittensor_cli/src/bittensor/extrinsics/transfer.py +++ b/bittensor_cli/src/bittensor/extrinsics/transfer.py @@ -98,7 +98,7 @@ async def do_transfer() -> tuple[bool, str, str]: block_hash_ = response.block_hash return True, block_hash_, "" else: - return False, "", format_error_message(await response.error_message) + return False, "", format_error_message(await response.error_message, subtensor.substrate) # Validate destination address. if not is_valid_bittensor_address_or_public_key(destination): From fb59bc00bf53bf65752c50f1f80cdaae67fe7de1 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 09:04:23 -0700 Subject: [PATCH 098/157] Hides overview, inspect, metagraph --- bittensor_cli/cli.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index efdc643b..471197fd 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -622,15 +622,15 @@ def __init__(self): self.wallet_app.command( "history", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] )(self.wallet_history) - self.wallet_app.command( - "overview", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] - )(self.wallet_overview) + # self.wallet_app.command( + # "overview", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] + # )(self.wallet_overview) self.wallet_app.command( "transfer", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] )(self.wallet_transfer) - self.wallet_app.command( - "inspect", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] - )(self.wallet_inspect) + # self.wallet_app.command( + # "inspect", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] + # )(self.wallet_inspect) self.wallet_app.command( "faucet", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] )(self.wallet_faucet) @@ -717,9 +717,9 @@ def __init__(self): self.subnets_app.command( "register", rich_help_panel=HELP_PANELS["SUBNETS"]["REGISTER"] )(self.subnets_register) - self.subnets_app.command( - "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] - )(self.subnets_metagraph) + # self.subnets_app.command( + # "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] + # )(self.subnets_metagraph) self.subnets_app.command( "show", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] )(self.subnets_show) From de3c03d64a08a632f92ec530b5db4dbc0da58afb Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 09:18:18 -0700 Subject: [PATCH 099/157] Fixes set-identity --- bittensor_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 471197fd..d53d2c11 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2319,8 +2319,8 @@ def pgp_check(s: str): twitter_url, info_, validator_id, - prompt, subnet_netuid, + prompt, ) ) From fcd5e1ae10eab3339d6169cd1b42bfb91efba9f7 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 09:40:56 -0700 Subject: [PATCH 100/157] Uses coldkeypub in set-identity --- bittensor_cli/src/commands/wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index bc3b4e69..0efe8dc1 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1524,7 +1524,7 @@ async def set_id( return False identified = ( - wallet.hotkey.ss58_address if validator_id else wallet.coldkey.ss58_address + wallet.hotkey.ss58_address if validator_id else wallet.coldkeypub.ss58_address ) encoded_id_dict = { "info": { From 999090998f3b8b2d6f59d8fdf0656fa26a9c6850 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 10:50:07 -0700 Subject: [PATCH 101/157] updates subnets show --- bittensor_cli/src/commands/stake/stake.py | 1 - bittensor_cli/src/commands/subnets.py | 25 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index ae4076b6..86d1aaac 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1618,7 +1618,6 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): footer_style="white", style="light_goldenrod1", justify="center", - width=5, no_wrap=True, ) table.add_column( diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index ff067136..28d2862a 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -257,6 +257,7 @@ async def subnets_list( - [bold white]Stake[/bold white]: The outstanding supply of stake across all staking accounts on this subnet. - [bold white]Rate[/bold white]: The rate of conversion between TAO and the subnet's staking unit. - [bold white]Tempo[/bold white]: The number of blocks between epochs. Represented as (k/n) where k is the blocks since the last epoch and n is the total blocks in the epoch. + - [bold white]Global weight[/bold white]: The global weight of the subnet across all subnets. """ ) @@ -341,7 +342,7 @@ async def show_root(): str((pos + 1)), str(root_state.global_stake[idx]), str(root_state.local_stake[idx]), - f"{str(total_emission_per_block):.4f}", + f"{(total_emission_per_block)}", f"{root_state.hotkeys[idx]}", f"{root_state.coldkeys[idx]}", ) @@ -358,6 +359,7 @@ async def show_root(): - Stake: The stake balance of this hotkey on root (measured in TAO). - Emission: The emission accrued to this hotkey across all subnets every block measured in TAO. - Hotkey: The hotkey ss58 address. + - Coldkey: The coldkey ss58 address. """ ) @@ -406,12 +408,14 @@ async def show_subnet(netuid_: int): ) tao_sum = Balance(0) stake_sum = Balance(0) + relative_emissions_sum = 0 for idx, hk in enumerate(subnet_state.hotkeys): hotkey_block_emission = ( subnet_state.emission[idx].tao / emission_sum if emission_sum != 0 else 0 ) + relative_emissions_sum += hotkey_block_emission tao_sum += subnet_state.global_stake[idx] stake_sum += subnet_state.local_stake[idx] rows.append( @@ -421,9 +425,10 @@ async def show_subnet(netuid_: int): f"{subnet_state.local_stake[idx].tao:.4f} {subnet_info.symbol}", # Stake f"{subnet_state.stake_weight[idx]:.4f}", # Weight # str(subnet_state.dividends[idx]), - f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f} {subnet_info.symbol}", # Dividents + f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f}", # Dividends str(subnet_state.incentives[idx]), # Incentive - f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f} {subnet_info.symbol}", # Emission + # f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f}", # Emissions relative + f"{Balance.from_tao(subnet_state.emission[idx].tao).set_unit(netuid_).tao:.5f} {subnet_info.symbol}", # Emissions f"{subnet_state.hotkeys[idx]}", # Hotkey f"{subnet_state.coldkeys[idx]}", # Coldkey ) @@ -450,10 +455,19 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="center", ) - table.add_column("Dividends", style="#8787d7", no_wrap=True, justify="center") + table.add_column("Dividends", style="#8787d7", no_wrap=True, justify="center", footer=f"{relative_emissions_sum:.3f}",) table.add_column("Incentive", style="#5fd7ff", no_wrap=True, justify="center") + + # Hiding relative emissions for now + # table.add_column( + # "Emissions", + # style="light_goldenrod2", + # no_wrap=True, + # justify="center", + # footer=f"{relative_emissions_sum:.3f}", + # ) table.add_column( - f"Emission ({Balance.get_unit(netuid_)})", + f"Emissions ({Balance.get_unit(netuid_)})", style="light_goldenrod2", no_wrap=True, justify="center", @@ -488,6 +502,7 @@ async def show_subnet(netuid_: int): - Incentives: Mining incentives earned by the hotkey (always zero in the RAO demo.) - Emission: The emission accrued to this hokey on this subnet every block (in staking units). - Hotkey: The hotkey ss58 address. + - Coldkey: The coldkey ss58 address. """ ) From 4b448f092fa5c2dd184366ab698b2c265fb9df45 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 29 Oct 2024 16:22:15 -0400 Subject: [PATCH 102/157] should be / emission_drain_tempo --- bittensor_cli/src/commands/stake/stake.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 86d1aaac..0e7ee3c5 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1562,9 +1562,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): slippage_percentage = "0.000%" tao_locked = pool.tao_in issuance = pool.alpha_out if pool.is_dynamic else tao_locked - per_block_emission = substake_.emission.tao / ( - (emission_drain_tempo / pool.tempo) * pool.tempo - ) + per_block_emission = substake_.emission.tao / emission_drain_tempo if alpha_value.tao > 0.00009: if issuance.tao != 0: alpha_ownership = "{:.4f}".format( From 9733a957795178d297cd394d35c03d0f348086b5 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 13:54:10 -0700 Subject: [PATCH 103/157] Updates color schemes --- bittensor_cli/cli.py | 6 +++--- bittensor_cli/src/commands/stake/stake.py | 24 ++++++++++----------- bittensor_cli/src/commands/subnets.py | 26 +++++++++++------------ bittensor_cli/src/commands/sudo.py | 6 +++--- bittensor_cli/src/commands/wallets.py | 8 +++---- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d53d2c11..159619a4 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -291,7 +291,7 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in return None elif netuid is None and all_netuids is False: answer = Prompt.ask( - "[green]Enter the netuid to use.[/green] Leave blank for all netuids", + "[dark_sea_green3]Enter the netuid to use.[/dark_sea_green3] Leave blank for all netuids", default=None, show_default=False, ) @@ -2580,11 +2580,11 @@ def stake_add( raise typer.Exit() if netuid is not None: amount = FloatPrompt.ask( - "[dark_orange]Amount to stake (TAO τ)[/dark_orange]" + "[dark_sea_green]Amount to stake (TAO τ)[/dark_sea_green]" ) else: amount = FloatPrompt.ask( - "[dark_orange]Amount to stake to each netuid (TAO τ)[/dark_orange]" + "[dark_sea_green]Amount to stake to each netuid (TAO τ)[/dark_sea_green]" ) if Balance.from_tao(amount) > free_balance: print_error( diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 0e7ee3c5..8f442158 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1228,7 +1228,7 @@ async def unstake( if len(netuids) > 1: console.print( - "[green]Tip: Enter 'q' any time to stop going over remaining subnets and process current unstakes." + "[dark_sea_green3]Tip: Enter 'q' any time to stop going over remaining subnets and process current unstakes.\n" ) for netuid in netuids: @@ -1260,8 +1260,8 @@ async def unstake( # Prompt the user for each subnet while True: response = Prompt.ask( - f"Unstake all: [dark_orange]{current_stake_balance}[/dark_orange]" - f" from [bright_magenta]{staking_address_name if staking_address_name else staking_address_ss58}[/bright_magenta] on netuid: [dark_orange]{netuid}? [y/n/q]", + f"Unstake all: [dark_sea_green3]{current_stake_balance}[/dark_sea_green3]" + f" from [dark_sea_green3]{staking_address_name if staking_address_name else staking_address_ss58}[/dark_sea_green3] on netuid: [dark_sea_green3]{netuid}? [y/n/q]", choices=["y", "n", "q"], default="n", show_choices=True, @@ -1277,7 +1277,7 @@ async def unstake( elif response.lower() == "n": while True: amount_input = Prompt.ask( - f"Enter amount to unstake in [dark_orange]{Balance.get_unit(netuid)}[/dark_orange] from subnet: [dark_orange]{netuid}[/dark_orange] (Max: [dark_orange]{current_stake_balance}[/dark_orange])" + f"Enter amount to unstake in [dark_sea_green3]{Balance.get_unit(netuid)}[/dark_sea_green3] from subnet: [dark_sea_green3]{netuid}[/dark_sea_green3] (Max: [dark_sea_green3]{current_stake_balance}[/dark_sea_green3])" ) if amount_input.lower() == "q": skip_remaining_subnets = True @@ -1369,7 +1369,7 @@ async def unstake( # Build the table table = Table( - title=f"\n[dark_orange]Unstaking to: \nWallet: [light_goldenrod2]{wallet.name}[/light_goldenrod2], Coldkey ss58: [light_goldenrod2]{wallet.coldkeypub.ss58_address}[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", + title=f"\n[navajo_white1]Unstaking to: \nWallet: [dark_sea_green3]{wallet.name}[/dark_sea_green3], Coldkey ss58: [dark_sea_green3]{wallet.coldkeypub.ss58_address}[/dark_sea_green3]\nNetwork: {subtensor.network}[/navajo_white1]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1380,7 +1380,7 @@ async def unstake( pad_edge=True, ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style="bright_magenta") + table.add_column("Hotkey", justify="center", style="plum2") table.add_column( f"Amount ({Balance.get_unit(1)})", justify="center", style="dark_sea_green" ) @@ -1395,7 +1395,7 @@ async def unstake( style="light_slate_blue", footer=f"{total_received_amount}", ) - table.add_column("Slippage", justify="center", style="rgb(220,50,47)") + table.add_column("Slippage", justify="center", style="light_salmon3") for op in unstake_operations: dynamic_info = op["dynamic_info"] @@ -1559,7 +1559,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ) slippage_percentage = f"[salmon1]{slippage_percentage_:.3f}%[/salmon1]" else: - slippage_percentage = "0.000%" + slippage_percentage = "[salmon1]0.000%[/salmon1]" tao_locked = pool.tao_in issuance = pool.alpha_out if pool.is_dynamic else tao_locked per_block_emission = substake_.emission.tao / emission_drain_tempo @@ -1680,10 +1680,10 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): console.print("\n\n") console.print( f"Wallet:\n" - f" Coldkey SS58: [bold dark_green]{wallet.coldkeypub.ss58_address}[/bold dark_green]\n" - f" Free Balance: [aquamarine3]{balance}[/aquamarine3]\n" - f" Total TAO ({Balance.unit}): [aquamarine3]{all_hotkeys_total_global_tao}[/aquamarine3]\n" - f" Total Value ({Balance.unit}): [aquamarine3]{all_hotkeys_total_tao_value}[/aquamarine3]" + f" Coldkey SS58: [bold plum2]{wallet.coldkeypub.ss58_address}[/bold plum2]\n" + f" Free Balance: [dark_sea_green]{balance}[/dark_sea_green]\n" + f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" + f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" ) console.print( """ diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 28d2862a..e311b8fd 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -192,7 +192,7 @@ async def subnets_list( ) table = Table( - title=f"[underline dark_orange]Subnets[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", + title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -207,7 +207,7 @@ async def subnets_list( table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") table.add_column( f"[bold white]EMISSION ({Balance.get_unit(0)})", - style="light_goldenrod2", + style="tan", justify="right", footer=f"τ {total_emissions:.4f}", ) @@ -228,11 +228,11 @@ async def subnets_list( ) table.add_column( "[bold white]Tempo (k/n)", - style="bright_magenta", + style="plum2", justify="right", overflow="fold", ) - table.add_column("[bold white]Global weight (γ)", style="green", justify="center") + table.add_column("[bold white]Global weight (γ)", style="dark_sea_green3", justify="center") # Sort rows by subnet.emission.tao, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) @@ -309,17 +309,17 @@ async def show_root(): ) table.add_column( f"[bold white]Emission ({Balance.get_unit(0)}/block)", - style="light_goldenrod2", + style="tan", justify="center", ) table.add_column( "[bold white]Hotkey", - style="bright_magenta", + style="plum2", justify="center", ) table.add_column( "[bold white]Coldkey", - style="bright_magenta", + style="plum2", justify="center", ) @@ -389,7 +389,7 @@ async def show_subnet(netuid_: int): # Define table properties table = Table( - title=f"[underline dark_orange]Subnet {netuid_}[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", + title=f"[underline navajo_white1]Subnet {netuid_}[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -468,16 +468,16 @@ async def show_subnet(netuid_: int): # ) table.add_column( f"Emissions ({Balance.get_unit(netuid_)})", - style="light_goldenrod2", + style="tan", no_wrap=True, justify="center", footer=str(Balance.from_tao(emission_sum).set_unit(subnet_info.netuid)), ) table.add_column( - "Hotkey", style="bright_magenta", no_wrap=True, justify="center" + "Hotkey", style="plum2", no_wrap=True, justify="center" ) table.add_column( - "Coldkey", style="bright_magenta", no_wrap=True, justify="center" + "Coldkey", style="plum2", no_wrap=True, justify="center" ) for row in rows: table.add_row(*row) @@ -487,7 +487,7 @@ async def show_subnet(netuid_: int): console.print(table) console.print("\n") console.print( - f"Subnet: {netuid_}:\n Owner: [bold bright_magenta]{subnet_info.owner}[/bold bright_magenta]\n Total Locked: [green]{subnet_info.total_locked}[/green]\n Owner Locked: [green]{subnet_info.owner_locked}[/green]" + f"Subnet: {netuid_}:\n Owner: [bold plum2]{subnet_info.owner}[/bold plum2]\n Total Locked: [dark_sea_green]{subnet_info.total_locked}[/dark_sea_green]\n Owner Locked: [dark_sea_green]{subnet_info.owner_locked}[/dark_sea_green]" ) console.print( """ @@ -631,7 +631,7 @@ async def register( ) table.add_column( f"Cost ({Balance.get_unit(0)})", - style="light_goldenrod2", + style="tan", no_wrap=True, justify="center", ) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 641fd7ba..36bab3a1 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -496,11 +496,11 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int): subnet = await subtensor.get_subnet_hyperparameters(netuid) table = Table( - Column("[white]HYPERPARAMETER", style="bright_magenta"), + Column("[white]HYPERPARAMETER", style="plum2"), Column("[white]VALUE", style="light_goldenrod2"), Column("[white]NORMALIZED", style="light_goldenrod3"), - title=f"[underline dark_orange]\nSubnet Hyperparameters[/underline dark_orange]\n NETUID: [dark_orange]" - f"{netuid}[/dark_orange] - Network: [dark_orange]{subtensor.network}[/dark_orange]\n", + title=f"[underline navajo_white1]\nSubnet Hyperparameters[/underline navajo_white1]\n NETUID: [navajo_white1]" + f"{netuid}[/navajo_white1] - Network: [navajo_white1]{subtensor.network}[/navajo_white1]\n", show_footer=True, width=None, pad_edge=False, diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 0efe8dc1..ce504118 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -269,13 +269,13 @@ async def wallet_balance( ), Column( "[white]Coldkey Address", - style="bright_magenta", + style="plum2", no_wrap=True, ), Column( "[white]Free Balance", justify="right", - style="light_goldenrod2", + style="tan", no_wrap=True, ), Column( @@ -287,10 +287,10 @@ async def wallet_balance( Column( "[white]Total Balance", justify="right", - style="green", + style="dark_sea_green", no_wrap=True, ), - title=f"[underline dark_orange]Wallet Coldkey Balance[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}", + title=f"\n[underline navajo_white1]Wallet Coldkey Balance[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}", show_footer=True, show_edge=False, border_style="bright_black", From c5a803a615a5b875f14920d1e8b3ae2e8b766e7e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 14:40:06 -0700 Subject: [PATCH 104/157] Adds support for coldkey_ss58 in btcli st list --- bittensor_cli/cli.py | 31 ++++++++++++++++++++--- bittensor_cli/src/commands/stake/stake.py | 18 ++++++------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 159619a4..286108ea 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2417,17 +2417,40 @@ def stake_list( wallet_name: Optional[str] = Options.wallet_name, wallet_hotkey: Optional[str] = Options.wallet_hotkey, wallet_path: Optional[str] = Options.wallet_path, + coldkey_ss58=typer.Option( + None, + "--ss58", + "--coldkey_ss58", + "--coldkey.ss58_address", + "--coldkey.ss58", + help="Coldkey address of the wallet", + ), quiet: bool = Options.quiet, verbose: bool = Options.verbose, # TODO add: all-wallets, reuse_last, html_output ): """List all stake accounts for wallet.""" self.verbosity_handler(quiet, verbose) - wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] - ) + + wallet = None + if coldkey_ss58: + if not is_valid_ss58_address(coldkey_ss58): + print_error("You entered an invalid ss58 address") + raise typer.Exit() + else: + coldkey_or_ss58 = Prompt.ask( + "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue] [Press Enter to use config values]", + ) + if is_valid_ss58_address(coldkey_or_ss58): + coldkey_ss58 = coldkey_or_ss58 + else: + wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name + wallet = self.wallet_ask( + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] + ) + return self._run_command( - stake.stake_list(wallet, self.initialize_chain(network)) + stake.stake_list(wallet, coldkey_ss58, self.initialize_chain(network)) ) def stake_add( diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 8f442158..06ab4ef9 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1501,12 +1501,14 @@ async def unstake( console.print("[green]Unstaking operations completed.[/green]") -async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): +async def stake_list( + wallet: Wallet, coldkey_ss58: str, subtensor: "SubtensorInterface" +): + coldkey_address = coldkey_ss58 if coldkey_ss58 else wallet.coldkeypub.ss58_address + sub_stakes = ( - await subtensor.get_stake_info_for_coldkeys( - coldkey_ss58_list=[wallet.coldkeypub.ss58_address] - ) - )[wallet.coldkeypub.ss58_address] + await subtensor.get_stake_info_for_coldkeys(coldkey_ss58_list=[coldkey_address]) + )[coldkey_address] # Get registered delegates details. registered_delegate_info = await subtensor.get_delegate_identities() @@ -1516,9 +1518,7 @@ async def stake_list(wallet: Wallet, subtensor: "SubtensorInterface"): emission_drain_tempo = int( await subtensor.substrate.query("SubtensorModule", "HotkeyEmissionTempo") ) - balance = (await subtensor.get_balance(wallet.coldkeypub.ss58_address))[ - wallet.coldkeypub.ss58_address - ] + balance = (await subtensor.get_balance(coldkey_address))[coldkey_address] # Iterate over substakes and aggregate them by hotkey. hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} @@ -1680,7 +1680,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): console.print("\n\n") console.print( f"Wallet:\n" - f" Coldkey SS58: [bold plum2]{wallet.coldkeypub.ss58_address}[/bold plum2]\n" + f" Coldkey SS58: [bold plum2]{coldkey_address}[/bold plum2]\n" f" Free Balance: [dark_sea_green]{balance}[/dark_sea_green]\n" f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" From e259e095ec78a1d6b1aa86c7b998d71519064322 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 00:07:09 +0200 Subject: [PATCH 105/157] Update requirements from staging --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c3a5dfc2..2e891bcf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ wheel async-property==0.2.2 aiohttp~=3.10.2 +backoff~=2.2.1 GitPython>=3.0.0 fuzzywuzzy~=0.18.0 netaddr~=1.3.0 From c57b98f335aaaeb51e3a7a596ae1ac04899a3216 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 17:34:00 -0700 Subject: [PATCH 106/157] Fixes order of prompts in st add, remove and alligns prompt color scheme --- bittensor_cli/cli.py | 50 ++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 286108ea..5d27f932 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1166,13 +1166,9 @@ def wallet_ask( f"Using the wallet name from config:[bold cyan] {wallet_name}" ) else: - wallet_name = typer.prompt( - typer.style("Enter the wallet name", fg="blue") - + typer.style( - " (Hint: You can set this with `btcli config set --wallet-name`)", - fg="green", - italic=True, - ), + wallet_name = Prompt.ask( + "Enter the [blue]wallet name[/blue]" + + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-name`)[/dark_sea_green3 italic]", default=defaults.wallet.name, ) @@ -1183,13 +1179,9 @@ def wallet_ask( f"Using the wallet hotkey from config:[bold cyan] {wallet_hotkey}" ) else: - wallet_hotkey = typer.prompt( - typer.style("Enter the wallet hotkey", fg="blue") - + typer.style( - " (Hint: You can set this with `btcli config set --wallet-hotkey`)", - fg="green", - italic=True, - ), + wallet_hotkey = Prompt.ask( + "Enter the [blue]wallet hotkey[/blue]" + + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-hotkey`)[/dark_sea_green3 italic]", default=defaults.wallet.hotkey, ) if wallet_path: @@ -1203,13 +1195,9 @@ def wallet_ask( ) if WO.PATH in ask_for and not wallet_path: - wallet_path = typer.prompt( - typer.style("Enter the wallet path", fg="blue") - + typer.style( - " (Hint: You can set this with `btcli config set --wallet-path`)", - fg="green", - italic=True, - ), + wallet_path = Prompt.ask( + "Enter the [blue]wallet path[/blue]" + + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-path`)[/dark_sea_green3 italic]", default=defaults.wallet.path, ) # Create the Wallet object @@ -2439,7 +2427,8 @@ def stake_list( raise typer.Exit() else: coldkey_or_ss58 = Prompt.ask( - "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue] [Press Enter to use config values]", + "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]", + default=self.config.get("wallet_name") or defaults.wallet.name, ) if is_valid_ss58_address(coldkey_or_ss58): coldkey_ss58 = coldkey_or_ss58 @@ -2538,11 +2527,16 @@ def stake_add( raise typer.Exit() if not wallet_hotkey and not all_hotkeys and not include_hotkeys: + if not wallet_name: + wallet_name = Prompt.ask( + "Enter the [blue]wallet name[/blue]", + default=self.config.get("wallet_name") or defaults.wallet.name, + ) hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to [Press Enter to use config values]", + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", + default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, ) if is_valid_ss58_address(hotkey_or_ss58): - hotkey_ss58_address = hotkey_or_ss58 wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) @@ -2727,8 +2721,14 @@ def stake_remove( and not all_hotkeys and not include_hotkeys ): + if not wallet_name: + wallet_name = Prompt.ask( + "Enter the [blue]wallet name[/blue]", + default=self.config.get("wallet_name") or defaults.wallet.name, + ) hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from. [Press Enter to use config values]" + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", + default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, ) if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 From ed6b2f80d38959bfd2746cd95a50380542ff1fd7 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 22:49:43 -0700 Subject: [PATCH 107/157] optimize st remove --- bittensor_cli/src/commands/stake/stake.py | 46 ++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 06ab4ef9..5a87a495 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1212,13 +1212,32 @@ async def unstake( assert wallet.hotkey is not None hotkeys_to_unstake_from = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] - # Prepare lists to store unstaking data per subnet - unstake_operations = [] - total_received_amount = Balance.from_tao(0) - current_wallet_balance: Balance = ( - await subtensor.get_balance(wallet.coldkeypub.ss58_address) - )[wallet.coldkeypub.ss58_address] - max_float_slippage = 0 + with console.status( + f"Retrieving stake and subnet data from {subtensor.network}...", + spinner="earth", + ): + # Prepare lists to store unstaking data per subnet + unstake_operations = [] + total_received_amount = Balance.from_tao(0) + current_wallet_balance: Balance = ( + await subtensor.get_balance(wallet.coldkeypub.ss58_address) + )[wallet.coldkeypub.ss58_address] + max_float_slippage = 0 + + # Fetch dynamic info and stake balances + chain_head = await subtensor.substrate.get_chain_head() + dynamic_info_list, stake_balances_dict = await asyncio.gather( + asyncio.gather( + *[subtensor.get_subnet_dynamic_info(x, chain_head) for x in netuids] + ), + subtensor.multi_get_stake_for_coldkey_and_hotkey_on_netuid( + hotkey_ss58s=[hk[1] for hk in hotkeys_to_unstake_from], + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuids=netuids, + block_hash=chain_head, + ), + ) + dynamic_info_all_netuids = dict(zip(netuids, dynamic_info_list)) # Iterate over hotkeys and netuids to collect unstake operations for hotkey in hotkeys_to_unstake_from: @@ -1235,19 +1254,12 @@ async def unstake( if skip_remaining_subnets: break # Exit the loop over netuids - # Check that the subnet exists. - dynamic_info = await subtensor.get_subnet_dynamic_info(netuid) + dynamic_info = dynamic_info_all_netuids.get(netuid) if dynamic_info is None: console.print(f"[red]Subnet: {netuid} does not exist.[/red]") continue # Skip to the next subnet - current_stake_balance: Balance = ( - await subtensor.get_stake_for_coldkey_and_hotkey_on_netuid( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=staking_address_ss58, - netuid=netuid, - ) - ) + current_stake_balance = stake_balances_dict[staking_address_ss58][netuid] if current_stake_balance.tao == 0: continue # No stake to unstake @@ -1369,7 +1381,7 @@ async def unstake( # Build the table table = Table( - title=f"\n[navajo_white1]Unstaking to: \nWallet: [dark_sea_green3]{wallet.name}[/dark_sea_green3], Coldkey ss58: [dark_sea_green3]{wallet.coldkeypub.ss58_address}[/dark_sea_green3]\nNetwork: {subtensor.network}[/navajo_white1]\n", + title=f"\n[tan]Unstaking to: \nWallet: [dark_sea_green3]{wallet.name}[/dark_sea_green3], Coldkey ss58: [dark_sea_green3]{wallet.coldkeypub.ss58_address}[/dark_sea_green3]\nNetwork: {subtensor.network}[/tan]\n", show_footer=True, show_edge=False, header_style="bold white", From d0840f4be28329f6b7dabfcd3fcbd227866a32d9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 29 Oct 2024 22:52:43 -0700 Subject: [PATCH 108/157] improved var --- bittensor_cli/src/commands/stake/stake.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 5a87a495..2b8caf42 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1226,7 +1226,7 @@ async def unstake( # Fetch dynamic info and stake balances chain_head = await subtensor.substrate.get_chain_head() - dynamic_info_list, stake_balances_dict = await asyncio.gather( + dynamic_info_list, stake_all_netuids = await asyncio.gather( asyncio.gather( *[subtensor.get_subnet_dynamic_info(x, chain_head) for x in netuids] ), @@ -1259,7 +1259,7 @@ async def unstake( console.print(f"[red]Subnet: {netuid} does not exist.[/red]") continue # Skip to the next subnet - current_stake_balance = stake_balances_dict[staking_address_ss58][netuid] + current_stake_balance = stake_all_netuids[staking_address_ss58][netuid] if current_stake_balance.tao == 0: continue # No stake to unstake From 2dc7135cf2e665e77bfb172c65e515344cc52afd Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 15:31:35 +0200 Subject: [PATCH 109/157] Clean up error handling for main CLI. Add KeyboardInterrupt. --- bittensor_cli/cli.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 5d27f932..42afda91 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -818,23 +818,23 @@ def _run_command(self, cmd: Coroutine) -> None: """ async def _run(): + run_cmd = asyncio.create_task(cmd) try: if self.subtensor: async with self.subtensor: - result = await cmd + result = await run_cmd else: - result = await cmd + result = await run_cmd return result except (ConnectionRefusedError, ssl.SSLError): err_console.print(f"Unable to connect to the chain: {self.subtensor}") - asyncio.create_task(cmd).cancel() raise typer.Exit() - except ConnectionClosed: - asyncio.create_task(cmd).cancel() - raise typer.Exit() - except SubstrateRequestException as e: - err_console.print(str(e)) + except (ConnectionClosed, SubstrateRequestException, KeyboardInterrupt) as e: + if isinstance(e, SubstrateRequestException): + err_console.print(str(e)) raise typer.Exit() + finally: + run_cmd.cancel() if sys.version_info < (3, 10): # For Python 3.9 or lower From 5ae34100a3bd8b05f6018d5bef8054b4ff5bf28f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 09:23:02 -0700 Subject: [PATCH 110/157] Reverts changes to _run() --- bittensor_cli/cli.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 42afda91..5d27f932 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -818,23 +818,23 @@ def _run_command(self, cmd: Coroutine) -> None: """ async def _run(): - run_cmd = asyncio.create_task(cmd) try: if self.subtensor: async with self.subtensor: - result = await run_cmd + result = await cmd else: - result = await run_cmd + result = await cmd return result except (ConnectionRefusedError, ssl.SSLError): err_console.print(f"Unable to connect to the chain: {self.subtensor}") + asyncio.create_task(cmd).cancel() raise typer.Exit() - except (ConnectionClosed, SubstrateRequestException, KeyboardInterrupt) as e: - if isinstance(e, SubstrateRequestException): - err_console.print(str(e)) + except ConnectionClosed: + asyncio.create_task(cmd).cancel() + raise typer.Exit() + except SubstrateRequestException as e: + err_console.print(str(e)) raise typer.Exit() - finally: - run_cmd.cancel() if sys.version_info < (3, 10): # For Python 3.9 or lower From b51f2bdd1af5073ce5502635db38f130ae144cbe Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 10:13:02 -0700 Subject: [PATCH 111/157] Adds hidden for overview, inspect --- bittensor_cli/cli.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 5d27f932..d1022138 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -622,15 +622,19 @@ def __init__(self): self.wallet_app.command( "history", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] )(self.wallet_history) - # self.wallet_app.command( - # "overview", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] - # )(self.wallet_overview) + self.wallet_app.command( + "overview", + rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"], + hidden=True, + )(self.wallet_overview) self.wallet_app.command( "transfer", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] )(self.wallet_transfer) - # self.wallet_app.command( - # "inspect", rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"] - # )(self.wallet_inspect) + self.wallet_app.command( + "inspect", + rich_help_panel=HELP_PANELS["WALLET"]["INFORMATION"], + hidden=True, + )(self.wallet_inspect) self.wallet_app.command( "faucet", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] )(self.wallet_faucet) From 14ff0fd3d44b0e0d19ec3e5b6e939fdde104c41e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 21:23:11 +0200 Subject: [PATCH 112/157] Fix taostats link --- bittensor_cli/src/bittensor/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index e63807a8..e65f665d 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -443,7 +443,7 @@ def get_explorer_url_for_network( explorer_opentensor_url = "{root_url}/query/{block_hash}".format( root_url=explorer_root_urls.get("opentensor"), block_hash=block_hash ) - explorer_taostats_url = "{root_url}/extrinsic/{block_hash}".format( + explorer_taostats_url = "{root_url}/hash/{block_hash}".format( root_url=explorer_root_urls.get("taostats"), block_hash=block_hash ) explorer_urls["opentensor"] = explorer_opentensor_url From 95ecb6f1905439b1f8efb315aac7b67243b42121 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 14:42:42 -0700 Subject: [PATCH 113/157] updated btcli s list --- bittensor_cli/src/commands/subnets.py | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index e311b8fd..3884f490 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -179,12 +179,13 @@ async def subnets_list( ( str(netuid), f"[light_goldenrod1]{subnet.symbol}[light_goldenrod1]", - f"τ {emission_tao:.4f}", - f"τ {subnet.tao_in.tao:,.4f}", - f"{subnet.alpha_out.tao:,.4f} {symbol}", - f"{subnet.price.tao:.4f} τ/{symbol}", - f"{subnet.blocks_since_last_step}/{subnet.tempo}", - f"{global_weight:.4f}" if global_weight is not None else "N/A", + f"τ {emission_tao:.4f}", # Emission (t) + f"{subnet.alpha_out.tao:,.4f} {symbol}", # Stake a_out + f"τ {subnet.tao_in.tao:,.4f}", # TAO Pool t_in + f"{subnet.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in + f"{subnet.price.tao:.4f} τ/{symbol}", # Rate t_in/a_in + f"{subnet.blocks_since_last_step}/{subnet.tempo}", # Tempo k/n + f"{global_weight:.4f}" if global_weight is not None else "N/A", # Local weight coeff. (γ) ) ) total_emissions = sum( @@ -212,17 +213,22 @@ async def subnets_list( footer=f"τ {total_emissions:.4f}", ) table.add_column( - f"[bold white]TAO ({Balance.get_unit(0)})", + f"[bold white]STAKE ({Balance.get_unit(1)}_out)", + style="light_salmon3", + justify="right", + ) + table.add_column( + f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", style="rgb(42,161,152)", justify="right", ) table.add_column( - f"[bold white]STAKE ({Balance.get_unit(1)})", - style="light_salmon3", + f"[bold white]Alpha Pool ({Balance.get_unit(1)}_in)", + style="rgb(42,161,152)", justify="right", ) table.add_column( - f"[bold white]RATE ({Balance.get_unit(1)}/{Balance.get_unit(0)})", + f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", style="medium_purple", justify="right", ) @@ -232,7 +238,7 @@ async def subnets_list( justify="right", overflow="fold", ) - table.add_column("[bold white]Global weight (γ)", style="dark_sea_green3", justify="center") + table.add_column("[bold white]Local weight coeff. (γ)", style="dark_sea_green3", justify="center") # Sort rows by subnet.emission.tao, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) @@ -244,7 +250,7 @@ async def subnets_list( # Print the table console.print(table) - # TODO: Add description for global weights + console.print( """ [bold white]Description[/bold white]: From 2cd8184f5007c92ea39c3b3d597a8d3d00593ffd Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 16:21:41 -0700 Subject: [PATCH 114/157] Updates description of subnet list --- bittensor_cli/src/commands/subnets.py | 103 ++++++++++++++++++-------- 1 file changed, 74 insertions(+), 29 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 3884f490..a0a9ab9c 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -2,7 +2,13 @@ import json import sqlite3 from textwrap import dedent +from rich.panel import Panel +from rich.markdown import Markdown +from rich.text import Text +from rich.style import Style +import textwrap from typing import TYPE_CHECKING, Optional, cast +from rich import box from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError @@ -179,13 +185,15 @@ async def subnets_list( ( str(netuid), f"[light_goldenrod1]{subnet.symbol}[light_goldenrod1]", - f"τ {emission_tao:.4f}", # Emission (t) - f"{subnet.alpha_out.tao:,.4f} {symbol}", # Stake a_out - f"τ {subnet.tao_in.tao:,.4f}", # TAO Pool t_in - f"{subnet.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in - f"{subnet.price.tao:.4f} τ/{symbol}", # Rate t_in/a_in - f"{subnet.blocks_since_last_step}/{subnet.tempo}", # Tempo k/n - f"{global_weight:.4f}" if global_weight is not None else "N/A", # Local weight coeff. (γ) + f"τ {emission_tao:.4f}", # Emission (t) + f"{subnet.alpha_out.tao:,.4f} {symbol}", # Stake a_out + f"τ {subnet.tao_in.tao:,.4f}", # TAO Pool t_in + f"{subnet.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in + f"{subnet.price.tao:.4f} τ/{symbol}", # Rate t_in/a_in + f"{subnet.blocks_since_last_step}/{subnet.tempo}", # Tempo k/n + f"{global_weight:.4f}" + if global_weight is not None + else "N/A", # Local weight coeff. (γ) ) ) total_emissions = sum( @@ -238,7 +246,9 @@ async def subnets_list( justify="right", overflow="fold", ) - table.add_column("[bold white]Local weight coeff. (γ)", style="dark_sea_green3", justify="center") + table.add_column( + "[bold white]Local weight coeff. (γ)", style="dark_sea_green3", justify="center" + ) # Sort rows by subnet.emission.tao, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) @@ -250,22 +260,55 @@ async def subnets_list( # Print the table console.print(table) - - console.print( - """ + header = """ [bold white]Description[/bold white]: -The table displays relevant information about each subnet on the network. +The table displays relevant information about each subnet on the network. The columns are as follows: - - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). - - [bold white]Symbol[/bold white]: The symbol representing the subnet's stake. - - [bold white]Emission[/bold white]: The amount of TAO added to the subnet every block. Calculated by dividing the TAO (t) column values by the sum of the TAO (t) column. - - [bold white]TAO[/bold white]: The TAO staked into the subnet ( which dynamically changes during stake, unstake and emission events ). - - [bold white]Stake[/bold white]: The outstanding supply of stake across all staking accounts on this subnet. - - [bold white]Rate[/bold white]: The rate of conversion between TAO and the subnet's staking unit. - - [bold white]Tempo[/bold white]: The number of blocks between epochs. Represented as (k/n) where k is the blocks since the last epoch and n is the total blocks in the epoch. - - [bold white]Global weight[/bold white]: The global weight of the subnet across all subnets. """ - ) + console.print(header) + description_table = Table(show_header=False, box=None, show_edge=False, leading=2) + + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet (its index).\n"), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.\n", + ), + ( + "[bold tan]Emission (τ)[/bold tan]", + "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + ), + ( + "[bold tan]STAKE (α_out)[/bold tan]", + "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + ), + ( + "[bold tan]Tempo (k/n)[/bold tan]", + 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n', + ), + ( + "[bold tan]Local weight coeff (γ)[/bold tan]", + "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + ), + ] + + description_table.add_column("Field", no_wrap=True, style="bold tan") + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def show(subtensor: "SubtensorInterface", netuid: int, prompt: bool = True): @@ -461,9 +504,15 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="center", ) - table.add_column("Dividends", style="#8787d7", no_wrap=True, justify="center", footer=f"{relative_emissions_sum:.3f}",) + table.add_column( + "Dividends", + style="#8787d7", + no_wrap=True, + justify="center", + footer=f"{relative_emissions_sum:.3f}", + ) table.add_column("Incentive", style="#5fd7ff", no_wrap=True, justify="center") - + # Hiding relative emissions for now # table.add_column( # "Emissions", @@ -479,12 +528,8 @@ async def show_subnet(netuid_: int): justify="center", footer=str(Balance.from_tao(emission_sum).set_unit(subnet_info.netuid)), ) - table.add_column( - "Hotkey", style="plum2", no_wrap=True, justify="center" - ) - table.add_column( - "Coldkey", style="plum2", no_wrap=True, justify="center" - ) + table.add_column("Hotkey", style="plum2", no_wrap=True, justify="center") + table.add_column("Coldkey", style="plum2", no_wrap=True, justify="center") for row in rows: table.add_row(*row) From 8bf0d391e79c90676149e9ce0911d9afad270a59 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 16:22:52 -0700 Subject: [PATCH 115/157] Removes extra imports --- bittensor_cli/src/commands/subnets.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index a0a9ab9c..af0b35c0 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -1,14 +1,7 @@ import asyncio import json import sqlite3 -from textwrap import dedent -from rich.panel import Panel -from rich.markdown import Markdown -from rich.text import Text -from rich.style import Style -import textwrap from typing import TYPE_CHECKING, Optional, cast -from rich import box from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError From 4f483b17c2984d2441489d5f138905895bc54ab6 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 16:38:13 -0700 Subject: [PATCH 116/157] tweaks to s list description --- bittensor_cli/src/commands/subnets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index af0b35c0..a0b79f5b 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -254,15 +254,13 @@ async def subnets_list( console.print(table) header = """ -[bold white]Description[/bold white]: -The table displays relevant information about each subnet on the network. -The columns are as follows: +[bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: """ console.print(header) description_table = Table(show_header=False, box=None, show_edge=False, leading=2) fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet (its index).\n"), + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet.\n"), ( "[bold tan]Symbol[/bold tan]", "The symbol for the subnet's dynamic TAO token.\n", From 70d8405c0e136e0c53df98ea94c2e4385b45bea9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 17:52:04 -0700 Subject: [PATCH 117/157] Updates btcli st list table --- bittensor_cli/src/commands/stake/stake.py | 148 ++++++++++++++++------ 1 file changed, 107 insertions(+), 41 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 2b8caf42..064b6464 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -8,6 +8,7 @@ from bittensor_wallet.errors import KeyFileError from rich.prompt import Confirm, FloatPrompt, Prompt from rich.table import Table +from rich import box from substrateinterface.exceptions import SubstrateRequestException from bittensor_cli.src.bittensor.balances import Balance @@ -1543,8 +1544,9 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): else hotkey_ ) rows = [] - total_global_tao = Balance(0) + total_tao_ownership = Balance(0) total_tao_value = Balance(0) + total_swapped_tao_value = Balance(0) for substake_ in substakes: netuid = substake_.netuid pool = dynamic_info[netuid] @@ -1563,6 +1565,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage( substake_.stake ) + total_swapped_tao_value += swapped_tao_value if pool.is_dynamic: slippage_percentage_ = ( 100 * float(slippage) / float(slippage + swapped_tao_value) @@ -1583,7 +1586,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): tao_ownership = Balance.from_tao( (alpha_value.tao / issuance.tao) * tao_locked.tao ) - total_global_tao += tao_ownership + total_tao_ownership += tao_ownership else: # TODO what's this var for? alpha_ownership = "0.0000" @@ -1592,22 +1595,20 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): [ str(netuid), # Number symbol, # Symbol - # f"[medium_purple]{tao_ownership}[/medium_purple] ([light_salmon3]{ alpha_ownership }[/light_salmon3][white]%[/white])", # Tao ownership. - f"[medium_purple]{tao_ownership}[/medium_purple]", # Tao ownership. - # f"[dark_sea_green]{ alpha_value }", # Alpha value - f"{substake_.stake.tao:,.4f} {symbol}", - f"{pool.price.tao:.4f} τ/{symbol}", - f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Tao equiv - f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap amount. - # f"[light_salmon3]{ alpha_ownership }%[/light_salmon3]", # Ownership. + f"{substake_.stake.tao:,.4f} {symbol}", # Stake (a) + f"{pool.tao_in.tao:,.4f} τ", # TAO pool (t_in) + f"{pool.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in + f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) + f"{pool.alpha_out.tao:,.4f} {symbol}", # Alpha out (a_out) + f"[medium_purple]{tao_ownership}[/medium_purple]", # TAO equiv + f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Exchange Value (α x τ/α) + f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap(α) -> τ "[bold cadet_blue]YES[/bold cadet_blue]" if substake_.is_registered - else "[dark_red]NO[/dark_red]", - # Registered. + else "[dark_red]NO[/dark_red]", # Registered str(Balance.from_tao(per_block_emission).set_unit(netuid)) if substake_.is_registered - else "[dark_red]N/A[/dark_red]", # emission per block. - # f"[light_slate_blue]{locked_value}[/light_slate_blue]", # Locked value + else "[dark_red]N/A[/dark_red]", # Emission(α/block) ] ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") @@ -1631,47 +1632,62 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): no_wrap=True, ) table.add_column( - f"[white]TAO({Balance.unit})", + f"[white]Stake ({Balance.get_unit(1)})", + footer_style="overline white", + style="rgb(42,161,152)", + justify="center", + ) + table.add_column( + f"[white]TAO pool ({Balance.unit}_in)", style="medium_purple", justify="right", - footer=f"{total_global_tao}", ) table.add_column( - f"[white]Stake({Balance.get_unit(1)})", - footer_style="overline white", - style="rgb(42,161,152)", - justify="center", + f"[white]Alpha pool ({Balance.get_unit(1)}_in)", + style="medium_purple", + justify="right", ) table.add_column( - f"[white]Rate({Balance.unit}/{Balance.get_unit(1)})", + f"[white]Rate \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", footer_style="white", style="light_goldenrod2", justify="center", ) table.add_column( - f"[white]Value({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", + f"[white]Alpha out ({Balance.get_unit(1)}_out)", + style="medium_purple", + justify="right", + ) + table.add_column( + f"[white]TAO equiv \n({Balance.unit}_in x {Balance.get_unit(1)}/{Balance.get_unit(1)}_out)", + style="medium_purple", + justify="right", + footer=f"{total_tao_ownership}", + ) + table.add_column( + f"[white]Exchange Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", footer_style="overline white", style="blue", justify="right", footer=f"{total_tao_value}", ) table.add_column( - f"[white]Swap({Balance.get_unit(1)}) -> {Balance.unit}", + f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", footer_style="overline white", style="white", justify="right", + footer=f"{total_swapped_tao_value}", ) - # table.add_column(f"[white]Control({bittensor.Balance.get_unit(1)})", style="aquamarine3", justify="right") table.add_column("[white]Registered", style="red", justify="right") table.add_column( - f"[white]Emission({Balance.get_unit(1)}/block)", + f"[white]Emission \n({Balance.get_unit(1)}/block)", style="light_goldenrod2", justify="right", ) for row in rows: table.add_row(*row) console.print(table) - return total_global_tao, total_tao_value + return total_tao_ownership, total_tao_value for substake in sub_stakes: hotkey = substake.hotkey_ss58 @@ -1697,23 +1713,73 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" ) - console.print( - """ -[bold white]Description[/bold white]: -Each table displays information about your coldkey's staking accounts with a hotkey. -The header of the table displays the hotkey and the footer displays the total stake and total value of all your staking accounts. -The columns of the table are as follows: - - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). - - [bold white]Symbol[/bold white]: The symbol representing the subnet stake's unit. - - [bold white]TAO[/bold white]: The hotkey's TAO balance on this subnet. This is this hotkey's proportion of total TAO staked into the subnet divided by the hotkey's share of outstanding stake. - - [bold white]Stake[/bold white]: The hotkey's stake balance in subnets staking unit. - - [bold white]Rate[/bold white]: The rate of exchange between the subnet's staking unit and the subnet's TAO. - - [bold white]Value[/bold white]: The price of the hotkey's stake in TAO computed via the exchange rate. - - [bold white]Swap[/bold white]: The amount of TAO received when unstaking all of the hotkey's stake (with slippage). - - [bold white]Registered[/bold white]: Whether the hotkey is registered on this subnet. - - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. + header = """ +[bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: """ + console.print(header) + description_table = Table(show_header=False, box=None, show_edge=False) + + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Emission (τ)[/bold tan]", + "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]Stake (α)[/bold tan]", + "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].', + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]Alpha out (α_out)[/bold tan]", + "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].', + ), + ( + "[bold tan]Exchange Value (α x τ/α)[/bold tan]", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]Swap (α → τ)[/bold tan]", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]Registered[/bold tan]", + "Indicates if the hotkey is registered in this subnet or not. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ( + "[bold tan]Emission (α/block)[/bold tan]", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + ), + ] + + description_table.add_column( + "Field", + no_wrap=True, + style="bold tan", ) + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def move_stake( From feb88400b1a40a727d417ece147874087511bfba Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 17:57:23 -0700 Subject: [PATCH 118/157] tweaks description of btcli st list --- bittensor_cli/src/commands/stake/stake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 064b6464..f3a9f908 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1717,7 +1717,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: """ console.print(header) - description_table = Table(show_header=False, box=None, show_edge=False) + description_table = Table(show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True) fields = [ ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), From 54cc6aed56806b535eff1fca28be67c3739e2e5d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 18:03:31 -0700 Subject: [PATCH 119/157] Fixes dupe emissions desc --- bittensor_cli/src/commands/stake/stake.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index f3a9f908..a0760c05 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1717,7 +1717,9 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: """ console.print(header) - description_table = Table(show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) fields = [ ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), @@ -1725,10 +1727,6 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): "[bold tan]Symbol[/bold tan]", "The symbol for the subnet's dynamic TAO token.", ), - ( - "[bold tan]Emission (τ)[/bold tan]", - "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", - ), ( "[bold tan]Stake (α)[/bold tan]", "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", From 6e521c5e016a991a8a4e5878ff7f49c3d22cab2e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 18:30:09 -0700 Subject: [PATCH 120/157] Exposes few urls in st list --- bittensor_cli/src/commands/stake/stake.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index a0760c05..bb8769a1 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1729,19 +1729,19 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ), ( "[bold tan]Stake (α)[/bold tan]", - "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].', + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]Alpha Pool (α_in)[/bold tan]", - 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].', + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Alpha out (α_out)[/bold tan]", From 97ce7662a4066384fda5fe5d9e8f93e8b04f480e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 18:31:15 -0700 Subject: [PATCH 121/157] line breaks in desc st list --- bittensor_cli/src/commands/stake/stake.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index bb8769a1..df92dbde 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1737,11 +1737,11 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ), ( "[bold tan]Alpha Pool (α_in)[/bold tan]", - 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. For more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Alpha out (α_out)[/bold tan]", From a9426d61c83756452b6e249417eec956bf28a16f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 19:01:43 -0700 Subject: [PATCH 122/157] updates hyperlinks --- bittensor_cli/src/commands/stake/stake.py | 18 ++++++++-------- bittensor_cli/src/commands/subnets.py | 25 +++++++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index df92dbde..0c66b089 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1605,15 +1605,15 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap(α) -> τ "[bold cadet_blue]YES[/bold cadet_blue]" if substake_.is_registered - else "[dark_red]NO[/dark_red]", # Registered + else "[hot_pink3]NO[/hot_pink3]", # Registered str(Balance.from_tao(per_block_emission).set_unit(netuid)) if substake_.is_registered - else "[dark_red]N/A[/dark_red]", # Emission(α/block) + else "[hot_pink3]N/A[/hot_pink3]", # Emission(α/block) ] ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") table = Table( - title=f"\n[dark_orange]Hotkey: {name}[/dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", + title=f"\n[dark_orange]Hotkey: {name}[/dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n\nSee below for an explanation of the columns\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1745,27 +1745,27 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ), ( "[bold tan]Alpha out (α_out)[/bold tan]", - "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", - 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].', + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]Exchange Value (α x τ/α)[/bold tan]", - "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Swap (α → τ)[/bold tan]", - "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Registered[/bold tan]", - "Indicates if the hotkey is registered in this subnet or not. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Emission (α/block)[/bold tan]", - "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ] diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index a0b79f5b..4a42642b 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -7,6 +7,7 @@ from bittensor_wallet.errors import KeyFileError from rich.prompt import Confirm from rich.table import Column, Table +from rich import box from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import SubnetState @@ -194,7 +195,7 @@ async def subnets_list( ) table = Table( - title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n", + title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n\nSee below for an explanation of the columns\n", show_footer=True, show_edge=False, header_style="bold white", @@ -257,41 +258,43 @@ async def subnets_list( [bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: """ console.print(header) - description_table = Table(show_header=False, box=None, show_edge=False, leading=2) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet.\n"), + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), ( "[bold tan]Symbol[/bold tan]", - "The symbol for the subnet's dynamic TAO token.\n", + "The symbol for the subnet's dynamic TAO token.", ), ( "[bold tan]Emission (τ)[/bold tan]", - "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]STAKE (α_out)[/bold tan]", - "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n', + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]Alpha Pool (α_in)[/bold tan]", - "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Tempo (k/n)[/bold tan]", - 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n', + 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]Local weight coeff (γ)[/bold tan]", - "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. For more, see [link=https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo][blue]URL[/blue][/link].\n", + "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ] From 6145d81a708d6c049733cc9d97f20c652e4e9b50 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 19:21:41 -0700 Subject: [PATCH 123/157] Adds prompts in stake list --- bittensor_cli/src/commands/stake/stake.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 0c66b089..47e7f40c 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1698,13 +1698,20 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): hotkeys_to_substakes[hotkey].append(substake) # Iterate over each hotkey and make a table + counter = 0 + num_hotkeys = len(hotkeys_to_substakes) all_hotkeys_total_global_tao = Balance(0) all_hotkeys_total_tao_value = Balance(0) for hotkey in hotkeys_to_substakes.keys(): + counter += 1 stake, value = table_substakes(hotkey, hotkeys_to_substakes[hotkey]) all_hotkeys_total_global_tao += stake all_hotkeys_total_tao_value += value + if num_hotkeys > 1 and counter < num_hotkeys: + console.print("\nPress any key to continue to the next hotkey...") + input() + console.print("\n\n") console.print( f"Wallet:\n" @@ -1713,6 +1720,9 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" ) + + console.print("\nPress any key to continue to column descriptions...") + input() header = """ [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: """ From b9727d1a14e8a2e90945bbdc407ccb9a883c9f13 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 30 Oct 2024 19:27:15 -0700 Subject: [PATCH 124/157] Updates input text and adds prompt in s list --- bittensor_cli/src/commands/stake/stake.py | 4 ++-- bittensor_cli/src/commands/subnets.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 47e7f40c..33198509 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1709,7 +1709,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): all_hotkeys_total_tao_value += value if num_hotkeys > 1 and counter < num_hotkeys: - console.print("\nPress any key to continue to the next hotkey...") + console.print("\nPress Enter to continue to the next hotkey...") input() console.print("\n\n") @@ -1721,7 +1721,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" ) - console.print("\nPress any key to continue to column descriptions...") + console.print("\nPress Enter to continue to column descriptions...") input() header = """ [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 4a42642b..286ff7f5 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -254,6 +254,8 @@ async def subnets_list( # Print the table console.print(table) + console.print("\nPress Enter to continue to column descriptions...") + input() header = """ [bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: """ From 914cec6c5dc418850171a900f3fd5ad750e62ba8 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 1 Nov 2024 18:04:28 -0700 Subject: [PATCH 125/157] Added live view to subnets list --- bittensor_cli/cli.py | 7 + bittensor_cli/src/commands/subnets.py | 478 +++++++++++++++++++------- 2 files changed, 357 insertions(+), 128 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d1022138..ce74d1c7 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -218,6 +218,11 @@ class Options: "--quiet", help="Display only critical information on the console.", ) + live = typer.Option( + False, + "--live", + help="Display live view of the table", + ) def list_prompt(init_var: list, list_type: type, help_text: str) -> list: @@ -3391,6 +3396,7 @@ def subnets_list( # html_output: bool = Options.html_output, quiet: bool = Options.quiet, verbose: bool = Options.verbose, + live_mode: bool = Options.live, ): """ List all subnets and their detailed information. @@ -3427,6 +3433,7 @@ def subnets_list( False, # reuse-last False, # html-output not self.config.get("use_cache", True), + live_mode, ) ) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 286ff7f5..7e5f7364 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -6,6 +6,10 @@ from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError from rich.prompt import Confirm +from rich.console import Console, Group +from rich.spinner import Spinner +from rich.text import Text +from rich.progress import Progress, BarColumn, TextColumn from rich.table import Column, Table from rich import box @@ -15,6 +19,7 @@ register_extrinsic, burned_register_extrinsic, ) +from rich.live import Live from bittensor_cli.src.bittensor.minigraph import MiniGraph from bittensor_cli.src.commands.wallets import set_id, set_id_prompts from bittensor_cli.src.bittensor.utils import ( @@ -154,151 +159,368 @@ async def _find_event_attributes_in_extrinsic_receipt( async def subnets_list( - subtensor: "SubtensorInterface", reuse_last: bool, html_output: bool, no_cache: bool + subtensor: "SubtensorInterface", + reuse_last: bool, + html_output: bool, + no_cache: bool, + live: bool, ): """List all subnet netuids in the network.""" - # TODO add reuse-last and html-output and no-cache - rows = [] - subnets = await subtensor.get_all_subnet_dynamic_info() - global_weights = await subtensor.get_global_weights( - [subnet.netuid for subnet in subnets] - ) + async def fetch_subnet_data(): + subnets = await subtensor.get_all_subnet_dynamic_info() + global_weights = await subtensor.get_global_weights( + [subnet.netuid for subnet in subnets] + ) + return subnets, global_weights - for subnet in subnets: - netuid = subnet.netuid - global_weight = global_weights.get(netuid) - symbol = f"{subnet.symbol}\u200e" + def define_table(total_emissions: float): + table = Table( + title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]" + f"\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n\n", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) - if netuid == 0: - emission_tao = 0.0 - else: - emission_tao = subnet.emission.tao + table.add_column("[bold white]NETUID", style="white", justify="center") + table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") + table.add_column( + f"[bold white]EMISSION ({Balance.get_unit(0)})", + style="tan", + justify="left", + footer=f"τ {total_emissions:.4f}", + ) + table.add_column( + f"[bold white]STAKE ({Balance.get_unit(1)}_out)", + style="light_salmon3", + justify="left", + ) + table.add_column( + f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", + style="rgb(42,161,152)", + justify="left", + ) + table.add_column( + f"[bold white]Alpha Pool ({Balance.get_unit(1)}_in)", + style="rgb(42,161,152)", + justify="left", + ) + table.add_column( + f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", + style="medium_purple", + justify="left", + ) + table.add_column( + "[bold white]Tempo (k/n)", + style="plum2", + justify="left", + overflow="fold", + ) + table.add_column( + "[bold white]Local weight coeff. (γ)", style="steel_blue", justify="left" + ) + return table - rows.append( - ( - str(netuid), - f"[light_goldenrod1]{subnet.symbol}[light_goldenrod1]", - f"τ {emission_tao:.4f}", # Emission (t) - f"{subnet.alpha_out.tao:,.4f} {symbol}", # Stake a_out - f"τ {subnet.tao_in.tao:,.4f}", # TAO Pool t_in - f"{subnet.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in - f"{subnet.price.tao:.4f} τ/{symbol}", # Rate t_in/a_in - f"{subnet.blocks_since_last_step}/{subnet.tempo}", # Tempo k/n - f"{global_weight:.4f}" - if global_weight is not None - else "N/A", # Local weight coeff. (γ) + # Non-live mode + def create_table(subnets, global_weights): + rows = [] + for subnet in subnets: + netuid = subnet.netuid + global_weight = global_weights.get(netuid) + symbol = f"{subnet.symbol}\u200e" + + if netuid == 0: + emission_tao = 0.0 + else: + emission_tao = subnet.emission.tao + + # Prepare content + netuid_cell = str(netuid) + symbol_cell = f"{subnet.symbol}" + emission_cell = f"{emission_tao:,.4f}" + alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" + tao_in_cell = f"{subnet.tao_in.tao:,.4f} τ" + alpha_in_cell = f"{subnet.alpha_in.tao:,.4f} {symbol}" + price_cell = f"{subnet.price.tao:.4f} τ/{symbol}" + tempo_cell = f"{subnet.blocks_since_last_step}/{subnet.tempo}" + global_weight_cell = ( + f"{global_weight:.4f}" if global_weight is not None else "N/A" ) + + rows.append( + ( + netuid_cell, # Netuid + symbol_cell, # Symbol + emission_cell, # Emission (τ) + alpha_out_cell, # Stake α_out + tao_in_cell, # TAO Pool τ_in + alpha_in_cell, # Alpha Pool α_in + price_cell, # Rate τ_in/α_in + tempo_cell, # Tempo k/n + global_weight_cell, # Local weight coeff. (γ) + ) + ) + + total_emissions = sum( + float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 ) - total_emissions = sum( - float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 - ) - table = Table( - title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n\nSee below for an explanation of the columns\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, - ) + table = define_table(total_emissions) - table.add_column("[bold white]NETUID", style="white", justify="center") - table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") - table.add_column( - f"[bold white]EMISSION ({Balance.get_unit(0)})", - style="tan", - justify="right", - footer=f"τ {total_emissions:.4f}", - ) - table.add_column( - f"[bold white]STAKE ({Balance.get_unit(1)}_out)", - style="light_salmon3", - justify="right", - ) - table.add_column( - f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", - style="rgb(42,161,152)", - justify="right", - ) - table.add_column( - f"[bold white]Alpha Pool ({Balance.get_unit(1)}_in)", - style="rgb(42,161,152)", - justify="right", - ) - table.add_column( - f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", - style="medium_purple", - justify="right", - ) - table.add_column( - "[bold white]Tempo (k/n)", - style="plum2", - justify="right", - overflow="fold", - ) - table.add_column( - "[bold white]Local weight coeff. (γ)", style="dark_sea_green3", justify="center" - ) + # Sort rows by emission, keeping the root subnet in the first position + sorted_rows = [rows[0]] + sorted( + rows[1:], key=lambda x: float(str(x[2]).replace(",", "")), reverse=True + ) + + # Add rows to the table + for row in sorted_rows: + table.add_row(*row) + return table + + # Live mode + def create_table_live(subnets, global_weights, previous_data): + def format_cell(value, previous_value, unit="", precision=4): + if previous_value is not None: + change = value - previous_value + if change > 0: + change_text = ( + f" [pale_green3](+{change:.{precision}f}{unit})[/pale_green3]" + ) + elif change < 0: + change_text = ( + f" [hot_pink3]({change:.{precision}f}{unit})[/hot_pink3]" + ) + else: + change_text = "" + else: + change_text = "" + return f"{value:,.{precision}f}{unit}{change_text}" + + rows = [] + current_data = {} # To store current values for comparison in the next update + + for subnet in subnets: + netuid = subnet.netuid + global_weight = global_weights.get(netuid) + symbol = f"{subnet.symbol}\u200e" + + if netuid == 0: + emission_tao = 0.0 + else: + emission_tao = subnet.emission.tao + + # Store current values for comparison + current_data[netuid] = { + "emission_tao": emission_tao, + "alpha_out": subnet.alpha_out.tao, + "tao_in": subnet.tao_in.tao, + "alpha_in": subnet.alpha_in.tao, + "price": subnet.price.tao, + "blocks_since_last_step": subnet.blocks_since_last_step, + "global_weight": global_weight, + } + + # Retrieve previous data if available + prev = previous_data.get(netuid) if previous_data else {} + + # Prepare content + netuid_cell = str(netuid) + symbol_cell = f"{subnet.symbol}" + emission_cell = format_cell( + emission_tao, prev.get("emission_tao"), unit="", precision=4 + ) + alpha_out_cell = format_cell( + subnet.alpha_out.tao, + prev.get("alpha_out"), + unit=f" {symbol}", + precision=5, + ) + tao_in_cell = format_cell( + subnet.tao_in.tao, prev.get("tao_in"), unit=" τ", precision=4 + ) + alpha_in_cell = format_cell( + subnet.alpha_in.tao, + prev.get("alpha_in"), + unit=f" {symbol}", + precision=4, + ) + price_cell = format_cell( + subnet.price.tao, prev.get("price"), unit=f" τ/{symbol}", precision=4 + ) - # Sort rows by subnet.emission.tao, keeping the first subnet in the first position - sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) + # Content: Blocks_since_last_step + prev_blocks_since_last_step = prev.get("blocks_since_last_step") + if prev_blocks_since_last_step is not None: + if subnet.blocks_since_last_step >= prev_blocks_since_last_step: + block_change = ( + subnet.blocks_since_last_step - prev_blocks_since_last_step + ) + else: + # Tempo restarted + block_change = ( + subnet.blocks_since_last_step + subnet.tempo + 1 + ) - prev_blocks_since_last_step + if block_change > 0: + block_change_text = f" [pale_green3](+{block_change})[/pale_green3]" + elif block_change < 0: + block_change_text = f" [hot_pink3]({block_change})[/hot_pink3]" + else: + block_change_text = "" + else: + block_change_text = "" + tempo_cell = ( + f"{subnet.blocks_since_last_step}/{subnet.tempo}{block_change_text}" + ) - # Add rows to the table - for row in sorted_rows: - table.add_row(*row) + # Content: Global_weight + prev_global_weight = prev.get("global_weight") + if prev_global_weight is not None and global_weight is not None: + weight_change = float(global_weight) - float(prev_global_weight) + if weight_change > 0: + weight_change_text = ( + f" [pale_green3](+{weight_change:.6f})[/pale_green3]" + ) + elif weight_change < 0: + weight_change_text = ( + f" [hot_pink3]({weight_change:.6f})[/hot_pink3]" + ) + else: + weight_change_text = "" + else: + weight_change_text = "" + + global_weight_cell = ( + f"{global_weight:.4f}{weight_change_text}" + if global_weight is not None + else "N/A" + ) - # Print the table - console.print(table) + rows.append( + ( + netuid_cell, # Netuid + symbol_cell, # Symbol + emission_cell, # Emission (τ) + alpha_out_cell, # Stake α_out + tao_in_cell, # TAO Pool τ_in + alpha_in_cell, # Alpha Pool α_in + price_cell, # Rate τ_in/α_in + tempo_cell, # Tempo k/n + global_weight_cell, # Local weight coeff. (γ) + ) + ) + + total_emissions = sum( + float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 + ) + table = define_table(total_emissions) + + # Sort rows by emission, keeping the first subnet in the first position + sorted_rows = [rows[0]] + sorted( + rows[1:], + key=lambda x: float(str(x[2]).split()[0].replace(",", "")), + reverse=True, + ) + + # Add rows to the table + for row in sorted_rows: + table.add_row(*row) + return table, current_data + + # Live mode + if live: + refresh_interval = 15 # seconds + + progress = Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(bar_width=20, style="green", complete_style="green"), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + console=console, + auto_refresh=True, + ) + progress_task = progress.add_task("Updating:", total=refresh_interval) + + previous_data = None + with Live(console=console, screen=True, auto_refresh=True) as live: + try: + while True: + subnets, global_weights = await fetch_subnet_data() + table, current_data = create_table_live( + subnets, global_weights, previous_data + ) + previous_data = current_data + progress.reset(progress_task) + start_time = asyncio.get_event_loop().time() + + # Create the message + message = "\nLive view active. Press [bold red]Ctrl + C[/bold red] to exit" + + # Include the message in the live render group + live_render = Group(table, progress, message) + live.update(live_render) + while not progress.finished: + await asyncio.sleep(0.1) + elapsed = asyncio.get_event_loop().time() - start_time + progress.update(progress_task, completed=elapsed) + + except KeyboardInterrupt: + pass # Ctrl + C + else: + # Non-live mode + subnets, global_weights = await fetch_subnet_data() + table = create_table(subnets, global_weights) + console.print(table) - console.print("\nPress Enter to continue to column descriptions...") - input() - header = """ + console.print("\nPress Enter to continue to column descriptions...") + input() + header = """ [bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: """ - console.print(header) - description_table = Table( - show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True - ) + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) - fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), - ( - "[bold tan]Symbol[/bold tan]", - "The symbol for the subnet's dynamic TAO token.", - ), - ( - "[bold tan]Emission (τ)[/bold tan]", - "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]STAKE (α_out)[/bold tan]", - "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Alpha Pool (α_in)[/bold tan]", - "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Tempo (k/n)[/bold tan]", - 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Local weight coeff (γ)[/bold tan]", - "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ] + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Emission (τ)[/bold tan]", + "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]STAKE (α_out)[/bold tan]", + "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Tempo (k/n)[/bold tan]", + 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Local weight coeff (γ)[/bold tan]", + "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ] description_table.add_column("Field", no_wrap=True, style="bold tan") description_table.add_column("Description", overflow="fold") From ebb1ad9109eb5003fa7a03c4ed6a0f21108fe644 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 1 Nov 2024 18:11:52 -0700 Subject: [PATCH 126/157] Adds sum to Rate in subnets list --- bittensor_cli/src/commands/subnets.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 7e5f7364..48b07e0c 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -174,7 +174,7 @@ async def fetch_subnet_data(): ) return subnets, global_weights - def define_table(total_emissions: float): + def define_table(total_emissions: float, total_rate: float): table = Table( title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]" f"\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n\n", @@ -215,6 +215,7 @@ def define_table(total_emissions: float): f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", style="medium_purple", justify="left", + footer=f"τ {total_rate:.4f}", ) table.add_column( "[bold white]Tempo (k/n)", @@ -270,8 +271,10 @@ def create_table(subnets, global_weights): total_emissions = sum( float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 ) - - table = define_table(total_emissions) + total_rate = sum( + float(subnet.price.tao) for subnet in subnets if subnet.netuid != 0 + ) + table = define_table(total_emissions, total_rate) # Sort rows by emission, keeping the root subnet in the first position sorted_rows = [rows[0]] + sorted( @@ -418,7 +421,10 @@ def format_cell(value, previous_value, unit="", precision=4): total_emissions = sum( float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 ) - table = define_table(total_emissions) + total_rate = sum( + float(subnet.price.tao) for subnet in subnets if subnet.netuid != 0 + ) + table = define_table(total_emissions, total_rate) # Sort rows by emission, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted( From 79b2598e3ff655953923d3291b8ed4abd172da85 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 4 Nov 2024 09:57:22 -0800 Subject: [PATCH 127/157] Adds verification for json file used in regenerating --- bittensor_cli/cli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ce74d1c7..4f1ba0da 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -371,6 +371,12 @@ def get_creation_data( json = prompt_answer elif mnemonic: mnemonic = parse_mnemonic(mnemonic) + + if json: + if not os.path.exists(json): + print_error(f"The JSON file '{json}' does not exist.") + raise typer.Exit() + if json and not json_password: json_password = Prompt.ask( "Enter the backup password for JSON file.", password=True From e5546e9f46a18022f0dbe0f79f632e03d899a510 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 4 Nov 2024 10:11:00 -0800 Subject: [PATCH 128/157] Dont show desc in-case of no stakes in btcli st list --- bittensor_cli/src/commands/stake/stake.py | 134 +++++++++++----------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 33198509..b819746f 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1720,74 +1720,76 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" ) + if not sub_stakes: + console.print(f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})") + else: + console.print("\nPress Enter to continue to column descriptions...") + input() + header = """ + [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: + """ + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) - console.print("\nPress Enter to continue to column descriptions...") - input() - header = """ -[bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: -""" - console.print(header) - description_table = Table( - show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True - ) - - fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), - ( - "[bold tan]Symbol[/bold tan]", - "The symbol for the subnet's dynamic TAO token.", - ), - ( - "[bold tan]Stake (α)[/bold tan]", - "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Alpha Pool (α_in)[/bold tan]", - 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Alpha out (α_out)[/bold tan]", - "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", - 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Exchange Value (α x τ/α)[/bold tan]", - "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Swap (α → τ)[/bold tan]", - "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Registered[/bold tan]", - "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Emission (α/block)[/bold tan]", - "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ] + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Stake (α)[/bold tan]", + "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Alpha out (α_out)[/bold tan]", + "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Exchange Value (α x τ/α)[/bold tan]", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Swap (α → τ)[/bold tan]", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Registered[/bold tan]", + "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Emission (α/block)[/bold tan]", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ] - description_table.add_column( - "Field", - no_wrap=True, - style="bold tan", - ) - description_table.add_column("Description", overflow="fold") - for field_name, description in fields: - description_table.add_row(field_name, description) - console.print(description_table) + description_table.add_column( + "Field", + no_wrap=True, + style="bold tan", + ) + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def move_stake( From 7bb0f6081b4723eca32840748a00654219da5941 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 4 Nov 2024 10:23:52 -0800 Subject: [PATCH 129/157] Adds validation for netuid options --- bittensor_cli/cli.py | 2 ++ bittensor_cli/src/bittensor/utils.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 4f1ba0da..332d3603 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -43,6 +43,7 @@ print_error, validate_chain_endpoint, retry_prompt, + validate_netuid, ) from typing_extensions import Annotated from textwrap import dedent @@ -163,6 +164,7 @@ class Options: None, help="The netuid of the subnet in the root network, (e.g. 1).", prompt=True, + callback=validate_netuid, ) netuid_not_req = typer.Option( None, diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index e65f665d..07a123d5 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -974,3 +974,9 @@ def retry_prompt( return var else: err_console.print(rejection_text) + + +def validate_netuid(value: int) -> int: + if value is not None and value < 0: + raise typer.BadParameter("Negative netuid passed. Please use correct netuid.") + return value From 9bcebb35ad28e35b03bb1d148804d4b2986fb27d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 4 Nov 2024 10:43:27 -0800 Subject: [PATCH 130/157] Adds validation for amount in st add & remove --- bittensor_cli/cli.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 332d3603..d2393f54 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2620,6 +2620,10 @@ def stake_add( amount = FloatPrompt.ask( "[dark_sea_green]Amount to stake to each netuid (TAO τ)[/dark_sea_green]" ) + + if amount <= 0: + print_error(f"You entered an incorrect stake amount: {amount}") + raise typer.Exit() if Balance.from_tao(amount) > free_balance: print_error( f"You dont have enough balance to stake. Current free Balance: {free_balance}." @@ -2796,6 +2800,10 @@ def stake_remove( else: excluded_hotkeys = [] + if amount and amount <= 0: + print_error(f"You entered an incorrect unstake amount: {amount}") + raise typer.Exit() + return self._run_command( stake.unstake( wallet, From c868c1467588ae5f08432981155f5a3fca2794bd Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 4 Nov 2024 15:53:52 -0800 Subject: [PATCH 131/157] Renamed fields in stake_list --- bittensor_cli/src/commands/stake/stake.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index b819746f..2f0894d1 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1596,8 +1596,8 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): str(netuid), # Number symbol, # Symbol f"{substake_.stake.tao:,.4f} {symbol}", # Stake (a) - f"{pool.tao_in.tao:,.4f} τ", # TAO pool (t_in) - f"{pool.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in + f"{pool.tao_in.tao:,.4f} τ", # TAO Reserves (t_in) + f"{pool.alpha_in.tao:,.4f} {symbol}", # Alpha Reserves a_in f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) f"{pool.alpha_out.tao:,.4f} {symbol}", # Alpha out (a_out) f"[medium_purple]{tao_ownership}[/medium_purple]", # TAO equiv @@ -1638,12 +1638,12 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): justify="center", ) table.add_column( - f"[white]TAO pool ({Balance.unit}_in)", + f"[white]TAO Reserves ({Balance.unit}_in)", style="medium_purple", justify="right", ) table.add_column( - f"[white]Alpha pool ({Balance.get_unit(1)}_in)", + f"[white]Alpha Reserves ({Balance.get_unit(1)}_in)", style="medium_purple", justify="right", ) @@ -1698,7 +1698,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): hotkeys_to_substakes[hotkey].append(substake) # Iterate over each hotkey and make a table - counter = 0 + counter = 0 num_hotkeys = len(hotkeys_to_substakes) all_hotkeys_total_global_tao = Balance(0) all_hotkeys_total_tao_value = Balance(0) @@ -1744,16 +1744,16 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( - "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + "[bold tan]TAO Reserves (τ_in)[/bold tan]", + 'Units of TAO in the TAO Reserves reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Reserves (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( - "[bold tan]Alpha Pool (α_in)[/bold tan]", - 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + "[bold tan]Alpha Reserves (α_in)[/bold tan]", + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Reserves(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Reserves(τ_in) / Alpha Reserves (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", ), ( "[bold tan]Alpha out (α_out)[/bold tan]", @@ -1761,7 +1761,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ), ( "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", - 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', ), ( "[bold tan]Exchange Value (α x τ/α)[/bold tan]", From 27bea7a2d9728f13053e651cd98157356e8349df Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 4 Nov 2024 16:46:04 -0800 Subject: [PATCH 132/157] Integrates color palette to stake app --- bittensor_cli/cli.py | 11 +- bittensor_cli/src/__init__.py | 50 ++++++++ bittensor_cli/src/commands/stake/stake.py | 146 +++++++++++----------- bittensor_cli/src/commands/wallets.py | 12 +- 4 files changed, 135 insertions(+), 84 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d2393f54..0361cc6b 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -24,6 +24,7 @@ WalletOptions as WO, WalletValidationTypes as WV, Constants, + COLOR_PALETTE, ) from bittensor_cli.src.bittensor import utils from bittensor_cli.src.bittensor.balances import Balance @@ -298,7 +299,7 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in return None elif netuid is None and all_netuids is False: answer = Prompt.ask( - "[dark_sea_green3]Enter the netuid to use.[/dark_sea_green3] Leave blank for all netuids", + f"Enter the [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]netuid[/{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}] to use. Leave blank for all netuids", default=None, show_default=False, ) @@ -823,7 +824,7 @@ def initialize_chain( elif self.config["network"]: self.subtensor = SubtensorInterface(self.config["network"]) console.print( - f"Using the specified network [dark_orange]{self.config['network']}[/dark_orange] from config" + f"Using the specified network [{COLOR_PALETTE['GENERAL']['LINKS']}]{self.config['network']}[/{COLOR_PALETTE['GENERAL']['LINKS']}] from config" ) else: self.subtensor = SubtensorInterface(defaults.subtensor.network) @@ -1185,7 +1186,7 @@ def wallet_ask( else: wallet_name = Prompt.ask( "Enter the [blue]wallet name[/blue]" - + " [dark_sea_green3 italic](Hint: You can set this with `btcli config set --wallet-name`)[/dark_sea_green3 italic]", + + f" [{COLOR_PALETTE['GENERAL']['HINT']} italic](Hint: You can set this with `btcli config set --wallet-name`)", default=defaults.wallet.name, ) @@ -2614,11 +2615,11 @@ def stake_add( raise typer.Exit() if netuid is not None: amount = FloatPrompt.ask( - "[dark_sea_green]Amount to stake (TAO τ)[/dark_sea_green]" + f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake (TAO τ)" ) else: amount = FloatPrompt.ask( - "[dark_sea_green]Amount to stake to each netuid (TAO τ)[/dark_sea_green]" + f"Amount to [{COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN']}]stake to each netuid (TAO τ)" ) if amount <= 0: diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 9e46757b..6cd3416b 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -550,3 +550,53 @@ class WalletValidationTypes(Enum): }, "WEIGHTS": {"COMMIT_REVEAL": "Commit / Reveal"}, } + +COLOR_PALETTE = { + "GENERAL": { + "HEADER": "#4196D6", + "LINKS": "#8CB9E9", + "HINT": "#A2E5B8", + "COLDKEY": "#9EF5E4", + "HOTKEY": "#ECC39D", + "SUBHEADING_MAIN": "#7ECFEC", + "SUBHEADING": "#AFEFFF", + "SUBHEADING_EXTRA_1": "#96A3C5", + "SUBHEADING_EXTRA_2": "#6D7BAF", + "CONFIRMATION_Y_N_Q": "#EE8DF8", + "SYMBOL": "#E7CC51", + "BALANCE": "#4F91C6" + }, + "STAKE": { + "STAKE_AMOUNT": "#53B5A0", + "STAKE_ALPHA": "#53B5A0", + "STAKE_SWAP": "#67A3A5", + "TAO": "#4F91C6", + "SLIPPAGE_TEXT": "#C25E7C", + "SLIPPAGE_PERCENT": "#E7B195", + "NOT_REGISTERED": "#EB6A6C", + "EXTRA_1": "#D781BB" + }, + "POOLS": { + "TAO": "#4F91C6", + "ALPHA_IN": "#D09FE9", + "ALPHA_OUT": "#AB7CC8", + "RATE": "#F8D384", + "TAO_EQUIV": "#8CB9E9", + "EMISSION": "#F8D384", + "EXTRA_1": "#CAA8FB", + "EXTRA_2": "#806DAF", + }, + "GREY": { + "GREY_100": "#F8F9FA", + "GREY_200": "#F1F3F4", + "GREY_300": "#DBDDE1", + "GREY_400": "#BDC1C6", + "GREY_500": "#5F6368", + "GREY_600": "#2E3134", + "GREY_700": "#282A2D", + "GREY_800": "#17181B", + "GREY_900": "#0E1013", + "BLACK": "#000000" + } + +} \ No newline at end of file diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 2f0894d1..c51eb847 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -11,6 +11,7 @@ from rich import box from substrateinterface.exceptions import SubstrateRequestException +from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import StakeInfo from bittensor_cli.src.bittensor.utils import ( @@ -864,7 +865,7 @@ async def stake_add( ) # Init the table. table = Table( - title=f"\n[dark_orange]Staking to: \nWallet: [light_goldenrod2]{wallet.name}[/light_goldenrod2], Coldkey ss58: [light_goldenrod2]{wallet.coldkeypub.ss58_address}[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Staking to: \nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1002,7 +1003,7 @@ async def stake_add( slippage_pct = f"{slippage_pct_float:.4f} %" else: slippage_pct_float = 0 - slippage_pct = "N/A" + slippage_pct = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]N/A[/{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]" max_slippage = max(slippage_pct_float, max_slippage) rows.append( ( @@ -1017,28 +1018,28 @@ async def stake_add( ) ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style="bright_magenta") + table.add_column("Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) table.add_column( - f"Amount ({Balance.get_unit(0)})", justify="center", style="dark_sea_green" + f"Amount ({Balance.get_unit(0)})", justify="center", style=COLOR_PALETTE["POOLS"]["TAO"] ) table.add_column( f"Rate (per {Balance.get_unit(0)})", justify="center", - style="light_goldenrod2", + style=COLOR_PALETTE["POOLS"]["RATE"], ) table.add_column( "Received", justify="center", - style="light_slate_blue", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], ) - table.add_column("Slippage", justify="center", style="rgb(220,50,47)") + table.add_column("Slippage", justify="center", style=COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']) for row in rows: table.add_row(*row) console.print(table) message = "" if max_slippage > 5: - message += "-------------------------------------------------------------------------------------------------------------------\n" - message += f"[bold][yellow]WARNING:[/yellow] The slippage on one of your operations is high: [bold red]{max_slippage} %[/bold red], this may result in a loss of funds.[/bold] \n" + message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n" + message += f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage} %[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n" message += "-------------------------------------------------------------------------------------------------------------------\n" console.print(message) console.print( @@ -1056,7 +1057,7 @@ async def stake_add( ) if prompt: if not Confirm.ask("Would you like to continue?"): - return False + raise typer.Exit() async def send_extrinsic( netuid_i, amount_, current, staking_address_ss58, status=None @@ -1088,7 +1089,7 @@ async def send_extrinsic( return if not prompt: # TODO verbose? console.print( - f":white_heavy_check_mark: [green]Submitted {amount_} to {netuid_i}[/green]" + f":white_heavy_check_mark: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Submitted {amount_} to {netuid_i}[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" ) else: await response.process_events() @@ -1108,10 +1109,10 @@ async def send_extrinsic( new_balance = new_balance_[wallet.coldkeypub.ss58_address] new_stake = new_stake_.set_unit(netuid_i) console.print( - f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" ) console.print( - f"Subnet: [dark_orange]{netuid_i}[/dark_orange] Stake:\n [blue]{current}[/blue] :arrow_right: [green]{new_stake}[/green]" + f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] Stake:\n [blue]{current}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}" ) # Perform staking operation. @@ -1273,8 +1274,8 @@ async def unstake( # Prompt the user for each subnet while True: response = Prompt.ask( - f"Unstake all: [dark_sea_green3]{current_stake_balance}[/dark_sea_green3]" - f" from [dark_sea_green3]{staking_address_name if staking_address_name else staking_address_ss58}[/dark_sea_green3] on netuid: [dark_sea_green3]{netuid}? [y/n/q]", + f"Unstake all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" + f" from [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{staking_address_name if staking_address_name else staking_address_ss58}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] on netuid: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]? [y/n/q]", choices=["y", "n", "q"], default="n", show_choices=True, @@ -1290,7 +1291,7 @@ async def unstake( elif response.lower() == "n": while True: amount_input = Prompt.ask( - f"Enter amount to unstake in [dark_sea_green3]{Balance.get_unit(netuid)}[/dark_sea_green3] from subnet: [dark_sea_green3]{netuid}[/dark_sea_green3] (Max: [dark_sea_green3]{current_stake_balance}[/dark_sea_green3])" + f"Enter amount to unstake in [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] from subnet: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] (Max: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}])" ) if amount_input.lower() == "q": skip_remaining_subnets = True @@ -1382,7 +1383,7 @@ async def unstake( # Build the table table = Table( - title=f"\n[tan]Unstaking to: \nWallet: [dark_sea_green3]{wallet.name}[/dark_sea_green3], Coldkey ss58: [dark_sea_green3]{wallet.coldkeypub.ss58_address}[/dark_sea_green3]\nNetwork: {subtensor.network}[/tan]\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Unstaking to: \nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1393,22 +1394,22 @@ async def unstake( pad_edge=True, ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style="plum2") + table.add_column("Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) table.add_column( - f"Amount ({Balance.get_unit(1)})", justify="center", style="dark_sea_green" + f"Amount ({Balance.get_unit(1)})", justify="center", style=COLOR_PALETTE["POOLS"]["TAO"] ) table.add_column( f"Rate ({Balance.get_unit(0)}/{Balance.get_unit(1)})", justify="center", - style="light_goldenrod2", + style=COLOR_PALETTE["POOLS"]["RATE"], ) table.add_column( f"Received ({Balance.get_unit(0)})", justify="center", - style="light_slate_blue", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], footer=f"{total_received_amount}", ) - table.add_column("Slippage", justify="center", style="light_salmon3") + table.add_column("Slippage", justify="center", style=COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']) for op in unstake_operations: dynamic_info = op["dynamic_info"] @@ -1427,8 +1428,8 @@ async def unstake( if max_float_slippage > 5: console.print( "\n" - f"-------------------------------------------------------------------------------------------------------------------\n" - f"[bold][yellow]WARNING:[/yellow] The slippage on one of your operations is high: [bold red]{max_float_slippage}%[/bold red], this may result in a loss of funds.[/bold] \n" + f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n" + f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_float_slippage} %[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n" f"-------------------------------------------------------------------------------------------------------------------\n" ) @@ -1447,7 +1448,7 @@ async def unstake( ) if prompt: if not Confirm.ask("Would you like to continue?"): - return False + raise typer.Exit() # Perform unstaking operations try: @@ -1506,12 +1507,12 @@ async def unstake( ) ).set_unit(netuid_i) console.print( - f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" ) console.print( - f"Subnet: [dark_orange]{netuid_i}[/dark_orange] Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [green]{new_stake}[/green]" + f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}" ) - console.print("[green]Unstaking operations completed.[/green]") + console.print(f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed.") async def stake_list( @@ -1572,9 +1573,9 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): if slippage + swapped_tao_value != 0 else 0 ) - slippage_percentage = f"[salmon1]{slippage_percentage_:.3f}%[/salmon1]" + slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]" else: - slippage_percentage = "[salmon1]0.000%[/salmon1]" + slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]" tao_locked = pool.tao_in issuance = pool.alpha_out if pool.is_dynamic else tao_locked per_block_emission = substake_.emission.tao / emission_drain_tempo @@ -1600,20 +1601,20 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f"{pool.alpha_in.tao:,.4f} {symbol}", # Alpha Reserves a_in f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) f"{pool.alpha_out.tao:,.4f} {symbol}", # Alpha out (a_out) - f"[medium_purple]{tao_ownership}[/medium_purple]", # TAO equiv - f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Exchange Value (α x τ/α) - f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap(α) -> τ - "[bold cadet_blue]YES[/bold cadet_blue]" + f"{tao_ownership}", # TAO equiv + f"{tao_value}", # Exchange Value (α x τ/α) + f"{swapped_tao_value} ({slippage_percentage})", # Swap(α) -> τ + "YES" if substake_.is_registered - else "[hot_pink3]NO[/hot_pink3]", # Registered + else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered str(Balance.from_tao(per_block_emission).set_unit(netuid)) if substake_.is_registered - else "[hot_pink3]N/A[/hot_pink3]", # Emission(α/block) + else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block) ] ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") table = Table( - title=f"\n[dark_orange]Hotkey: {name}[/dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n\nSee below for an explanation of the columns\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {name}\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n\n[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1626,62 +1627,61 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): table.add_column("[white]Netuid", footer_style="overline white", style="grey89") table.add_column( "[white]Symbol", - footer_style="white", - style="light_goldenrod1", + style=COLOR_PALETTE["GENERAL"]["SYMBOL"], justify="center", no_wrap=True, ) table.add_column( f"[white]Stake ({Balance.get_unit(1)})", footer_style="overline white", - style="rgb(42,161,152)", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="center", ) table.add_column( f"[white]TAO Reserves ({Balance.unit}_in)", - style="medium_purple", + style=COLOR_PALETTE["STAKE"]["TAO"], justify="right", ) table.add_column( f"[white]Alpha Reserves ({Balance.get_unit(1)}_in)", - style="medium_purple", + style=COLOR_PALETTE["POOLS"]["ALPHA_IN"], justify="right", ) table.add_column( f"[white]Rate \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", footer_style="white", - style="light_goldenrod2", + style=COLOR_PALETTE["POOLS"]["RATE"], justify="center", ) table.add_column( f"[white]Alpha out ({Balance.get_unit(1)}_out)", - style="medium_purple", + style=COLOR_PALETTE["POOLS"]["ALPHA_OUT"], justify="right", ) table.add_column( f"[white]TAO equiv \n({Balance.unit}_in x {Balance.get_unit(1)}/{Balance.get_unit(1)}_out)", - style="medium_purple", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], justify="right", footer=f"{total_tao_ownership}", ) table.add_column( f"[white]Exchange Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", footer_style="overline white", - style="blue", + style=COLOR_PALETTE["STAKE"]["TAO"], justify="right", footer=f"{total_tao_value}", ) table.add_column( f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", footer_style="overline white", - style="white", + style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], justify="right", footer=f"{total_swapped_tao_value}", ) - table.add_column("[white]Registered", style="red", justify="right") + table.add_column("[white]Registered",style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="right") table.add_column( f"[white]Emission \n({Balance.get_unit(1)}/block)", - style="light_goldenrod2", + style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="right", ) for row in rows: @@ -1715,10 +1715,10 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): console.print("\n\n") console.print( f"Wallet:\n" - f" Coldkey SS58: [bold plum2]{coldkey_address}[/bold plum2]\n" - f" Free Balance: [dark_sea_green]{balance}[/dark_sea_green]\n" - f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" - f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" + f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" + f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f" Total TAO ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{all_hotkeys_total_global_tao}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f" Total Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{all_hotkeys_total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" ) if not sub_stakes: console.print(f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})") @@ -1822,15 +1822,15 @@ async def move_stake( if origin_stake_balance == Balance.from_tao(0).set_unit(origin_netuid): print_error( - f"Your balance is [dark_orange]0[/dark_orange] in Netuid: [dark_orange]{origin_netuid}[/dark_orange]" + f"Your balance is [{COLOR_PALETTE['POOLS']['TAO']}]0[/{COLOR_PALETTE['POOLS']['TAO']}] in Netuid: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}" ) raise typer.Exit() console.print( - f"\nOrigin netuid: [dark_orange]{origin_netuid}[/dark_orange], Origin stake: [dark_orange]{origin_stake_balance}" + f"\nOrigin Netuid: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], Origin stake: [{COLOR_PALETTE['POOLS']['TAO']}]{origin_stake_balance}" ) console.print( - f"Destination netuid: [dark_orange]{destination_netuid}[/dark_orange], Destination stake: [dark_orange]{destination_stake_balance}\n" + f"Destination netuid: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{destination_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], Destination stake: [{COLOR_PALETTE['POOLS']['TAO']}]{destination_stake_balance}\n" ) # Determine the amount we are moving. @@ -1841,13 +1841,13 @@ async def move_stake( amount_to_move_as_balance = origin_stake_balance else: # max_stake # TODO improve this - if Confirm.ask(f"Move all: [dark_orange]{origin_stake_balance}[/dark_orange]?"): + if Confirm.ask(f"Move all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]?"): amount_to_move_as_balance = origin_stake_balance else: try: amount = float( Prompt.ask( - f"Enter amount to move in [dark_orange]{Balance.get_unit(origin_netuid)}" + f"Enter amount to move in [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(origin_netuid)}" ) ) amount_to_move_as_balance = Balance.from_tao(amount) @@ -1859,7 +1859,7 @@ async def move_stake( amount_to_move_as_balance.set_unit(origin_netuid) if amount_to_move_as_balance > origin_stake_balance: err_console.print( - f"[red]Not enough stake[/red]:\n Stake balance:[dark_orange]{origin_stake_balance}[/dark_orange] < Moving amount: [dark_orange]{amount_to_move_as_balance}[/dark_orange]" + f"[red]Not enough stake[/red]:\n Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] < Moving amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_move_as_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" ) return False @@ -1903,7 +1903,7 @@ async def move_stake( ) table = Table( - title=f"\n[dark_orange]Moving stake from: [light_goldenrod2]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})[/light_goldenrod2] to: [light_goldenrod2]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})[/light_goldenrod2]\nNetwork: {subtensor.network}[/dark_orange]\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Moving stake from: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\nNetwork: {subtensor.network}\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1913,26 +1913,26 @@ async def move_stake( show_lines=False, pad_edge=True, ) - table.add_column("origin netuid", justify="center", style="green") - table.add_column("origin hotkey", justify="center", style="bright_magenta") - table.add_column("dest netuid", justify="center", style="green") - table.add_column("dest hotkey", justify="center", style="bright_magenta") + table.add_column("origin netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) + table.add_column("origin hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) + table.add_column("dest netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) + table.add_column("dest hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) table.add_column( f"amount ({Balance.get_unit(origin_netuid)})", justify="center", - style="medium_purple", + style=COLOR_PALETTE["STAKE"]["TAO"], ) table.add_column( f"rate ({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})", justify="center", - style="cyan", + style=COLOR_PALETTE["POOLS"]["RATE"], ) table.add_column( f"received ({Balance.get_unit(destination_netuid)})", justify="center", - style="rgb(220,50,47)", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], ) - table.add_column("slippage", justify="center", style="salmon1") + table.add_column("slippage", justify="center", style=COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']) table.add_row( f"{Balance.get_unit(origin_netuid)}({origin_netuid})", @@ -1949,9 +1949,9 @@ async def move_stake( console.print(table) message = "" if slippage_pct_float > 5: - message += "\t-------------------------------------------------------------------------------------------------------------------\n" - message += f"\t[bold][yellow]WARNING:[/yellow]\tSlippage is high: [bold red]{slippage_pct}[/bold red], this may result in a loss of funds.[/bold] \n" - message += "\t-------------------------------------------------------------------------------------------------------------------\n" + message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n" + message += f"[bold]WARNING:\tSlippage is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_pct}[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.[/bold] \n" + message += "-------------------------------------------------------------------------------------------------------------------\n" console.print(message) if not Confirm.ask("Would you like to continue?"): return True @@ -2011,10 +2011,10 @@ async def move_stake( ).set_unit(destination_netuid) console.print( f"Origin Stake:\n [blue]{origin_stake_balance}[/blue] :arrow_right: " - f"[green]{new_origin_stake_balance}[/green]" + f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_origin_stake_balance}" ) console.print( f"Destination Stake:\n [blue]{destination_stake_balance}[/blue] :arrow_right: " - f"[green]{new_destination_stake_balance}[/green]" + f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_destination_stake_balance}" ) return diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index ce504118..0c4d0d4d 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -25,7 +25,7 @@ import scalecodec import typer -from bittensor_cli.src import TYPE_REGISTRY +from bittensor_cli.src import TYPE_REGISTRY, COLOR_PALETTE from bittensor_cli.src.bittensor import utils from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import ( @@ -269,28 +269,28 @@ async def wallet_balance( ), Column( "[white]Coldkey Address", - style="plum2", + style=COLOR_PALETTE["GENERAL"]["COLDKEY"], no_wrap=True, ), Column( "[white]Free Balance", justify="right", - style="tan", + style=COLOR_PALETTE["GENERAL"]["BALANCE"], no_wrap=True, ), Column( "[white]Staked Balance", justify="right", - style="orange1", + style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"], no_wrap=True, ), Column( "[white]Total Balance", justify="right", - style="dark_sea_green", + style=COLOR_PALETTE["GENERAL"]["BALANCE"], no_wrap=True, ), - title=f"\n[underline navajo_white1]Wallet Coldkey Balance[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}", + title=f"\n [{COLOR_PALETTE['GENERAL']['HEADER']}]Wallet Coldkey Balance\nNetwork: {subtensor.network}", show_footer=True, show_edge=False, border_style="bright_black", From df654ad1aaae54e46dc71848287383a675c2ac97 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 5 Nov 2024 14:45:50 -0800 Subject: [PATCH 133/157] Disables overview, inspect, history, metagraph for rao games --- bittensor_cli/cli.py | 49 +++++++++++++++++++++++----- bittensor_cli/src/bittensor/utils.py | 34 +++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0361cc6b..33ad573c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -45,6 +45,8 @@ validate_chain_endpoint, retry_prompt, validate_netuid, + is_rao_network, + get_effective_network, ) from typing_extensions import Annotated from textwrap import dedent @@ -735,9 +737,9 @@ def __init__(self): self.subnets_app.command( "register", rich_help_panel=HELP_PANELS["SUBNETS"]["REGISTER"] )(self.subnets_register) - # self.subnets_app.command( - # "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] - # )(self.subnets_metagraph) + self.subnets_app.command( + "metagraph", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"], hidden=True + )(self.subnets_metagraph) self.subnets_app.command( "show", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] )(self.subnets_show) @@ -1396,6 +1398,12 @@ def wallet_overview( "Hotkeys names must be a comma-separated list, e.g., `--exclude-hotkeys hk1,hk2`.", ) + # For Rao games + effective_network = get_effective_network(self.config, network) + if is_rao_network(effective_network): + print_error("This command is disabled on the 'rao' network.") + raise typer.Exit() + return self._run_command( wallets.overview( wallet, @@ -1464,6 +1472,13 @@ def wallet_transfer( ask_for=[WO.NAME, WO.PATH], validate=WV.WALLET, ) + + # For Rao games + effective_network = get_effective_network(self.config, network) + if is_rao_network(effective_network): + print_error("This command is disabled on the 'rao' network.") + raise typer.Exit() + subtensor = self.initialize_chain(network) return self._run_command( wallets.transfer( @@ -1589,6 +1604,12 @@ def wallet_inspect( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate ) + # For Rao games + effective_network = get_effective_network(self.config, network) + if is_rao_network(effective_network): + print_error("This command is disabled on the 'rao' network.") + raise typer.Exit() + self.initialize_chain(network) return self._run_command( wallets.inspect( @@ -2138,13 +2159,17 @@ def wallet_history( [green]$[/green] btcli wallet history """ + # TODO: Fetch effective network and redirect users accordingly - this only works on finney + # no_use_config_str = "Using the network [dark_orange]finney[/dark_orange] and ignoring network/chain configs" - no_use_config_str = "Using the network [dark_orange]finney[/dark_orange] and ignoring network/chain configs" - - if self.config.get("network"): - if self.config.get("network") != "finney": - console.print(no_use_config_str) - + # if self.config.get("network"): + # if self.config.get("network") != "finney": + # console.print(no_use_config_str) + + # For Rao games + print_error("This command is disabled on the 'rao' network.") + raise typer.Exit() + self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask( wallet_name, @@ -3726,6 +3751,12 @@ def subnets_metagraph( ) raise typer.Exit() + # For Rao games + effective_network = get_effective_network(self.config, network) + if is_rao_network(effective_network): + print_error("This command is disabled on the 'rao' network.") + raise typer.Exit() + if reuse_last: if netuid is not None: console.print("Cannot specify netuid when using `--reuse-last`") diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 07a123d5..ffe319d1 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -23,6 +23,7 @@ from bittensor_cli.src.bittensor.balances import Balance +from bittensor_cli.src import defaults, Constants if TYPE_CHECKING: @@ -980,3 +981,36 @@ def validate_netuid(value: int) -> int: if value is not None and value < 0: raise typer.BadParameter("Negative netuid passed. Please use correct netuid.") return value + + +def get_effective_network(config, network: Optional[list[str]]) -> str: + """ + Determines the effective network to be used, considering the network parameter, + the configuration, and the default. + """ + if network: + for item in network: + if item.startswith("ws"): + network_ = item + break + else: + network_ = item + return network_ + elif config.get("network"): + return config["network"] + else: + return defaults.subtensor.network + +def is_rao_network(network: str) -> bool: + """Check if the given network is 'rao'.""" + + network = network.lower() + rao_identifiers = [ + "rao", + Constants.rao_entrypoint, + ] + return ( + network == "rao" + or network in rao_identifiers + or "rao.chain.opentensor.ai" in network + ) From 4b84b86f7265fc81d67f481f2e8ae12deb8edbe2 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 5 Nov 2024 15:12:42 -0800 Subject: [PATCH 134/157] Adds option to skip printing info tables in s list, st list --- bittensor_cli/src/commands/stake/stake.py | 198 +++++++++++++--------- bittensor_cli/src/commands/subnets.py | 110 ++++++------ 2 files changed, 179 insertions(+), 129 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index c51eb847..5507cec4 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1018,9 +1018,13 @@ async def stake_add( ) ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) table.add_column( - f"Amount ({Balance.get_unit(0)})", justify="center", style=COLOR_PALETTE["POOLS"]["TAO"] + "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"] + ) + table.add_column( + f"Amount ({Balance.get_unit(0)})", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO"], ) table.add_column( f"Rate (per {Balance.get_unit(0)})", @@ -1032,7 +1036,9 @@ async def stake_add( justify="center", style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], ) - table.add_column("Slippage", justify="center", style=COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']) + table.add_column( + "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] + ) for row in rows: table.add_row(*row) console.print(table) @@ -1394,9 +1400,13 @@ async def unstake( pad_edge=True, ) table.add_column("Netuid", justify="center", style="grey89") - table.add_column("Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) table.add_column( - f"Amount ({Balance.get_unit(1)})", justify="center", style=COLOR_PALETTE["POOLS"]["TAO"] + "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"] + ) + table.add_column( + f"Amount ({Balance.get_unit(1)})", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO"], ) table.add_column( f"Rate ({Balance.get_unit(0)}/{Balance.get_unit(1)})", @@ -1409,7 +1419,9 @@ async def unstake( style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], footer=f"{total_received_amount}", ) - table.add_column("Slippage", justify="center", style=COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']) + table.add_column( + "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] + ) for op in unstake_operations: dynamic_info = op["dynamic_info"] @@ -1512,7 +1524,9 @@ async def unstake( console.print( f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}" ) - console.print(f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed.") + console.print( + f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed." + ) async def stake_list( @@ -1678,7 +1692,11 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): justify="right", footer=f"{total_swapped_tao_value}", ) - table.add_column("[white]Registered",style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="right") + table.add_column( + "[white]Registered", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], + justify="right", + ) table.add_column( f"[white]Emission \n({Balance.get_unit(1)}/block)", style=COLOR_PALETTE["POOLS"]["EMISSION"], @@ -1723,73 +1741,81 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): if not sub_stakes: console.print(f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})") else: - console.print("\nPress Enter to continue to column descriptions...") - input() - header = """ - [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: - """ - console.print(header) - description_table = Table( - show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True - ) + display_table = Prompt.ask( + "\nPress Enter to view column descriptions or type 'q' to skip:", + choices=["", "q"], + default="", + show_choices=True, + ).lower() + + if display_table == "q": + console.print(f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped.") + else: + header = """ + [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: + """ + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) - fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), - ( - "[bold tan]Symbol[/bold tan]", - "The symbol for the subnet's dynamic TAO token.", - ), - ( - "[bold tan]Stake (α)[/bold tan]", - "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]TAO Reserves (τ_in)[/bold tan]", - 'Units of TAO in the TAO Reserves reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Reserves (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Alpha Reserves (α_in)[/bold tan]", - 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Reserves(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Reserves(τ_in) / Alpha Reserves (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Alpha out (α_out)[/bold tan]", - "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", - 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Exchange Value (α x τ/α)[/bold tan]", - "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Swap (α → τ)[/bold tan]", - "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Registered[/bold tan]", - "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Emission (α/block)[/bold tan]", - "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ] + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Stake (α)[/bold tan]", + "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Reserves (τ_in)[/bold tan]", + 'Units of TAO in the TAO Reserves reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Reserves (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Alpha Reserves (α_in)[/bold tan]", + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Reserves(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Reserves(τ_in) / Alpha Reserves (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Alpha out (α_out)[/bold tan]", + "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Exchange Value (α x τ/α)[/bold tan]", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Swap (α → τ)[/bold tan]", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Registered[/bold tan]", + "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Emission (α/block)[/bold tan]", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ] - description_table.add_column( - "Field", - no_wrap=True, - style="bold tan", - ) - description_table.add_column("Description", overflow="fold") - for field_name, description in fields: - description_table.add_row(field_name, description) - console.print(description_table) + description_table.add_column( + "Field", + no_wrap=True, + style="bold tan", + ) + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def move_stake( @@ -1841,7 +1867,9 @@ async def move_stake( amount_to_move_as_balance = origin_stake_balance else: # max_stake # TODO improve this - if Confirm.ask(f"Move all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]?"): + if Confirm.ask( + f"Move all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]?" + ): amount_to_move_as_balance = origin_stake_balance else: try: @@ -1913,10 +1941,18 @@ async def move_stake( show_lines=False, pad_edge=True, ) - table.add_column("origin netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) - table.add_column("origin hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) - table.add_column("dest netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) - table.add_column("dest hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) + table.add_column( + "origin netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"] + ) + table.add_column( + "origin hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"] + ) + table.add_column( + "dest netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"] + ) + table.add_column( + "dest hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"] + ) table.add_column( f"amount ({Balance.get_unit(origin_netuid)})", justify="center", @@ -1932,7 +1968,11 @@ async def move_stake( justify="center", style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], ) - table.add_column("slippage", justify="center", style=COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']) + table.add_column( + "slippage", + justify="center", + style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"], + ) table.add_row( f"{Balance.get_unit(origin_netuid)}({origin_netuid})", diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 48b07e0c..476af591 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -5,7 +5,7 @@ from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError -from rich.prompt import Confirm +from rich.prompt import Confirm, Prompt from rich.console import Console, Group from rich.spinner import Spinner from rich.text import Text @@ -13,6 +13,7 @@ from rich.table import Column, Table from rich import box +from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import SubnetState from bittensor_cli.src.bittensor.extrinsics.registration import ( @@ -482,57 +483,66 @@ def format_cell(value, previous_value, unit="", precision=4): table = create_table(subnets, global_weights) console.print(table) - console.print("\nPress Enter to continue to column descriptions...") - input() - header = """ -[bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: -""" - console.print(header) - description_table = Table( - show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True - ) + display_table = Prompt.ask( + "\nPress Enter to view column descriptions or type 'q' to skip:", + choices=["", "q"], + default="", + ).lower() - fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), - ( - "[bold tan]Symbol[/bold tan]", - "The symbol for the subnet's dynamic TAO token.", - ), - ( - "[bold tan]Emission (τ)[/bold tan]", - "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]STAKE (α_out)[/bold tan]", - "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Alpha Pool (α_in)[/bold tan]", - "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Tempo (k/n)[/bold tan]", - 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', - ), - ( - "[bold tan]Local weight coeff (γ)[/bold tan]", - "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ] + if display_table == "q": + console.print( + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped." + ) + else: + header = """ + [bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: + """ + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) + + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Emission (τ)[/bold tan]", + "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]STAKE (α_out)[/bold tan]", + "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Tempo (k/n)[/bold tan]", + 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Local weight coeff (γ)[/bold tan]", + "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ] - description_table.add_column("Field", no_wrap=True, style="bold tan") - description_table.add_column("Description", overflow="fold") - for field_name, description in fields: - description_table.add_row(field_name, description) - console.print(description_table) + description_table.add_column("Field", no_wrap=True, style="bold tan") + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def show(subtensor: "SubtensorInterface", netuid: int, prompt: bool = True): From c7677412d0c4d5ad6b67b7952639275630cd3aac Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 5 Nov 2024 16:37:32 -0800 Subject: [PATCH 135/157] Integrates palette to stake app --- bittensor_cli/src/__init__.py | 21 ++-- .../src/bittensor/extrinsics/registration.py | 27 ++--- bittensor_cli/src/commands/stake/stake.py | 4 +- bittensor_cli/src/commands/subnets.py | 98 +++++++++++++------ bittensor_cli/src/commands/sudo.py | 13 +-- 5 files changed, 105 insertions(+), 58 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 6cd3416b..b3077d8e 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -564,7 +564,12 @@ class WalletValidationTypes(Enum): "SUBHEADING_EXTRA_2": "#6D7BAF", "CONFIRMATION_Y_N_Q": "#EE8DF8", "SYMBOL": "#E7CC51", - "BALANCE": "#4F91C6" + "BALANCE": "#4F91C6", + "COST": "#53B5A0", + "SUCCESS": "#53B5A0", + "NETUID": "#CBA880", + "NETUID_EXTRA": "#DDD5A9", + "TEMPO": "#67A3A5", }, "STAKE": { "STAKE_AMOUNT": "#53B5A0", @@ -574,7 +579,7 @@ class WalletValidationTypes(Enum): "SLIPPAGE_TEXT": "#C25E7C", "SLIPPAGE_PERCENT": "#E7B195", "NOT_REGISTERED": "#EB6A6C", - "EXTRA_1": "#D781BB" + "EXTRA_1": "#D781BB", }, "POOLS": { "TAO": "#4F91C6", @@ -596,7 +601,11 @@ class WalletValidationTypes(Enum): "GREY_700": "#282A2D", "GREY_800": "#17181B", "GREY_900": "#0E1013", - "BLACK": "#000000" - } - -} \ No newline at end of file + "BLACK": "#000000", + }, + "SUDO": { + "HYPERPARAMETER": "#4F91C6", + "VALUE": "#D09FE9", + "NORMALIZED": "#AB7CC8", + }, +} diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index b45b3a5a..ae5db605 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -28,6 +28,7 @@ from rich.status import Status from substrateinterface.exceptions import SubstrateRequestException +from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.chain_data import NeuronInfo from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.utils import ( @@ -532,9 +533,9 @@ async def get_neuron_for_pubkey_and_subnet(): if prompt: if not Confirm.ask( f"Continue Registration?\n" - f" hotkey ({wallet.hotkey_str}):\t[bold white]{wallet.hotkey.ss58_address}[/bold white]\n" - f" coldkey ({wallet.name}):\t[bold white]{wallet.coldkeypub.ss58_address}[/bold white]\n" - f" network:\t\t[bold white]{subtensor.network}[/bold white]" + f" hotkey [{COLOR_PALETTE['GENERAL']['HOTKEY']}]({wallet.hotkey_str})[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]:\t[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n" + f" coldkey [{COLOR_PALETTE['GENERAL']['COLDKEY']}]({wallet.name})[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]:\t[{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" + f" network:\t\t[{COLOR_PALETTE['GENERAL']['LINKS']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['LINKS']}]\n" ): return False @@ -587,7 +588,7 @@ async def get_neuron_for_pubkey_and_subnet(): ) if is_registered: err_console.print( - f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/green]" + f":white_heavy_check_mark: [dark_sea_green3]Already registered on netuid:{netuid}[/dark_sea_green3]" ) return True @@ -634,8 +635,8 @@ async def get_neuron_for_pubkey_and_subnet(): if "HotKeyAlreadyRegisteredInSubNet" in err_msg: console.print( - f":white_heavy_check_mark: [green]Already Registered on " - f"[bold]subnet:{netuid}[/bold][/green]" + f":white_heavy_check_mark: [dark_sea_green3]Already Registered on " + f"[bold]subnet:{netuid}[/bold][/dark_sea_green3]" ) return True err_console.print( @@ -653,7 +654,7 @@ async def get_neuron_for_pubkey_and_subnet(): ) if is_registered: console.print( - ":white_heavy_check_mark: [green]Registered[/green]" + ":white_heavy_check_mark: [dark_sea_green3]Registered[/dark_sea_green3]" ) return True else: @@ -728,11 +729,11 @@ async def burned_register_extrinsic( if not neuron.is_null: console.print( - ":white_heavy_check_mark: [green]Already Registered[/green]:\n" - f"uid: [bold white]{neuron.uid}[/bold white]\n" - f"netuid: [bold white]{neuron.netuid}[/bold white]\n" - f"hotkey: [bold white]{neuron.hotkey}[/bold white]\n" - f"coldkey: [bold white]{neuron.coldkey}[/bold white]" + ":white_heavy_check_mark: [dark_sea_green3]Already Registered[/dark_sea_green3]:\n" + f"uid: [{COLOR_PALETTE['GENERAL']['NETUID_EXTRA']}]{neuron.uid}[/{COLOR_PALETTE['GENERAL']['NETUID_EXTRA']}]\n" + f"netuid: [{COLOR_PALETTE['GENERAL']['NETUID']}]{neuron.netuid}[/{COLOR_PALETTE['GENERAL']['NETUID']}]\n" + f"hotkey: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{neuron.hotkey}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n" + f"coldkey: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{neuron.coldkey}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" ) return True @@ -775,7 +776,7 @@ async def burned_register_extrinsic( console.print( "Balance:\n" - f" [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]" + f" [blue]{old_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance[wallet.coldkey.ss58_address]}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" ) if len(netuids_for_hotkey) > 0: diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 5507cec4..aefa7f52 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1749,7 +1749,9 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ).lower() if display_table == "q": - console.print(f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped.") + console.print( + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped." + ) else: header = """ [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 476af591..b9ad3177 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -95,15 +95,17 @@ async def _find_event_attributes_in_extrinsic_receipt( burn_cost = await lock_cost(subtensor) if burn_cost > your_balance: err_console.print( - f"Your balance of: [green]{your_balance}[/green] is not enough to pay the subnet lock cost of: " - f"[green]{burn_cost}[/green]" + f"Your balance of: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}[{COLOR_PALETTE['POOLS']['TAO']}] is not enough to pay the subnet lock cost of: " + f"[{COLOR_PALETTE['POOLS']['TAO']}]{burn_cost}[{COLOR_PALETTE['POOLS']['TAO']}]" ) return False if prompt: - console.print(f"Your balance is: [green]{your_balance}[/green]") + console.print( + f"Your balance is: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}" + ) if not Confirm.ask( - f"Do you want to register a subnet for [green]{burn_cost}[/green]?" + f"Do you want to register a subnet for [{COLOR_PALETTE['POOLS']['TAO']}]{burn_cost}?" ): return False @@ -151,7 +153,7 @@ async def _find_event_attributes_in_extrinsic_receipt( response, "NetworkAdded" ) console.print( - f":white_heavy_check_mark: [green]Registered subnetwork with netuid: {attributes[0]}[/green]" + f":white_heavy_check_mark: [{COLOR_PALETTE['GENERAL']['SUCCESS']}]Registered subnetwork with netuid: {attributes[0]}" ) return True @@ -177,8 +179,8 @@ async def fetch_subnet_data(): def define_table(total_emissions: float, total_rate: float): table = Table( - title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]" - f"\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnets" + f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}\n\n", show_footer=True, show_edge=False, header_style="bold white", @@ -189,38 +191,42 @@ def define_table(total_emissions: float, total_rate: float): pad_edge=True, ) - table.add_column("[bold white]NETUID", style="white", justify="center") - table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") + table.add_column("[bold white]NETUID", style="grey89", justify="center") + table.add_column( + "[bold white]SYMBOL", + style=COLOR_PALETTE["GENERAL"]["SYMBOL"], + justify="right", + ) table.add_column( f"[bold white]EMISSION ({Balance.get_unit(0)})", - style="tan", + style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="left", footer=f"τ {total_emissions:.4f}", ) table.add_column( f"[bold white]STAKE ({Balance.get_unit(1)}_out)", - style="light_salmon3", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="left", ) table.add_column( f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", - style="rgb(42,161,152)", + style=COLOR_PALETTE["STAKE"]["TAO"], justify="left", ) table.add_column( f"[bold white]Alpha Pool ({Balance.get_unit(1)}_in)", - style="rgb(42,161,152)", + style=COLOR_PALETTE["POOLS"]["ALPHA_IN"], justify="left", ) table.add_column( f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", - style="medium_purple", + style=COLOR_PALETTE["POOLS"]["RATE"], justify="left", footer=f"τ {total_rate:.4f}", ) table.add_column( "[bold white]Tempo (k/n)", - style="plum2", + style=COLOR_PALETTE["GENERAL"]["TEMPO"], justify="left", overflow="fold", ) @@ -672,7 +678,8 @@ async def show_subnet(netuid_: int): # Define table properties table = Table( - title=f"[underline navajo_white1]Subnet {netuid_}[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n", + title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnet [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -692,6 +699,7 @@ async def show_subnet(netuid_: int): tao_sum = Balance(0) stake_sum = Balance(0) relative_emissions_sum = 0 + stake_weight_sum = 0 for idx, hk in enumerate(subnet_state.hotkeys): hotkey_block_emission = ( subnet_state.emission[idx].tao / emission_sum @@ -701,6 +709,7 @@ async def show_subnet(netuid_: int): relative_emissions_sum += hotkey_block_emission tao_sum += subnet_state.global_stake[idx] stake_sum += subnet_state.local_stake[idx] + stake_weight_sum += subnet_state.stake_weight[idx] rows.append( ( str(idx), # UID @@ -720,14 +729,14 @@ async def show_subnet(netuid_: int): table.add_column("UID", style="grey89", no_wrap=True, justify="center") table.add_column( f"TAO({Balance.get_unit(0)})", - style="medium_purple", + style=COLOR_PALETTE["STAKE"]["TAO"], no_wrap=True, justify="right", footer=str(tao_sum), ) table.add_column( f"Stake({Balance.get_unit(netuid_)})", - style="rgb(42,161,152)", + style=COLOR_PALETTE["POOLS"]["ALPHA_IN"], no_wrap=True, justify="right", footer=f"{stake_sum.set_unit(subnet_info.netuid)}", @@ -737,10 +746,11 @@ async def show_subnet(netuid_: int): style="blue", no_wrap=True, justify="center", + footer=f"{stake_weight_sum:.3f}", ) table.add_column( "Dividends", - style="#8787d7", + style=COLOR_PALETTE["POOLS"]["EMISSION"], no_wrap=True, justify="center", footer=f"{relative_emissions_sum:.3f}", @@ -757,13 +767,23 @@ async def show_subnet(netuid_: int): # ) table.add_column( f"Emissions ({Balance.get_unit(netuid_)})", - style="tan", + style=COLOR_PALETTE["POOLS"]["EMISSION"], no_wrap=True, justify="center", footer=str(Balance.from_tao(emission_sum).set_unit(subnet_info.netuid)), ) - table.add_column("Hotkey", style="plum2", no_wrap=True, justify="center") - table.add_column("Coldkey", style="plum2", no_wrap=True, justify="center") + table.add_column( + "Hotkey", + style=COLOR_PALETTE["GENERAL"]["HOTKEY"], + no_wrap=True, + justify="center", + ) + table.add_column( + "Coldkey", + style=COLOR_PALETTE["GENERAL"]["COLDKEY"], + no_wrap=True, + justify="center", + ) for row in rows: table.add_row(*row) @@ -772,7 +792,9 @@ async def show_subnet(netuid_: int): console.print(table) console.print("\n") console.print( - f"Subnet: {netuid_}:\n Owner: [bold plum2]{subnet_info.owner}[/bold plum2]\n Total Locked: [dark_sea_green]{subnet_info.total_locked}[/dark_sea_green]\n Owner Locked: [dark_sea_green]{subnet_info.owner_locked}[/dark_sea_green]" + f"Subnet: {netuid_}:\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" + f" Total Locked: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{subnet_info.total_locked}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f" Owner Locked: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{subnet_info.owner_locked}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" ) console.print( """ @@ -810,7 +832,9 @@ async def lock_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: ) if lc: lock_cost_ = Balance(lc) - console.print(f"Subnet lock cost: [green]{lock_cost_}[/green]") + console.print( + f"Subnet lock cost: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{lock_cost_}" + ) return lock_cost_ else: err_console.print("Subnet lock cost: [red]Failed to get subnet lock cost[/red]") @@ -898,7 +922,8 @@ async def register( # TODO make this a reusable function, also used in subnets list # Show creation table. table = Table( - title=f"\n[white]Register to netuid [dark_orange]{netuid}[/dark_orange]\nNetwork: [dark_orange]{subtensor.network}[/dark_orange]\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Register to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]netuid: {netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -912,23 +937,32 @@ async def register( "Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center" ) table.add_column( - "Symbol", style="rgb(211,54,130)", no_wrap=True, justify="center" + "Symbol", + style=COLOR_PALETTE["GENERAL"]["SYMBOL"], + no_wrap=True, + justify="center", ) table.add_column( f"Cost ({Balance.get_unit(0)})", - style="tan", + style=COLOR_PALETTE["POOLS"]["TAO"], no_wrap=True, justify="center", ) table.add_column( - "Hotkey", style="bright_magenta", no_wrap=True, justify="center" + "Hotkey", + style=COLOR_PALETTE["GENERAL"]["HOTKEY"], + no_wrap=True, + justify="center", ) table.add_column( - "Coldkey", style="bold bright_magenta", no_wrap=True, justify="center" + "Coldkey", + style=COLOR_PALETTE["GENERAL"]["COLDKEY"], + no_wrap=True, + justify="center", ) table.add_row( str(netuid), - f"[light_goldenrod1]{Balance.get_unit(netuid)}[light_goldenrod1]", + f"{Balance.get_unit(netuid)}", f"τ {current_recycle.tao:.4f}", f"{wallet.hotkey.ss58_address}", f"{wallet.coldkeypub.ss58_address}", @@ -936,8 +970,8 @@ async def register( console.print(table) if not ( Confirm.ask( - f"Your balance is: [bold green]{balance}[/bold green]\nThe cost to register by recycle is " - f"[bold red]{current_recycle}[/bold red]\nDo you want to continue?", + f"Your balance is: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\nThe cost to register by recycle is " + f"[{COLOR_PALETTE['GENERAL']['COST']}]{current_recycle}[/{COLOR_PALETTE['GENERAL']['COST']}]\nDo you want to continue?", default=False, ) ): diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 36bab3a1..83f0c9a6 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -8,7 +8,7 @@ from rich.prompt import Confirm from scalecodec import GenericCall -from bittensor_cli.src import HYPERPARAMS, DelegatesDetails +from bittensor_cli.src import HYPERPARAMS, DelegatesDetails, COLOR_PALETTE from bittensor_cli.src.bittensor.chain_data import decode_account_id from bittensor_cli.src.bittensor.utils import ( console, @@ -496,11 +496,12 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int): subnet = await subtensor.get_subnet_hyperparameters(netuid) table = Table( - Column("[white]HYPERPARAMETER", style="plum2"), - Column("[white]VALUE", style="light_goldenrod2"), - Column("[white]NORMALIZED", style="light_goldenrod3"), - title=f"[underline navajo_white1]\nSubnet Hyperparameters[/underline navajo_white1]\n NETUID: [navajo_white1]" - f"{netuid}[/navajo_white1] - Network: [navajo_white1]{subtensor.network}[/navajo_white1]\n", + Column("[white]HYPERPARAMETER", style=COLOR_PALETTE['SUDO']['HYPERPARAMETER']), + Column("[white]VALUE", style=COLOR_PALETTE['SUDO']['VALUE']), + Column("[white]NORMALIZED", style=COLOR_PALETTE['SUDO']['NORMALIZED']), + title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]\nSubnet Hyperparameters\n NETUID: " + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f" - Network: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", show_footer=True, width=None, pad_edge=False, From 966fa5298b9559a8e629cc266977bb1fd144cc61 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 5 Nov 2024 17:25:35 -0800 Subject: [PATCH 136/157] Partial: integrates new palette to sudo, wallet apps --- bittensor_cli/cli.py | 41 +++++++++++++-------------- bittensor_cli/src/commands/sudo.py | 16 +++++------ bittensor_cli/src/commands/wallets.py | 6 ++-- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 33ad573c..10b56eab 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1183,7 +1183,7 @@ def wallet_ask( if self.config.get("wallet_name"): wallet_name = self.config.get("wallet_name") console.print( - f"Using the wallet name from config:[bold cyan] {wallet_name}" + f"Using the [blue]wallet name[/blue] from config:[bold cyan] {wallet_name}" ) else: wallet_name = Prompt.ask( @@ -1196,7 +1196,7 @@ def wallet_ask( if self.config.get("wallet_hotkey"): wallet_hotkey = self.config.get("wallet_hotkey") console.print( - f"Using the wallet hotkey from config:[bold cyan] {wallet_hotkey}" + f"Using the [blue]wallet hotkey[/blue] from config:[bold cyan] {wallet_hotkey}" ) else: wallet_hotkey = Prompt.ask( @@ -1211,7 +1211,7 @@ def wallet_ask( elif self.config.get("wallet_path"): wallet_path = self.config.get("wallet_path") console.print( - f"Using the wallet path from config:[bold magenta] {wallet_path}" + f"Using the [blue]wallet path[/blue] from config:[bold magenta] {wallet_path}" ) if WO.PATH in ask_for and not wallet_path: @@ -1751,7 +1751,7 @@ def wallet_regen_coldkey( if not wallet_name: wallet_name = Prompt.ask( - "Enter the name of the new wallet", default=defaults.wallet.name + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name ) wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) @@ -1805,7 +1805,7 @@ def wallet_regen_coldkey_pub( if not wallet_name: wallet_name = Prompt.ask( - "Enter the name of the new wallet", default=defaults.wallet.name + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name ) wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) @@ -1920,12 +1920,12 @@ def wallet_new_hotkey( if not wallet_name: wallet_name = Prompt.ask( - "Enter the wallet name", default=defaults.wallet.name + f"Enter the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]wallet name", default=defaults.wallet.name ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( - "Enter the name of the new hotkey", default=defaults.wallet.hotkey + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", default=defaults.wallet.hotkey ) wallet = self.wallet_ask( @@ -1975,7 +1975,7 @@ def wallet_new_coldkey( if not wallet_name: wallet_name = Prompt.ask( - "Enter the name of the new wallet", default=defaults.wallet.name + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name ) wallet = self.wallet_ask( @@ -2043,12 +2043,12 @@ def wallet_create_wallet( if not wallet_name: wallet_name = Prompt.ask( - "Enter the name of the new wallet (coldkey)", + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name, ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( - "Enter the the name of the new hotkey", default=defaults.wallet.hotkey + f"Enter the the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", default=defaults.wallet.hotkey ) self.verbosity_handler(quiet, verbose) @@ -2426,8 +2426,9 @@ def wallet_sign( self.verbosity_handler(quiet, verbose) if use_hotkey is None: use_hotkey = Confirm.ask( - "Would you like to sign the transaction using your [red]hotkey[/red]?" - "\n[Type [red]y[/red] for [red]hotkey[/red] and [blue]n[/blue] for [blue]coldkey[/blue]] (default is [blue]coldkey[/blue])", + f"Would you like to sign the transaction using your [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]?" + f"\n[Type [{COLOR_PALETTE['GENERAL']['HOTKEY']}]y[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] for [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f" and [{COLOR_PALETTE['GENERAL']['COLDKEY']}]n[/{COLOR_PALETTE['GENERAL']['COLDKEY']}] for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]] (default is [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey[/{COLOR_PALETTE['GENERAL']['COLDKEY']}])", default=False, ) @@ -2438,7 +2439,7 @@ def wallet_sign( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate ) if not message: - message = typer.prompt("Enter the message to encode and sign") + message = Prompt.ask("Enter the [blue]message[/blue] to encode and sign") return self._run_command(wallets.sign(wallet, message, use_hotkey)) @@ -3226,7 +3227,7 @@ def sudo_set( if not param_value: param_value = Prompt.ask( - f"Enter the new value for [dark_orange]{param_name}[/dark_orange] in the VALUE column format" + f"Enter the new value for [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{param_name}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] in the VALUE column format" ) wallet = self.wallet_ask( @@ -3379,16 +3380,12 @@ def sudo_set_take( current_take = self._run_command( sudo.get_current_take(self.initialize_chain(network), wallet) ) - console.print(f"Current take is [dark_orange]{current_take * 100.:.2f}%") + console.print(f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%") if not take: - max_value_style = typer.style(f"Max: {max_value}", fg="magenta") - min_value_style = typer.style(f"Min: {min_value}", fg="magenta") - prompt_text = typer.style( - "Enter take value (0.18 for 18%)", fg="bright_cyan", bold=True + take = FloatPrompt.ask( + f"Enter [blue]take value[/blue] (0.18 for 18%) [blue]Min: {min_value} Max: {max_value}" ) - take = FloatPrompt.ask(f"{prompt_text} {min_value_style} {max_value_style}") - if not (min_value <= take <= max_value): print_error( f"Take value must be between {min_value} and {max_value}. Provided value: {take}" @@ -3429,7 +3426,7 @@ def sudo_get_take( current_take = self._run_command( sudo.get_current_take(self.initialize_chain(network), wallet) ) - console.print(f"Current take is [dark_orange]{current_take * 100.:.2f}%") + console.print(f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%") def subnets_list( self, diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 83f0c9a6..7a6b1132 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -118,7 +118,7 @@ async def set_hyperparameter_extrinsic( return False with console.status( - f":satellite: Setting hyperparameter {parameter} to {value} on subnet: {netuid} ...", + f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{value}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...", spinner="earth", ): substrate = subtensor.substrate @@ -167,7 +167,7 @@ async def set_hyperparameter_extrinsic( # Successful registration, final check for membership else: console.print( - f":white_heavy_check_mark: [green]Hyperparameter {parameter} changed to {value}[/green]" + f":white_heavy_check_mark: [dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]" ) return True @@ -409,7 +409,7 @@ async def set_take_extrinsic( if current_take_u16 < take_u16: console.print( - f"Current take is [dark_orange]{current_take * 100.:.2f}%[/dark_orange]. Increasing to [dark_orange]{take * 100:.2f}%." + f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%[/{COLOR_PALETTE['POOLS']['RATE']}]. Increasing to [{COLOR_PALETTE['POOLS']['RATE']}]{take * 100:.2f}%." ) with console.status( f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..." @@ -426,7 +426,7 @@ async def set_take_extrinsic( else: console.print( - f"Current take is [dark_orange]{current_take * 100.:.2f}%[/dark_orange]. Decreasing to [dark_orange]{take * 100:.2f}%." + f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%[/{COLOR_PALETTE['POOLS']['RATE']}]. Decreasing to [{COLOR_PALETTE['POOLS']['RATE']}]{take * 100:.2f}%." ) with console.status( f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..." @@ -444,7 +444,7 @@ async def set_take_extrinsic( if not success: err_console.print(err) else: - console.print(":white_heavy_check_mark: [green]Finalized[/green]") + console.print(":white_heavy_check_mark: [dark_sea_green_3]Finalized[/dark_sea_green_3]") return success @@ -692,7 +692,7 @@ async def _do_set_take() -> bool: ) if not len(netuids_registered) > 0: err_console.print( - f"Hotkey [dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] is not registered to any subnet. Please register using [dark_orange]`btcli subnets register`[/dark_orange] and try again." + f"Hotkey [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] is not registered to any subnet. Please register using [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]`btcli subnets register`[{COLOR_PALETTE['GENERAL']['SUBHEADING']}] and try again." ) return False @@ -708,10 +708,10 @@ async def _do_set_take() -> bool: return False else: new_take = await get_current_take(subtensor, wallet) - console.print(f"New take is [dark_orange]{new_take * 100.:.2f}%") + console.print(f"New take is [{COLOR_PALETTE['POOLS']['RATE']}]{new_take * 100.:.2f}%") return True - console.print(f"Setting take on [dark_orange]network: {subtensor.network}") + console.print(f"Setting take on [{COLOR_PALETTE['GENERAL']['LINKS']}]network: {subtensor.network}") try: wallet.unlock_hotkey() diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 0c4d0d4d..b408c7f6 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1694,11 +1694,11 @@ async def sign(wallet: Wallet, message: str, use_hotkey: str): ) if not use_hotkey: keypair = wallet.coldkey - print_verbose(f"Signing using coldkey: {wallet.name}") + print_verbose(f"Signing using [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey: {wallet.name}") else: keypair = wallet.hotkey - print_verbose(f"Signing using hotkey: {wallet.hotkey_str}") + print_verbose(f"Signing using [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey: {wallet.hotkey_str}") signed_message = keypair.sign(message.encode("utf-8")).hex() - console.print("[bold green]Message signed successfully:") + console.print("[dark_sea_green3]Message signed successfully:") console.print(signed_message) From 68798fc7618acd667dc6ac1a8f342fb117610711 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 7 Nov 2024 16:32:10 -0800 Subject: [PATCH 137/157] Overhauled identities module --- bittensor_cli/cli.py | 253 ++++++++---------- bittensor_cli/src/bittensor/chain_data.py | 18 ++ .../src/bittensor/subtensor_interface.py | 64 +++-- bittensor_cli/src/bittensor/utils.py | 65 ++++- bittensor_cli/src/commands/subnets.py | 88 +++--- bittensor_cli/src/commands/wallets.py | 244 +++++------------ 6 files changed, 353 insertions(+), 379 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 10b56eab..ba6932b5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 import asyncio -import binascii import curses -from functools import partial import os.path import re import ssl @@ -47,6 +45,7 @@ validate_netuid, is_rao_network, get_effective_network, + prompt_for_identity, ) from typing_extensions import Annotated from textwrap import dedent @@ -1213,6 +1212,8 @@ def wallet_ask( console.print( f"Using the [blue]wallet path[/blue] from config:[bold magenta] {wallet_path}" ) + else: + wallet_path = defaults.wallet.path if WO.PATH in ask_for and not wallet_path: wallet_path = Prompt.ask( @@ -1751,7 +1752,8 @@ def wallet_regen_coldkey( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + default=defaults.wallet.name, ) wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) @@ -1805,7 +1807,8 @@ def wallet_regen_coldkey_pub( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + default=defaults.wallet.name, ) wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) @@ -1920,12 +1923,14 @@ def wallet_new_hotkey( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]wallet name", default=defaults.wallet.name + f"Enter the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]wallet name", + default=defaults.wallet.name, ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", default=defaults.wallet.hotkey + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", + default=defaults.wallet.hotkey, ) wallet = self.wallet_ask( @@ -1975,7 +1980,8 @@ def wallet_new_coldkey( if not wallet_name: wallet_name = Prompt.ask( - f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", default=defaults.wallet.name + f"Enter the name of the [{COLOR_PALETTE['GENERAL']['COLDKEY']}]new wallet (coldkey)", + default=defaults.wallet.name, ) wallet = self.wallet_ask( @@ -2048,7 +2054,8 @@ def wallet_create_wallet( ) if not wallet_hotkey: wallet_hotkey = Prompt.ask( - f"Enter the the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", default=defaults.wallet.hotkey + f"Enter the the name of the [{COLOR_PALETTE['GENERAL']['HOTKEY']}]new hotkey", + default=defaults.wallet.hotkey, ) self.verbosity_handler(quiet, verbose) @@ -2165,11 +2172,11 @@ def wallet_history( # if self.config.get("network"): # if self.config.get("network") != "finney": # console.print(no_use_config_str) - + # For Rao games print_error("This command is disabled on the 'rao' network.") raise typer.Exit() - + self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask( wallet_name, @@ -2186,69 +2193,37 @@ def wallet_set_id( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[list[str]] = Options.network, - display_name: str = typer.Option( + name: str = typer.Option( "", - "--display-name", - "--display", + "--name", help="The display name for the identity.", ), - legal_name: str = typer.Option( - "", - "--legal-name", - "--legal", - help="The legal name for the identity.", - ), web_url: str = typer.Option( "", "--web-url", "--web", help="The web URL for the identity.", ), - riot_handle: str = typer.Option( - "", - "--riot-handle", - "--riot", - help="The Riot handle for the identity.", - ), - email: str = typer.Option( - "", - help="The email address for the identity.", - ), - pgp_fingerprint: str = typer.Option( - "", - "--pgp-fingerprint", - "--pgp", - help="The PGP fingerprint for the identity.", - ), image_url: str = typer.Option( "", "--image-url", "--image", help="The image URL for the identity.", ), - info_: str = typer.Option( + discord_handle: str = typer.Option( "", - "--info", - "-i", - help="The info for the identity.", + "--discord", + help="The Discord handle for the identity.", ), - twitter_url: str = typer.Option( + description: str = typer.Option( "", - "-x", - "-𝕏", - "--twitter-url", - "--twitter", - help="The 𝕏 (Twitter) URL for the identity.", + "--description", + help="The description for the identity.", ), - validator_id: Optional[bool] = typer.Option( - None, - "--validator/--not-validator", - help="Are you updating a validator hotkey identity?", - ), - subnet_netuid: Optional[int] = typer.Option( - None, - "--netuid", - help="Netuid if you are updating identity of a subnet owner", + additional_info: str = typer.Option( + "", + "--additional", + help="Additional details for the identity.", ), quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -2276,94 +2251,64 @@ def wallet_set_id( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.HOTKEY, WO.PATH, WO.NAME], - validate=WV.WALLET_AND_HOTKEY, + ask_for=[WO.NAME], + validate=WV.WALLET, ) - if not any( - [ - display_name, - legal_name, - web_url, - riot_handle, - email, - pgp_fingerprint, - image_url, - info_, - twitter_url, - ] - ): - console.print( - "[yellow]All fields are optional. Press Enter to skip a field.[/yellow]" - ) - text_rejection = partial( - retry_prompt, - rejection=lambda x: sys.getsizeof(x) > 113, - rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.", - ) - - def pgp_check(s: str): - try: - if s.startswith("0x"): - s = s[2:] # Strip '0x' - pgp_fingerprint_encoded = binascii.unhexlify(s.replace(" ", "")) - except Exception: - return True - return True if len(pgp_fingerprint_encoded) != 20 else False - - display_name = display_name or text_rejection("Display name") - legal_name = legal_name or text_rejection("Legal name") - web_url = web_url or text_rejection("Web URL") - riot_handle = riot_handle or text_rejection("Riot handle") - email = email or text_rejection("Email address") - pgp_fingerprint = pgp_fingerprint or retry_prompt( - "PGP fingerprint (Eg: A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678)", - lambda s: False if not s else pgp_check(s), - "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes.", - ) - image_url = image_url or text_rejection("Image URL") - info_ = info_ or text_rejection("Enter info") - twitter_url = twitter_url or text_rejection("𝕏 (Twitter) URL") - - validator_id = validator_id or Confirm.ask( - "Are you updating a [bold blue]validator hotkey[/bold blue] identity or a [bold blue]subnet " - "owner[/bold blue] identity?\n" - "Enter [bold green]Y[/bold green] for [bold]validator hotkey[/bold] or [bold red]N[/bold red] for " - "[bold]subnet owner[/bold]", - show_choices=True, - ) - - if validator_id is False: - subnet_netuid = IntPrompt.ask("Enter the netuid of the subnet you own") + current_identity = self._run_command( + wallets.get_id( + self.initialize_chain(network), + wallet.coldkeypub.ss58_address, + "Current on-chain identity", + ) + ) + + if prompt: + if not Confirm.ask( + "Cost to register an [blue]Identity[/blue] is [blue]0.1 TAO[/blue]," + " are you sure you wish to continue?" + ): + console.print(":cross_mark: Aborted!") + raise typer.Exit() + + identity = prompt_for_identity( + current_identity, + name, + web_url, + image_url, + discord_handle, + description, + additional_info, + ) return self._run_command( wallets.set_id( wallet, self.initialize_chain(network), - display_name, - legal_name, - web_url, - pgp_fingerprint, - riot_handle, - email, - image_url, - twitter_url, - info_, - validator_id, - subnet_netuid, + identity["name"], + identity["url"], + identity["image"], + identity["discord"], + identity["description"], + identity["additional"], prompt, ) ) def wallet_get_id( self, - target_ss58_address: str = typer.Option( + wallet_name: Optional[str] = Options.wallet_name, + wallet_hotkey: Optional[str] = Options.wallet_hotkey, + wallet_path: Optional[str] = Options.wallet_path, + coldkey_ss58=typer.Option( None, + "--ss58", + "--coldkey_ss58", + "--coldkey.ss58_address", + "--coldkey.ss58", "--key", "-k", - "--ss58", - help="The coldkey or hotkey ss58 address to query.", - prompt=True, + help="Coldkey address of the wallet", ), network: Optional[list[str]] = Options.network, quiet: bool = Options.quiet, @@ -2386,13 +2331,28 @@ def wallet_get_id( [bold]Note[/bold]: This command is primarily used for informational purposes and has no side effects on the blockchain network state. """ - if not is_valid_ss58_address(target_ss58_address): - print_error("You have entered an incorrect ss58 address. Please try again") - raise typer.Exit() + wallet = None + if coldkey_ss58: + if not is_valid_ss58_address(coldkey_ss58): + print_error("You entered an invalid ss58 address") + raise typer.Exit() + else: + coldkey_or_ss58 = Prompt.ask( + "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]", + default=self.config.get("wallet_name") or defaults.wallet.name, + ) + if is_valid_ss58_address(coldkey_or_ss58): + coldkey_ss58 = coldkey_or_ss58 + else: + wallet_name = coldkey_or_ss58 if coldkey_or_ss58 else wallet_name + wallet = self.wallet_ask( + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + ) + coldkey_ss58 = wallet.coldkeypub.ss58_address self.verbosity_handler(quiet, verbose) return self._run_command( - wallets.get_id(self.initialize_chain(network), target_ss58_address) + wallets.get_id(self.initialize_chain(network), coldkey_ss58) ) def wallet_sign( @@ -3380,7 +3340,9 @@ def sudo_set_take( current_take = self._run_command( sudo.get_current_take(self.initialize_chain(network), wallet) ) - console.print(f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%") + console.print( + f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%" + ) if not take: take = FloatPrompt.ask( @@ -3426,7 +3388,9 @@ def sudo_get_take( current_take = self._run_command( sudo.get_current_take(self.initialize_chain(network), wallet) ) - console.print(f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%") + console.print( + f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%" + ) def subnets_list( self, @@ -3541,13 +3505,32 @@ def subnets_create( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, + ask_for=[ + WO.NAME, + ], + validate=WV.WALLET, ) - return self._run_command( + success = self._run_command( subnets.create(wallet, self.initialize_chain(network), prompt) ) + if success and prompt: + set_id = Confirm.ask( + "[dark_sea_green3]Do you want to set/update your identity?", + default=False, + show_default=True, + ) + if set_id: + self.wallet_set_id( + wallet_name=wallet.name, + wallet_hotkey=wallet.hotkey, + wallet_path=wallet.path, + network=network, + prompt=prompt, + quiet=quiet, + verbose=verbose, + ) + def subnets_pow_register( self, wallet_name: Optional[str] = Options.wallet_name, diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index bee31608..9465edda 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -78,6 +78,24 @@ def decode_account_id(account_id_bytes: tuple): return ss58_encode(bytes(account_id_bytes).hex(), SS58_FORMAT) +def decode_hex_identity(info_dictionary): + decoded_info = {} + for k, v in info_dictionary.items(): + if isinstance(v, dict): + item = next(iter(v.values())) + else: + item = v + + if isinstance(item, tuple): + try: + decoded_info[k] = bytes(item).decode() + except UnicodeDecodeError: + print(f"Could not decode: {k}: {item}") + else: + decoded_info[k] = item + return decoded_info + + def process_stake_data(stake_data): decoded_stake_data = {} for account_id_bytes, stake_ in stake_data: diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 0fa1fc9d..6fbc4c14 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -24,6 +24,7 @@ NeuronInfo, SubnetHyperparameters, decode_account_id, + decode_hex_identity, DelegateInfoLite, DynamicInfo, ) @@ -819,6 +820,36 @@ async def get_delegated( return DelegateInfo.delegated_list_from_vec_u8(bytes_result) + async def query_all_identities( + self, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[str, dict]: + """ + Queries all identities on the Bittensor blockchain. + + :param block_hash: The hash of the blockchain block number at which to perform the query. + :param reuse_block: Whether to reuse the last-used blockchain block hash. + + :return: A dictionary mapping addresses to their decoded identity data. + """ + + identities = await self.substrate.query_map( + module="SubtensorModule", + storage_function="Identities", + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + + if identities is None: + return {} + + all_identities = { + decode_account_id(ss58_address[0]): decode_hex_identity(identity) + for ss58_address, identity in identities + } + return all_identities + async def query_identity( self, key: str, @@ -843,40 +874,17 @@ async def query_identity( The identity information can include various attributes such as the neuron's stake, rank, and other network-specific details, providing insights into the neuron's role and status within the Bittensor network. """ - - def decode_hex_identity_dict(info_dictionary): - for k, v in info_dictionary.items(): - if isinstance(v, dict): - item = next(iter(v.values())) - else: - item = v - if isinstance(item, tuple) and item: - if len(item) > 1: - try: - info_dictionary[k] = ( - bytes(item).hex(sep=" ", bytes_per_sep=2).upper() - ) - except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") - else: - try: - info_dictionary[k] = bytes(item[0]).decode("utf-8") - except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") - else: - info_dictionary[k] = item - - return info_dictionary - identity_info = await self.substrate.query( - module="Registry", - storage_function="IdentityOf", + module="SubtensorModule", + storage_function="Identities", params=[key], block_hash=block_hash, reuse_block_hash=reuse_block, ) + if not identity_info: + return {} try: - return decode_hex_identity_dict(identity_info["info"]) + return decode_hex_identity(identity_info) except TypeError: return {} diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index ffe319d1..cc47de9f 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -3,9 +3,11 @@ import os import sqlite3 import webbrowser +import sys from pathlib import Path from typing import TYPE_CHECKING, Any, Collection, Optional, Union, Callable from urllib.parse import urlparse +from functools import partial from bittensor_wallet import Wallet, Keypair from bittensor_wallet.utils import SS58_FORMAT @@ -16,6 +18,7 @@ import numpy as np from numpy.typing import NDArray from rich.console import Console +from rich.prompt import Prompt import scalecodec from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset @@ -954,7 +957,7 @@ def retry_prompt( rejection_text: str, default="", show_default=False, - prompt_type=typer.prompt, + prompt_type=Prompt.ask, ): """ Allows for asking prompts again if they do not meet a certain criteria (as defined in `rejection`) @@ -1001,9 +1004,9 @@ def get_effective_network(config, network: Optional[list[str]]) -> str: else: return defaults.subtensor.network + def is_rao_network(network: str) -> bool: """Check if the given network is 'rao'.""" - network = network.lower() rao_identifiers = [ "rao", @@ -1014,3 +1017,61 @@ def is_rao_network(network: str) -> bool: or network in rao_identifiers or "rao.chain.opentensor.ai" in network ) + + +def prompt_for_identity( + current_identity: dict, + name: Optional[str], + web_url: Optional[str], + image_url: Optional[str], + discord_handle: Optional[str], + description: Optional[str], + additional_info: Optional[str], +): + """ + Prompts the user for identity fields with validation. + Returns a dictionary with the updated fields. + """ + identity_fields = {} + + fields = [ + ("name", "[blue]Display name[/blue]", name), + ("url", "[blue]Web URL[/blue]", web_url), + ("image", "[blue]Image URL[/blue]", image_url), + ("discord", "[blue]Discord handle[/blue]", discord_handle), + ("description", "[blue]Description[/blue]", description), + ("additional", "[blue]Additional information[/blue]", additional_info), + ] + + text_rejection = partial( + retry_prompt, + rejection=lambda x: sys.getsizeof(x) > 113, + rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.", + ) + + if not any( + [ + name, + web_url, + image_url, + discord_handle, + description, + additional_info, + ] + ): + console.print( + "[yellow]All fields are optional. Press Enter to skip and keep the default/existing value.[/yellow]\n" + "[dark_sea_green3]Tip: Entering a space and pressing Enter will clear existing default value.\n" + ) + + for key, prompt, value in fields: + if value: + identity_fields[key] = value + else: + identity_fields[key] = text_rejection( + prompt, + default=current_identity.get(key, ""), + show_default=True, + ) + + return identity_fields diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index b9ad3177..ec066b9e 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -2,6 +2,7 @@ import json import sqlite3 from typing import TYPE_CHECKING, Optional, cast +import typer from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError @@ -22,7 +23,7 @@ ) from rich.live import Live from bittensor_cli.src.bittensor.minigraph import MiniGraph -from bittensor_cli.src.commands.wallets import set_id, set_id_prompts +from bittensor_cli.src.commands.wallets import set_id, get_id from bittensor_cli.src.bittensor.utils import ( RAO_PER_TAO, console, @@ -35,6 +36,7 @@ millify, render_table, update_metadata_table, + prompt_for_identity, ) if TYPE_CHECKING: @@ -153,7 +155,7 @@ async def _find_event_attributes_in_extrinsic_receipt( response, "NetworkAdded" ) console.print( - f":white_heavy_check_mark: [{COLOR_PALETTE['GENERAL']['SUCCESS']}]Registered subnetwork with netuid: {attributes[0]}" + f":white_heavy_check_mark: [dark_sea_green3]Registered subnetwork with netuid: {attributes[0]}" ) return True @@ -172,10 +174,11 @@ async def subnets_list( async def fetch_subnet_data(): subnets = await subtensor.get_all_subnet_dynamic_info() - global_weights = await subtensor.get_global_weights( - [subnet.netuid for subnet in subnets] + global_weights, identities = await asyncio.gather( + subtensor.get_global_weights([subnet.netuid for subnet in subnets]), + subtensor.query_all_identities(), ) - return subnets, global_weights + return subnets, global_weights, identities def define_table(total_emissions: float, total_rate: float): table = Table( @@ -191,20 +194,21 @@ def define_table(total_emissions: float, total_rate: float): pad_edge=True, ) - table.add_column("[bold white]NETUID", style="grey89", justify="center") + table.add_column("[bold white]Netuid", style="grey89", justify="center") table.add_column( - "[bold white]SYMBOL", + "[bold white]Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"], justify="right", ) + table.add_column("[bold white]Owner", style="cyan", justify="left") table.add_column( - f"[bold white]EMISSION ({Balance.get_unit(0)})", + f"[bold white]Emission ({Balance.get_unit(0)})", style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="left", footer=f"τ {total_emissions:.4f}", ) table.add_column( - f"[bold white]STAKE ({Balance.get_unit(1)}_out)", + f"[bold white]Stake ({Balance.get_unit(1)}_out)", style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="left", ) @@ -236,7 +240,7 @@ def define_table(total_emissions: float, total_rate: float): return table # Non-live mode - def create_table(subnets, global_weights): + def create_table(subnets, global_weights, identities): rows = [] for subnet in subnets: netuid = subnet.netuid @@ -245,12 +249,15 @@ def create_table(subnets, global_weights): if netuid == 0: emission_tao = 0.0 + identity = "~" else: emission_tao = subnet.emission.tao + identity = identities.get(subnet.owner, {}).get("name", "~") - # Prepare content + # Prepare cells netuid_cell = str(netuid) symbol_cell = f"{subnet.symbol}" + identity_cell = identity emission_cell = f"{emission_tao:,.4f}" alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" tao_in_cell = f"{subnet.tao_in.tao:,.4f} τ" @@ -265,6 +272,7 @@ def create_table(subnets, global_weights): ( netuid_cell, # Netuid symbol_cell, # Symbol + identity_cell, # Identity emission_cell, # Emission (τ) alpha_out_cell, # Stake α_out tao_in_cell, # TAO Pool τ_in @@ -285,16 +293,15 @@ def create_table(subnets, global_weights): # Sort rows by emission, keeping the root subnet in the first position sorted_rows = [rows[0]] + sorted( - rows[1:], key=lambda x: float(str(x[2]).replace(",", "")), reverse=True + rows[1:], key=lambda x: float(str(x[3]).replace(",", "")), reverse=True ) - # Add rows to the table for row in sorted_rows: table.add_row(*row) return table # Live mode - def create_table_live(subnets, global_weights, previous_data): + def create_table_live(subnets, global_weights, identities, previous_data): def format_cell(value, previous_value, unit="", precision=4): if previous_value is not None: change = value - previous_value @@ -322,8 +329,10 @@ def format_cell(value, previous_value, unit="", precision=4): if netuid == 0: emission_tao = 0.0 + identity = "~" else: emission_tao = subnet.emission.tao + identity = identities.get(subnet.owner, {}).get("name", "~") # Store current values for comparison current_data[netuid] = { @@ -335,12 +344,11 @@ def format_cell(value, previous_value, unit="", precision=4): "blocks_since_last_step": subnet.blocks_since_last_step, "global_weight": global_weight, } - - # Retrieve previous data if available prev = previous_data.get(netuid) if previous_data else {} - # Prepare content + # Prepare cells netuid_cell = str(netuid) + identity_cell = identity symbol_cell = f"{subnet.symbol}" emission_cell = format_cell( emission_tao, prev.get("emission_tao"), unit="", precision=4 @@ -364,7 +372,7 @@ def format_cell(value, previous_value, unit="", precision=4): subnet.price.tao, prev.get("price"), unit=f" τ/{symbol}", precision=4 ) - # Content: Blocks_since_last_step + # Tempo cell prev_blocks_since_last_step = prev.get("blocks_since_last_step") if prev_blocks_since_last_step is not None: if subnet.blocks_since_last_step >= prev_blocks_since_last_step: @@ -388,7 +396,7 @@ def format_cell(value, previous_value, unit="", precision=4): f"{subnet.blocks_since_last_step}/{subnet.tempo}{block_change_text}" ) - # Content: Global_weight + # Local weight coeff cell prev_global_weight = prev.get("global_weight") if prev_global_weight is not None and global_weight is not None: weight_change = float(global_weight) - float(prev_global_weight) @@ -415,6 +423,7 @@ def format_cell(value, previous_value, unit="", precision=4): ( netuid_cell, # Netuid symbol_cell, # Symbol + identity_cell, # Identity emission_cell, # Emission (τ) alpha_out_cell, # Stake α_out tao_in_cell, # TAO Pool τ_in @@ -436,11 +445,9 @@ def format_cell(value, previous_value, unit="", precision=4): # Sort rows by emission, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted( rows[1:], - key=lambda x: float(str(x[2]).split()[0].replace(",", "")), + key=lambda x: float(str(x[3]).split()[0].replace(",", "")), reverse=True, ) - - # Add rows to the table for row in sorted_rows: table.add_row(*row) return table, current_data @@ -462,18 +469,15 @@ def format_cell(value, previous_value, unit="", precision=4): with Live(console=console, screen=True, auto_refresh=True) as live: try: while True: - subnets, global_weights = await fetch_subnet_data() + subnets, global_weights, identities = await fetch_subnet_data() table, current_data = create_table_live( - subnets, global_weights, previous_data + subnets, global_weights, identities, previous_data ) previous_data = current_data progress.reset(progress_task) start_time = asyncio.get_event_loop().time() - - # Create the message message = "\nLive view active. Press [bold red]Ctrl + C[/bold red] to exit" - # Include the message in the live render group live_render = Group(table, progress, message) live.update(live_render) while not progress.finished: @@ -485,8 +489,8 @@ def format_cell(value, previous_value, unit="", precision=4): pass # Ctrl + C else: # Non-live mode - subnets, global_weights = await fetch_subnet_data() - table = create_table(subnets, global_weights) + subnets, global_weights, identities = await fetch_subnet_data() + table = create_table(subnets, global_weights, identities) console.print(table) display_table = Prompt.ask( @@ -853,8 +857,30 @@ async def create(wallet: Wallet, subtensor: "SubtensorInterface", prompt: bool): ) if do_set_identity: - id_prompts = set_id_prompts(validator=False) - await set_id(wallet, subtensor, *id_prompts, prompt=prompt) + current_identity = await get_id( + subtensor, wallet.coldkeypub.ss58_address, "Current on-chain identity" + ) + if prompt: + if not Confirm.ask( + "\nCost to register an [blue]Identity[/blue] is [blue]0.1 TAO[/blue]," + " are you sure you wish to continue?" + ): + console.print(":cross_mark: Aborted!") + raise typer.Exit() + + identity = prompt_for_identity(current_identity=current_identity) + + await set_id( + wallet, + subtensor, + identity["name"], + identity["url"], + identity["image"], + identity["discord"], + identity["description"], + identity["additional"], + prompt, + ) async def pow_register( diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index b408c7f6..3f59ae11 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1413,213 +1413,90 @@ async def swap_hotkey( ) -def set_id_prompts( - validator: bool, -) -> tuple[str, str, str, str, str, str, str, str, str, bool, int]: - """ - Used to prompt the user to input their info for setting the ID - :return: (display_name, legal_name, web_url, riot_handle, email,pgp_fingerprint, image_url, info_, twitter_url, - validator_id) - """ - text_rejection = partial( - retry_prompt, - rejection=lambda x: sys.getsizeof(x) > 113, - rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.", - ) +def create_identity_table(title: str = None): + if not title: + title = "On-Chain Identity" - def pgp_check(s: str): - try: - if s.startswith("0x"): - s = s[2:] # Strip '0x' - pgp_fingerprint_encoded = binascii.unhexlify(s.replace(" ", "")) - except Exception: - return True - return True if len(pgp_fingerprint_encoded) != 20 else False - - display_name = text_rejection("Display name") - legal_name = text_rejection("Legal name") - web_url = text_rejection("Web URL") - riot_handle = text_rejection("Riot handle") - email = text_rejection("Email address") - pgp_fingerprint = retry_prompt( - "PGP fingerprint (Eg: A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678)", - lambda s: False if not s else pgp_check(s), - "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes.", - ) - image_url = text_rejection("Image URL") - info_ = text_rejection("Enter info") - twitter_url = text_rejection("𝕏 (Twitter) URL") - - subnet_netuid = None - if validator is False: - subnet_netuid = IntPrompt.ask("Enter the netuid of the subnet you own") - - return ( - display_name, - legal_name, - web_url, - pgp_fingerprint, - riot_handle, - email, - image_url, - twitter_url, - info_, - validator, - subnet_netuid, + table = Table( + Column("Item", justify="right", style=COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN'], no_wrap=True), + Column("Value", style=COLOR_PALETTE['GENERAL']['SUBHEADING']), + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{title}", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, ) + return table async def set_id( wallet: Wallet, subtensor: SubtensorInterface, - display_name: str, - legal_name: str, + name: str, web_url: str, - pgp_fingerprint: str, - riot_handle: str, - email: str, - image: str, - twitter: str, - info_: str, - validator_id: bool, - subnet_netuid: int, + image_url: str, + discord_handle: str, + description: str, + additional_info: str, prompt: bool, ): """Create a new or update existing identity on-chain.""" - id_dict = { - "additional": [[]], - "display": display_name, - "legal": legal_name, - "web": web_url, - "pgp_fingerprint": pgp_fingerprint, - "riot": riot_handle, - "email": email, - "image": image, - "twitter": twitter, - "info": info_, + identity_data = { + "name": name.encode(), + "url": web_url.encode(), + "image": image_url.encode(), + "discord": discord_handle.encode(), + "description": description.encode(), + "additional": additional_info.encode(), } - try: - pgp_fingerprint_encoded = binascii.unhexlify(pgp_fingerprint.replace(" ", "")) - except Exception as e: - print_error(f"The PGP is not in the correct format: {e}") - raise typer.Exit() - - for field, string in id_dict.items(): - if ( - field == "pgp_fingerprint" - and pgp_fingerprint - and len(pgp_fingerprint_encoded) != 20 - ): - err_console.print( - "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes." - ) - return False - elif (size := getsizeof(string)) > 113: # 64 + 49 overhead bytes for string + for field, value in identity_data.items(): + max_size = 64 # bytes + if len(value) > max_size: err_console.print( - f"[red]Error:[/red] Identity field [white]{field}[/white] must be <= 64 raw bytes.\n" - f"Value: '{string}' currently [white]{size} bytes[/white]." + f"[red]Error:[/red] Identity field [white]{field}[/white] must be <= {max_size} bytes.\n" + f"Value '{value.decode()}' is {len(value)} bytes." ) return False - identified = ( - wallet.hotkey.ss58_address if validator_id else wallet.coldkeypub.ss58_address - ) - encoded_id_dict = { - "info": { - "additional": [[]], - "display": {f"Raw{len(display_name.encode())}": display_name.encode()}, - "legal": {f"Raw{len(legal_name.encode())}": legal_name.encode()}, - "web": {f"Raw{len(web_url.encode())}": web_url.encode()}, - "riot": {f"Raw{len(riot_handle.encode())}": riot_handle.encode()}, - "email": {f"Raw{len(email.encode())}": email.encode()}, - "pgp_fingerprint": pgp_fingerprint_encoded if pgp_fingerprint else None, - "image": {f"Raw{len(image.encode())}": image.encode()}, - "info": {f"Raw{len(info_.encode())}": info_.encode()}, - "twitter": {f"Raw{len(twitter.encode())}": twitter.encode()}, - }, - "identified": identified, - } - - if prompt: - if not Confirm.ask( - "Cost to register an Identity is [bold white italic]0.1 Tao[/bold white italic]," - " are you sure you wish to continue?" - ): - console.print(":cross_mark: Aborted!") - raise typer.Exit() - - if validator_id: - block_hash = await subtensor.substrate.get_chain_head() - - is_registered_on_root, hotkey_owner = await asyncio.gather( - is_hotkey_registered( - subtensor, netuid=0, hotkey_ss58=wallet.hotkey.ss58_address - ), - subtensor.get_hotkey_owner( - hotkey_ss58=wallet.hotkey.ss58_address, block_hash=block_hash - ), - ) - - if not is_registered_on_root: - print_error("The hotkey is not registered on root. Aborting.") - return False - - own_hotkey = wallet.coldkeypub.ss58_address == hotkey_owner - if not own_hotkey: - print_error("The hotkey doesn't belong to the coldkey wallet. Aborting.") - return False - else: - subnet_owner_ = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="SubnetOwner", - params=[subnet_netuid], - ) - subnet_owner = decode_account_id(subnet_owner_[0]) - if subnet_owner != wallet.coldkeypub.ss58_address: - print_error(f":cross_mark: This wallet doesn't own subnet {subnet_netuid}.") - return False - try: wallet.unlock_coldkey() except KeyFileError: err_console.print("Error decrypting coldkey (possibly incorrect password)") return False + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_identity", + call_params=identity_data, + ) + with console.status( - ":satellite: [bold green]Updating identity on-chain...", spinner="earth" + " :satellite: [dark_sea_green3]Updating identity on-chain...", spinner="earth" ): - call = await subtensor.substrate.compose_call( - call_module="Registry", - call_function="set_identity", - call_params=encoded_id_dict, - ) success, err_msg = await subtensor.sign_and_send_extrinsic(call, wallet) if not success: err_console.print(f"[red]:cross_mark: Failed![/red] {err_msg}") return - console.print(":white_heavy_check_mark: Success!") - identity = await subtensor.query_identity( - identified or wallet.coldkey.ss58_address - ) + console.print(":white_heavy_check_mark: [dark_sea_green3]Success!") + identity = await subtensor.query_identity(wallet.coldkeypub.ss58_address) - table = Table( - Column("Key", justify="right", style="cyan", no_wrap=True), - Column("Value", style="magenta"), - title="[bold white italic]Updated On-Chain Identity", - ) - - table.add_row("Address", identified or wallet.coldkey.ss58_address) + table = create_identity_table(title="New on-chain Identity") + table.add_row("Address", wallet.coldkeypub.ss58_address) for key, value in identity.items(): - table.add_row(key, str(value) if value is not None else "~") + table.add_row(key, str(value) if value else "~") return console.print(table) -async def get_id(subtensor: SubtensorInterface, ss58_address: str): +async def get_id(subtensor: SubtensorInterface, ss58_address: str, title: str = None): with console.status( ":satellite: [bold green]Querying chain identity...", spinner="earth" ): @@ -1627,22 +1504,19 @@ async def get_id(subtensor: SubtensorInterface, ss58_address: str): if not identity: err_console.print( - f"[red]Identity not found[/red]" - f" for [light_goldenrod3]{ss58_address}[/light_goldenrod3]" - f" on [white]{subtensor}[/white]" + f"[red]Existing identity not found[/red]" + f" for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" + f" on {subtensor}" ) - return - table = Table( - Column("Item", justify="right", style="cyan", no_wrap=True), - Column("Value", style="magenta"), - title="[bold white italic]On-Chain Identity", - ) - + return {} + + table = create_identity_table(title) table.add_row("Address", ss58_address) for key, value in identity.items(): - table.add_row(key, str(value) if value is not None else "~") + table.add_row(key, str(value) if value else "~") - return console.print(table) + console.print(table) + return identity async def check_coldkey_swap(wallet: Wallet, subtensor: SubtensorInterface): @@ -1694,10 +1568,14 @@ async def sign(wallet: Wallet, message: str, use_hotkey: str): ) if not use_hotkey: keypair = wallet.coldkey - print_verbose(f"Signing using [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey: {wallet.name}") + print_verbose( + f"Signing using [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey: {wallet.name}" + ) else: keypair = wallet.hotkey - print_verbose(f"Signing using [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey: {wallet.hotkey_str}") + print_verbose( + f"Signing using [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey: {wallet.hotkey_str}" + ) signed_message = keypair.sign(message.encode("utf-8")).hex() console.print("[dark_sea_green3]Message signed successfully:") From 0f2df53ee11ea3d004b9abc5d88b9ec3e126c477 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 7 Nov 2024 16:45:47 -0800 Subject: [PATCH 138/157] ui improvements + small fix --- bittensor_cli/src/bittensor/utils.py | 2 +- bittensor_cli/src/commands/subnets.py | 12 ++++++++++-- bittensor_cli/src/commands/wallets.py | 13 +++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index cc47de9f..95cf5371 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -1060,7 +1060,7 @@ def prompt_for_identity( ] ): console.print( - "[yellow]All fields are optional. Press Enter to skip and keep the default/existing value.[/yellow]\n" + "\n[yellow]All fields are optional. Press Enter to skip and keep the default/existing value.[/yellow]\n" "[dark_sea_green3]Tip: Entering a space and pressing Enter will clear existing default value.\n" ) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index ec066b9e..c4ab7cbb 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -853,7 +853,7 @@ async def create(wallet: Wallet, subtensor: "SubtensorInterface", prompt: bool): if success and prompt: # Prompt for user to set identity. do_set_identity = Confirm.ask( - "Subnetwork registered successfully. Would you like to set your identity?" + "Would you like to set your [blue]identity?[/blue]" ) if do_set_identity: @@ -868,7 +868,15 @@ async def create(wallet: Wallet, subtensor: "SubtensorInterface", prompt: bool): console.print(":cross_mark: Aborted!") raise typer.Exit() - identity = prompt_for_identity(current_identity=current_identity) + identity = prompt_for_identity( + current_identity=current_identity, + name=None, + web_url=None, + image_url=None, + discord_handle=None, + description=None, + additional_info=None, + ) await set_id( wallet, diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 3f59ae11..5def5405 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1418,8 +1418,13 @@ def create_identity_table(title: str = None): title = "On-Chain Identity" table = Table( - Column("Item", justify="right", style=COLOR_PALETTE['GENERAL']['SUBHEADING_MAIN'], no_wrap=True), - Column("Value", style=COLOR_PALETTE['GENERAL']['SUBHEADING']), + Column( + "Item", + justify="right", + style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"], + no_wrap=True, + ), + Column("Value", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]), title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{title}", show_footer=True, show_edge=False, @@ -1504,12 +1509,12 @@ async def get_id(subtensor: SubtensorInterface, ss58_address: str, title: str = if not identity: err_console.print( - f"[red]Existing identity not found[/red]" + f"[blue]Existing identity not found[/blue]" f" for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" f" on {subtensor}" ) return {} - + table = create_identity_table(title) table.add_row("Address", ss58_address) for key, value in identity.items(): From 4197af3b3ef2de7d9289a8ae0511abc4efa7f7b1 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 8 Nov 2024 10:14:36 -0800 Subject: [PATCH 139/157] Adds root register call for sn 0 --- bittensor_cli/src/commands/subnets.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index c4ab7cbb..09f1d3f2 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -21,6 +21,7 @@ register_extrinsic, burned_register_extrinsic, ) +from bittensor_cli.src.bittensor.extrinsics.root import root_register_extrinsic from rich.live import Live from bittensor_cli.src.bittensor.minigraph import MiniGraph from bittensor_cli.src.commands.wallets import set_id, get_id @@ -1011,13 +1012,16 @@ async def register( ): return - await burned_register_extrinsic( - subtensor, - wallet=wallet, - netuid=netuid, - prompt=False, - old_balance=balance, - ) + if netuid == 0: + await root_register_extrinsic(subtensor, wallet=wallet) + else: + await burned_register_extrinsic( + subtensor, + wallet=wallet, + netuid=netuid, + prompt=False, + old_balance=balance, + ) # TODO: Confirm emissions, incentive, Dividends are to be fetched from subnet_state or keep NeuronInfo From f621ad84dbb851275d7061e0ee6e9dfc46761015 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 20 Nov 2024 16:42:24 -0800 Subject: [PATCH 140/157] Adds interactive in unstake to select hotkeys/netuids a coldkey is staked to --- bittensor_cli/cli.py | 73 +-- .../bittensor/async_substrate_interface.py | 51 +- .../src/bittensor/subtensor_interface.py | 45 +- bittensor_cli/src/commands/stake/stake.py | 470 ++++++++++++------ bittensor_cli/src/commands/subnets.py | 12 +- 5 files changed, 446 insertions(+), 205 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ba6932b5..f7ae8f20 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2679,6 +2679,12 @@ def stake_remove( "hotkeys in `--include-hotkeys`.", ), prompt: bool = Options.prompt, + interactive: bool = typer.Option( + False, + "--interactive", + "-i", + help="Enter interactive mode for unstaking.", + ), quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2694,33 +2700,38 @@ def stake_remove( [blue bold]Note[/blue bold]: This command is for users who wish to reallocate their stake or withdraw them from the network. It allows for flexible management of TAO stake across different neurons (hotkeys) on the network. """ self.verbosity_handler(quiet, verbose) - netuid = get_optional_netuid(netuid, all_netuids) - if all_hotkeys and include_hotkeys: + if interactive and any( + [hotkey_ss58_address, include_hotkeys, exclude_hotkeys, all_hotkeys] + ): err_console.print( - "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag" - "should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`." + "Interactive mode cannot be used with hotkey selection options like --include-hotkeys, --exclude-hotkeys, --all-hotkeys, or --hotkey." ) raise typer.Exit() - if include_hotkeys and exclude_hotkeys: - err_console.print( - "You have specified both including and excluding hotkeys options. Select one or the other." - ) - raise typer.Exit() + if not interactive: + netuid = get_optional_netuid(netuid, all_netuids) + if all_hotkeys and include_hotkeys: + err_console.print( + "You have specified hotkeys to include and also the `--all-hotkeys` flag. The flag" + " should only be used standalone (to use all hotkeys) or with `--exclude-hotkeys`." + ) + raise typer.Exit() - if unstake_all and amount: - err_console.print( - "Cannot specify both a specific amount and 'unstake-all'. Choose one or the other." - ) - raise typer.Exit() + if include_hotkeys and exclude_hotkeys: + err_console.print( + "You have specified both including and excluding hotkeys options. Select one or the other." + ) + raise typer.Exit() - # TODO: We are prompting for amount for each subnet later - confirm this to be removed - # if not unstake_all and not amount and not keep_stake: - # amount = FloatPrompt.ask("[blue bold]Amount to unstake (TAO τ)[/blue bold]") + if unstake_all and amount: + err_console.print( + "Cannot specify both a specific amount and 'unstake-all'. Choose one or the other." + ) + raise typer.Exit() - if unstake_all and not amount: - if not Confirm.ask("Unstake all staked TAO tokens?", default=False): + if amount and amount <= 0: + print_error(f"You entered an incorrect unstake amount: {amount}") raise typer.Exit() if ( @@ -2728,6 +2739,7 @@ def stake_remove( and not hotkey_ss58_address and not all_hotkeys and not include_hotkeys + and not interactive ): if not wallet_name: wallet_name = Prompt.ask( @@ -2735,7 +2747,7 @@ def stake_remove( default=self.config.get("wallet_name") or defaults.wallet.name, ) hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from", default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, ) if is_valid_ss58_address(hotkey_or_ss58): @@ -2753,7 +2765,13 @@ def stake_remove( validate=WV.WALLET_AND_HOTKEY, ) - elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address: + elif ( + all_hotkeys + or include_hotkeys + or exclude_hotkeys + or hotkey_ss58_address + or interactive + ): wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) @@ -2771,8 +2789,8 @@ def stake_remove( included_hotkeys = parse_to_list( include_hotkeys, str, - "Hotkeys must be a comma-separated list of ss58s, e.g., `--include-hotkeys 5Grw....,5Grw....`.", - is_ss58=True, + "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--include-hotkeys hk1,hk2`.", + is_ss58=False, ) else: included_hotkeys = [] @@ -2781,16 +2799,12 @@ def stake_remove( excluded_hotkeys = parse_to_list( exclude_hotkeys, str, - "Hotkeys must be a comma-separated list of ss58s, e.g., `--exclude-hotkeys 5Grw....,5Grw....`.", - is_ss58=True, + "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--exclude-hotkeys hk3,hk4`.", + is_ss58=False, ) else: excluded_hotkeys = [] - if amount and amount <= 0: - print_error(f"You entered an incorrect unstake amount: {amount}") - raise typer.Exit() - return self._run_command( stake.unstake( wallet, @@ -2804,6 +2818,7 @@ def stake_remove( keep_stake, unstake_all, prompt, + interactive, ) ) diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index ebe0e9f5..bd6bbb98 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -4,7 +4,7 @@ from collections import defaultdict from dataclasses import dataclass from hashlib import blake2b -from typing import Optional, Any, Union, Callable, Awaitable, cast, Iterable +from typing import Optional, Any, Union, Callable, Awaitable, cast from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15 from async_property import async_property @@ -460,6 +460,9 @@ def __init__(self, chain, runtime_config, metadata, type_registry): self.runtime_config = runtime_config self.metadata = metadata + def __str__(self): + return f"Runtime: {self.chain} | {self.config}" + @property def implements_scaleinfo(self) -> bool: """ @@ -897,9 +900,10 @@ async def init_runtime( async def get_runtime(block_hash, block_id) -> Runtime: # Check if runtime state already set to current block - if (block_hash and block_hash == self.last_block_hash) or ( - block_id and block_id == self.block_id - ): + if ( + (block_hash and block_hash == self.last_block_hash) + or (block_id and block_id == self.block_id) + ) and self.metadata is not None: return Runtime( self.chain, self.runtime_config, @@ -945,9 +949,11 @@ async def get_runtime(block_hash, block_id) -> Runtime: raise SubstrateRequestException( f"No runtime information for block '{block_hash}'" ) - # Check if runtime state already set to current block - if runtime_info.get("specVersion") == self.runtime_version: + if ( + runtime_info.get("specVersion") == self.runtime_version + and self.metadata is not None + ): return Runtime( self.chain, self.runtime_config, @@ -962,16 +968,19 @@ async def get_runtime(block_hash, block_id) -> Runtime: if self.runtime_version in self.__metadata_cache: # Get metadata from cache # self.debug_message('Retrieved metadata for {} from memory'.format(self.runtime_version)) - self.metadata = self.__metadata_cache[self.runtime_version] + metadata = self.metadata = self.__metadata_cache[ + self.runtime_version + ] else: - self.metadata = await self.get_block_metadata( + metadata = self.metadata = await self.get_block_metadata( block_hash=runtime_block_hash, decode=True ) # self.debug_message('Retrieved metadata for {} from Substrate node'.format(self.runtime_version)) # Update metadata cache self.__metadata_cache[self.runtime_version] = self.metadata - + else: + metadata = self.metadata # Update type registry self.reload_type_registry(use_remote_preset=False, auto_discover=True) @@ -1012,7 +1021,10 @@ async def get_runtime(block_hash, block_id) -> Runtime: if block_id and block_hash: raise ValueError("Cannot provide block_hash and block_id at the same time") - if not (runtime := self.runtime_cache.retrieve(block_id, block_hash)): + if ( + not (runtime := self.runtime_cache.retrieve(block_id, block_hash)) + or runtime.metadata is None + ): runtime = await get_runtime(block_hash, block_id) self.runtime_cache.add_item(block_id, block_hash, runtime) return runtime @@ -1123,7 +1135,7 @@ async def create_storage_key( ------- StorageKey """ - await self.init_runtime(block_hash=block_hash) + runtime = await self.init_runtime(block_hash=block_hash) return StorageKey.create_from_storage_function( pallet, @@ -1603,6 +1615,7 @@ async def _make_rpc_request( result_handler: Optional[ResultHandler] = None, ) -> RequestManager.RequestResults: request_manager = RequestManager(payloads) + subscription_added = False async with self.ws as ws: @@ -1785,23 +1798,13 @@ async def query_multiple( # By allowing for specifying the block hash, users, if they have multiple query types they want # to do, can simply query the block hash first, and then pass multiple query_subtensor calls # into an asyncio.gather, with the specified block hash - if len(params) != len(set(params)): - raise SubstrateRequestException( - "You are attempting to query multiple values, but you have duplicates." - ) - block_hash = await self._get_current_block_hash(block_hash, reuse_block_hash) if block_hash: self.last_block_hash = block_hash runtime = await self.init_runtime(block_hash=block_hash) preprocessed: tuple[Preprocessed] = await asyncio.gather( *[ - self._preprocess( - [x] if not isinstance(x, Iterable) else list(x), - block_hash, - storage_function, - module, - ) + self._preprocess([x], block_hash, storage_function, module) for x in params ] ) @@ -1809,10 +1812,10 @@ async def query_multiple( self.make_payload(item.queryable, item.method, item.params) for item in preprocessed ] - # These will always be the same throughout the preprocessed list, so we just grab the first one value_scale_type = preprocessed[0].value_scale_type storage_item = preprocessed[0].storage_item + responses = await self._make_rpc_request( all_info, value_scale_type, storage_item, runtime ) @@ -2281,7 +2284,7 @@ async def get_metadata_constant(self, module_name, constant_name, block_hash=Non MetadataModuleConstants """ - # await self.init_runtime(block_hash=block_hash) + await self.init_runtime(block_hash=block_hash) for module in self.metadata.pallets: if module_name == module.name and module.constants: diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 6fbc4c14..87f3c250 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -845,9 +845,9 @@ async def query_all_identities( return {} all_identities = { - decode_account_id(ss58_address[0]): decode_hex_identity(identity) - for ss58_address, identity in identities - } + decode_account_id(ss58_address[0]): decode_hex_identity(identity) + for ss58_address, identity in identities + } return all_identities async def query_identity( @@ -888,6 +888,45 @@ async def query_identity( except TypeError: return {} + async def fetch_coldkey_hotkey_identities( + self, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[str, dict]: + """ + Builds a dictionary containing coldkeys and hotkeys with their associated identities and relationships. + :param block_hash: The hash of the blockchain block number for the query. + :param reuse_block: Whether to reuse the last-used blockchain block hash. + :return: Dict with 'coldkeys' and 'hotkeys' as keys. + """ + + coldkey_identities = await self.query_all_identities() + query = await self.substrate.query_multiple( + params=[(ss58) for ss58, _ in coldkey_identities.items()], + module="SubtensorModule", + storage_function="OwnedHotkeys", + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + + identities = {"coldkeys": {}, "hotkeys": {}} + for coldkey_ss58, hotkeys in query.items(): + coldkey_identity = coldkey_identities.get(coldkey_ss58) + hotkeys = [decode_account_id(hotkey[0]) for hotkey in hotkeys or []] + + identities["coldkeys"][coldkey_ss58] = { + "identity": coldkey_identity, + "hotkeys": hotkeys, + } + + for hotkey_ss58 in hotkeys: + identities["hotkeys"][hotkey_ss58] = { + "coldkey": coldkey_ss58, + "identity": coldkey_identity, + } + + return identities + async def weights( self, netuid: int, block_hash: Optional[str] = None ) -> list[tuple[int, list[tuple[int, int]]]]: diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index aefa7f52..b5f1c01e 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -17,19 +17,14 @@ from bittensor_cli.src.bittensor.utils import ( # TODO add back in caching console, - create_table, err_console, print_verbose, print_error, - get_coldkey_wallets_for_path, get_hotkey_wallets_for_wallet, is_valid_ss58_address, - get_metadata_table, - update_metadata_table, - render_tree, u16_normalized_float, - validate_coldkey_presence, format_error_message, + group_subnets, ) if TYPE_CHECKING: @@ -1157,7 +1152,207 @@ async def send_extrinsic( await asyncio.sleep(tx_rate_limit_blocks * 12) # 12 sec per block -# TODO: Decouple stuff from here. Works but is a mess +async def unstake_selection( + subtensor: "SubtensorInterface", + wallet: Wallet, + dynamic_info, + identities, + old_identities, +): + stake_infos = await subtensor.get_stake_info_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address + ) + + if not stake_infos: + print_error("You have no stakes to unstake.") + return + + hotkey_stakes = {} + for stake_info in stake_infos: + hotkey_ss58 = stake_info.hotkey_ss58 + netuid_ = stake_info.netuid + stake_amount = stake_info.stake + if stake_amount.tao > 0: + hotkey_stakes.setdefault(hotkey_ss58, {})[netuid_] = stake_amount + + hotkeys_info = [] + for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()): + identity = identities["hotkeys"].get(hotkey_ss58) or old_identities.get( + hotkey_ss58 + ) + hotkey_name = "~" + if identity: + hotkey_name = identity.get("identity", {}).get("name", "") or identity.get( + "display", "~" + ) + # TODO: Add wallet ids here. + + hotkeys_info.append( + { + "index": idx, + "identity": hotkey_name, + "netuids": list(netuid_stakes.keys()), + "hotkey_ss58": hotkey_ss58, + } + ) + + # Display existing hotkeys, id, and staked netuids. + table = Table( + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) + table.add_column("Index", justify="right") + table.add_column("Identity", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]) + table.add_column("Netuids", style=COLOR_PALETTE["GENERAL"]["NETUID"]) + table.add_column("Hotkey Address", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]) + + for hotkey_info in hotkeys_info: + index = str(hotkey_info["index"]) + identity = hotkey_info["identity"] + netuids = group_subnets([n for n in hotkey_info["netuids"]]) + hotkey_ss58 = hotkey_info["hotkey_ss58"] + table.add_row(index, identity, netuids, hotkey_ss58) + + console.print("\n", table) + + # Prompt to select hotkey to unstake. + hotkey_options = [str(hotkey_info["index"]) for hotkey_info in hotkeys_info] + hotkey_idx = Prompt.ask( + "\nEnter the index of the hotkey you want to unstake from", + choices=hotkey_options, + ) + selected_hotkey_info = hotkeys_info[int(hotkey_idx)] + selected_hotkey_ss58 = selected_hotkey_info["hotkey_ss58"] + selected_hotkey_name = selected_hotkey_info["identity"] + netuid_stakes = hotkey_stakes[selected_hotkey_ss58] + + # Display hotkey's staked netuids with amount. + table = Table( + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Stakes for hotkey \n[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey_name}\n{selected_hotkey_ss58}\n", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) + table.add_column("Subnet", justify="right") + table.add_column("Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]) + table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"]) + table.add_column( + f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", + style=COLOR_PALETTE["POOLS"]["RATE"], + justify="left", + ) + + for netuid_, stake_amount in netuid_stakes.items(): + symbol = dynamic_info[netuid_].symbol + rate = f"{dynamic_info[netuid_].price.tao:.4f} τ/{symbol}" + table.add_row(str(netuid_), symbol, str(stake_amount), rate) + console.print("\n", table) + + # Ask which netuids to unstake from for the selected hotkey. + while True: + netuid_input = Prompt.ask( + "\nEnter the netuids of the [blue]subnets to unstake[/blue] from (comma-separated), or '[blue]all[/blue]' to unstake from all", + default="all", + ) + + if netuid_input.lower() == "all": + selected_netuids = list(netuid_stakes.keys()) + break + else: + try: + netuid_list = [int(n.strip()) for n in netuid_input.split(",")] + invalid_netuids = [n for n in netuid_list if n not in netuid_stakes] + if invalid_netuids: + print_error( + f"The following netuids are invalid or not available: {', '.join(map(str, invalid_netuids))}. Please try again." + ) + else: + selected_netuids = netuid_list + break + except ValueError: + print_error( + "Please enter valid netuids (numbers), separated by commas, or 'all'." + ) + + hotkeys_to_unstake_from = [] + for netuid_ in selected_netuids: + hotkeys_to_unstake_from.append( + (selected_hotkey_name, selected_hotkey_ss58, netuid_) + ) + return hotkeys_to_unstake_from + + +def ask_unstake_amount( + current_stake_balance: Balance, + netuid: int, + staking_address_name: str, + staking_address_ss58: str, + interactive: bool, +) -> Optional[Balance]: + """Prompt the user to decide the amount to unstake.""" + while True: + response = Prompt.ask( + f"Unstake all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" + f" from [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{staking_address_name if staking_address_name else staking_address_ss58}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" + f" on netuid: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]? [y/n/q]", + choices=["y", "n", "q"], + default="n" if interactive else "y", + show_choices=True, + ).lower() + + if response == "q": + return None # Quit + + elif response == "y": + return current_stake_balance + + elif response == "n": + while True: + amount_input = Prompt.ask( + f"Enter amount to unstake in [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" + f" from subnet: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" + f" (Max: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}])" + ) + if amount_input.lower() == "q": + return None # Quit + + try: + amount_value = float(amount_input) + if amount_value <= 0: + console.print("[red]Amount must be greater than zero.[/red]") + continue # Re-prompt + + amount_to_unstake = Balance.from_tao(amount_value) + amount_to_unstake.set_unit(netuid) + if amount_to_unstake > current_stake_balance: + console.print( + f"[red]Amount exceeds current stake balance of {current_stake_balance}.[/red]" + ) + continue # Re-prompt + + return amount_to_unstake + + except ValueError: + console.print( + "[red]Invalid input. Please enter a numeric value or 'q' to quit.[/red]" + ) + + else: + console.print("[red]Invalid input. Please enter 'y', 'n', or 'q'.[/red]") + + async def unstake( wallet: Wallet, subtensor: "SubtensorInterface", @@ -1170,61 +1365,84 @@ async def unstake( keep_stake: float, unstake_all: bool, prompt: bool, + interactive: bool = False, ): - """Unstake token of amount from hotkey(s).""" - netuids = ( - [int(netuid)] - if netuid is not None - else await subtensor.get_all_subnet_netuids() - ) - # Get the hotkey_names (if any) and the hotkey_ss58s. - hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = [] - if hotkey_ss58_address: - print_verbose(f"Unstaking from ss58 ({hotkey_ss58_address})") - # Unstake from specific hotkey. - hotkeys_to_unstake_from = [(None, hotkey_ss58_address)] - elif all_hotkeys: - print_verbose("Unstaking from all hotkeys") - # Unstake from all hotkeys. - all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) - # Exclude hotkeys that are specified. - hotkeys_to_unstake_from = [ - (wallet.hotkey_str, wallet.hotkey.ss58_address) - for wallet in all_hotkeys_ - if wallet.hotkey_str not in exclude_hotkeys - ] - elif include_hotkeys: - print_verbose("Unstaking from included hotkeys") - # Unstake from specific hotkeys. - for hotkey_ss58_or_hotkey_name in include_hotkeys: - if is_valid_ss58_address(hotkey_ss58_or_hotkey_name): - # If the hotkey is a valid ss58 address, we add it to the list. - hotkeys_to_unstake_from.append((None, hotkey_ss58_or_hotkey_name)) - else: - # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. - # We then get the hotkey from the wallet and add it to the list. - wallet_ = Wallet( - name=wallet.name, - path=wallet.path, - hotkey=hotkey_ss58_or_hotkey_name, - ) - hotkeys_to_unstake_from.append( - (wallet_.hotkey_str, wallet_.hotkey.ss58_address) - ) + """Unstake tokens from hotkey(s).""" + with console.status( + f"Retrieving subnet data & identities from {subtensor.network}...", + spinner="earth", + ): + all_sn_dynamic_info_, ck_hk_identities, old_identities = await asyncio.gather( + subtensor.get_all_subnet_dynamic_info(), + subtensor.fetch_coldkey_hotkey_identities(), + subtensor.get_delegate_identities(), + ) + all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} + + if interactive: + hotkeys_to_unstake_from = await unstake_selection( + subtensor, wallet, all_sn_dynamic_info, ck_hk_identities, old_identities + ) + if not hotkeys_to_unstake_from: + console.print("[red]No unstake operations to perform.[/red]") + return False + netuids = list({netuid for _, _, netuid in hotkeys_to_unstake_from}) + else: - # Only cli.config.wallet.hotkey is specified. - # So we unstake from that single hotkey. - print_verbose( - f"Unstaking from wallet: ({wallet.name}) from hotkey: ({wallet.hotkey_str})" + netuids = ( + [int(netuid)] + if netuid is not None + else await subtensor.get_all_subnet_netuids() ) - assert wallet.hotkey is not None - hotkeys_to_unstake_from = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] + + # Get the hotkey_names (if any) and the hotkey_ss58s. + hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = [] + if hotkey_ss58_address: + print_verbose(f"Unstaking from ss58 ({hotkey_ss58_address})") + # Unstake from specific hotkey. + hotkeys_to_unstake_from = [(None, hotkey_ss58_address)] + elif all_hotkeys: + print_verbose("Unstaking from all hotkeys") + # Unstake from all hotkeys. + all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) + # Exclude hotkeys that are specified. + hotkeys_to_unstake_from = [ + (wallet.hotkey_str, wallet.hotkey.ss58_address) + for wallet in all_hotkeys_ + if wallet.hotkey_str not in exclude_hotkeys + ] + elif include_hotkeys: + print_verbose("Unstaking from included hotkeys") + # Unstake from specific hotkeys. + for hotkey_identifier in include_hotkeys: + if is_valid_ss58_address(hotkey_identifier): + # If the hotkey is a valid ss58 address, we add it to the list. + hotkeys_to_unstake_from.append((None, hotkey_identifier)) + else: + # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. + # We then get the hotkey from the wallet and add it to the list. + wallet_ = Wallet( + name=wallet.name, + path=wallet.path, + hotkey=hotkey_identifier, + ) + hotkeys_to_unstake_from.append( + (wallet_.hotkey_str, wallet_.hotkey.ss58_address) + ) + else: + # Only cli.config.wallet.hotkey is specified. + # So we unstake from that single hotkey. + print_verbose( + f"Unstaking from wallet: ({wallet.name}) from hotkey: ({wallet.hotkey_str})" + ) + assert wallet.hotkey is not None + hotkeys_to_unstake_from = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] with console.status( - f"Retrieving stake and subnet data from {subtensor.network}...", + f"Retrieving stake data from {subtensor.network}...", spinner="earth", ): - # Prepare lists to store unstaking data per subnet + # Prepare unstaking transactions unstake_operations = [] total_received_amount = Balance.from_tao(0) current_wallet_balance: Balance = ( @@ -1232,42 +1450,49 @@ async def unstake( )[wallet.coldkeypub.ss58_address] max_float_slippage = 0 - # Fetch dynamic info and stake balances + # Fetch stake balances chain_head = await subtensor.substrate.get_chain_head() - dynamic_info_list, stake_all_netuids = await asyncio.gather( - asyncio.gather( - *[subtensor.get_subnet_dynamic_info(x, chain_head) for x in netuids] - ), - subtensor.multi_get_stake_for_coldkey_and_hotkey_on_netuid( + stake_in_netuids = ( + await subtensor.multi_get_stake_for_coldkey_and_hotkey_on_netuid( hotkey_ss58s=[hk[1] for hk in hotkeys_to_unstake_from], coldkey_ss58=wallet.coldkeypub.ss58_address, netuids=netuids, block_hash=chain_head, - ), + ) + ) + + # Flag to check if user wants to quit + skip_remaining_subnets = False + if hotkeys_to_unstake_from: + console.print( + "[dark_sea_green3]Tip: Enter 'q' any time to skip further entries and process existing unstakes" ) - dynamic_info_all_netuids = dict(zip(netuids, dynamic_info_list)) # Iterate over hotkeys and netuids to collect unstake operations for hotkey in hotkeys_to_unstake_from: - staking_address_name, staking_address_ss58 = hotkey + if skip_remaining_subnets: + break + + if interactive: + staking_address_name, staking_address_ss58, netuid = hotkey + netuids_to_process = [netuid] + else: + staking_address_name, staking_address_ss58 = hotkey + netuids_to_process = netuids + initial_amount = amount - skip_remaining_subnets = False # Flag to check if user wants to quit - if len(netuids) > 1: + if len(netuids_to_process) > 1: console.print( "[dark_sea_green3]Tip: Enter 'q' any time to stop going over remaining subnets and process current unstakes.\n" ) - for netuid in netuids: + for netuid in netuids_to_process: if skip_remaining_subnets: break # Exit the loop over netuids - dynamic_info = dynamic_info_all_netuids.get(netuid) - if dynamic_info is None: - console.print(f"[red]Subnet: {netuid} does not exist.[/red]") - continue # Skip to the next subnet - - current_stake_balance = stake_all_netuids[staking_address_ss58][netuid] + dynamic_info = all_sn_dynamic_info.get(netuid) + current_stake_balance = stake_in_netuids[staking_address_ss58][netuid] if current_stake_balance.tao == 0: continue # No stake to unstake @@ -1277,70 +1502,18 @@ async def unstake( elif unstake_all: amount_to_unstake_as_balance = current_stake_balance else: - # Prompt the user for each subnet - while True: - response = Prompt.ask( - f"Unstake all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]" - f" from [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{staking_address_name if staking_address_name else staking_address_ss58}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] on netuid: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]? [y/n/q]", - choices=["y", "n", "q"], - default="n", - show_choices=True, - ).lower() - - if response.lower() == "q": - skip_remaining_subnets = True - break # Exit the loop over netuids - - elif response.lower() == "y": - amount_to_unstake_as_balance = current_stake_balance - break # Proceed with unstake operation - elif response.lower() == "n": - while True: - amount_input = Prompt.ask( - f"Enter amount to unstake in [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] from subnet: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] (Max: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}])" - ) - if amount_input.lower() == "q": - skip_remaining_subnets = True - break # Exit the loop over netuids - - try: - amount_value = float(amount_input) - if amount_value < 0 or amount_value == 0: - console.print( - "[red]Amount cannot be negative or zero.[/red]" - ) - continue # Re-prompt - - amount_to_unstake_as_balance = Balance.from_tao( - amount_value - ) - amount_to_unstake_as_balance.set_unit(netuid) - if amount_to_unstake_as_balance > current_stake_balance: - console.print( - f"[red]Amount exceeds current stake balance of {current_stake_balance}.[/red]" - ) - continue # Re-prompt - - break # Valid amount entered - - except ValueError: - console.print( - "[red]Invalid input. Please enter a numeric value or 'q' to quit.[/red]" - ) - - if skip_remaining_subnets: - break # Exit the loop over netuids - - break # Exit the response loop - - else: - console.print( - "[red]Invalid input. Please enter 'y', 'n', or 'q'.[/red]" - ) - continue # Re-prompt - - if skip_remaining_subnets: - break # Exit the loop over netuids + amount_to_unstake_as_balance = ask_unstake_amount( + current_stake_balance, + netuid, + staking_address_name + if staking_address_name + else staking_address_ss58, + staking_address_ss58, + interactive, + ) + if amount_to_unstake_as_balance is None: + skip_remaining_subnets = True + break # Check enough stake to remove. amount_to_unstake_as_balance.set_unit(netuid) @@ -1349,7 +1522,7 @@ async def unstake( f"[red]Not enough stake to remove[/red]:\n Stake balance: [dark_orange]{current_stake_balance}[/dark_orange]" f" < Unstaking amount: [dark_orange]{amount_to_unstake_as_balance}[/dark_orange]" ) - continue # Skip to the next subnet + continue # Skip to the next subnet - useful when single amount is specified for all subnets received_amount, slippage = dynamic_info.alpha_to_tao_with_slippage( amount_to_unstake_as_balance @@ -1389,7 +1562,9 @@ async def unstake( # Build the table table = Table( - title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Unstaking to: \nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Unstaking to: \nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]," + f" Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" + f"Network: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1441,7 +1616,8 @@ async def unstake( console.print( "\n" f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n" - f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_float_slippage} %[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n" + f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_float_slippage} %[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]," + " this may result in a loss of funds.\n" f"-------------------------------------------------------------------------------------------------------------------\n" ) @@ -1451,7 +1627,7 @@ async def unstake( The table displays information about the stake remove operation you are about to perform. The columns are as follows: - [bold white]Netuid[/bold white]: The netuid of the subnet you are unstaking from. - - [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are unstaking from. + - [bold white]Hotkey[/bold white]: The ss58 address or identity of the hotkey you are unstaking from. - [bold white]Amount[/bold white]: The stake amount you are removing from this key. - [bold white]Rate[/bold white]: The rate of exchange between TAO and the subnet's stake. - [bold white]Received[/bold white]: The amount of free balance TAO you will receive on this subnet after slippage. @@ -1469,7 +1645,7 @@ async def unstake( err_console.print("Error decrypting coldkey (possibly incorrect password)") return False - with console.status(f"\n:satellite: Performing unstaking operations...") as status: + with console.status("\n:satellite: Performing unstaking operations...") as status: for op in unstake_operations: netuid_i = op["netuid"] staking_address_name = op["hotkey_name"] @@ -1522,7 +1698,8 @@ async def unstake( f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" ) console.print( - f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}" + f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f" Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}" ) console.print( f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed." @@ -2060,3 +2237,10 @@ async def move_stake( f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_destination_stake_balance}" ) return + + +async def fetch_coldkey_stake(subtensor: "SubtensorInterface", wallet: Wallet): + sub_stakes = await subtensor.get_stake_info_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address + ) + return sub_stakes diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 09f1d3f2..7fa2e40c 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -580,7 +580,7 @@ async def show_root(): return table = Table( - title=f"[underline dark_orange]Root Network[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", + title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]Root Network\n[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Network: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -593,27 +593,27 @@ async def show_root(): table.add_column("[bold white]Position", style="white", justify="center") table.add_column( f"[bold white] TAO ({Balance.get_unit(0)})", - style="medium_purple", + style=COLOR_PALETTE["STAKE"]["TAO"], justify="center", ) table.add_column( f"[bold white]Stake ({Balance.get_unit(0)})", - style="rgb(42,161,152)", + style=COLOR_PALETTE["POOLS"]["ALPHA_IN"], justify="center", ) table.add_column( f"[bold white]Emission ({Balance.get_unit(0)}/block)", - style="tan", + style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="center", ) table.add_column( "[bold white]Hotkey", - style="plum2", + style=COLOR_PALETTE["GENERAL"]["HOTKEY"], justify="center", ) table.add_column( "[bold white]Coldkey", - style="plum2", + style=COLOR_PALETTE["GENERAL"]["COLDKEY"], justify="center", ) From a382b757b6b477efbcc74dc1e0b669cd7cc4884f Mon Sep 17 00:00:00 2001 From: Watchmaker Date: Thu, 21 Nov 2024 14:21:04 -0800 Subject: [PATCH 141/157] Modifying descriptions and links in stake and subnets dot py files --- bittensor_cli/src/commands/stake/stake.py | 18 +++++++++--------- bittensor_cli/src/commands/subnets.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index b5f1c01e..38fec1be 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1946,35 +1946,35 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ), ( "[bold tan]Stake (α)[/bold tan]", - "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "The stake amount this hotkey holds in the subnet, expressed in subnet's alpha token currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#staking[/blue].", ), ( "[bold tan]TAO Reserves (τ_in)[/bold tan]", - 'Units of TAO in the TAO Reserves reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Reserves (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + 'Number of TAO in the TAO reserves of the pool for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].', ), ( "[bold tan]Alpha Reserves (α_in)[/bold tan]", - 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Reserves(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + "Number of subnet alpha tokens in the alpha reserves of the pool for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].", ), ( "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Reserves(τ_in) / Alpha Reserves (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Exchange rate between TAO and subnet dTAO token. Calculated as the reserve ratio: (TAO Pool (τ_in) / Alpha Pool (α_in)). Note that the terms relative price, alpha token price, alpha price are the same as exchange rate. This rate can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#rate-%CF%84_in%CE%B1_in[/blue].", ), ( "[bold tan]Alpha out (α_out)[/bold tan]", - "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Total stake in the subnet, expressed in subnet's alpha token currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#stake-%CE%B1_out-or-alpha-out-%CE%B1_out", ), ( "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", - 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#local-weight-or-tao-equiv-%CF%84_in-x-%CE%B1%CE%B1_out[/blue].', ), ( "[bold tan]Exchange Value (α x τ/α)[/bold tan]", - "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].", ), ( "[bold tan]Swap (α → τ)[/bold tan]", - "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].", ), ( "[bold tan]Registered[/bold tan]", @@ -1982,7 +1982,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): ), ( "[bold tan]Emission (α/block)[/bold tan]", - "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#emissions[/blue].", ), ] diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 7fa2e40c..0d31ce08 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -521,31 +521,31 @@ def format_cell(value, previous_value, unit="", precision=4): ), ( "[bold tan]Emission (τ)[/bold tan]", - "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Shows how the one τ per block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's alpha token price by the sum of all alpha prices across all the subnets. This fraction of TAO is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#emissions[/blue].", ), ( "[bold tan]STAKE (α_out)[/bold tan]", - "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "Total stake in the subnet, expressed in the subnet's alpha token currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#stake-%CE%B1_out-or-alpha-out-%CE%B1_out[/blue].", ), ( - "[bold tan]TAO Pool (τ_in)[/bold tan]", - 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + "[bold tan]TAO Reserves (τ_in)[/bold tan]", + 'Number of TAO in the TAO reserves of the pool for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].', ), ( - "[bold tan]Alpha Pool (α_in)[/bold tan]", - "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "[bold tan]Alpha Reserves (α_in)[/bold tan]", + "Number of subnet alpha tokens in the alpha reserves of the pool for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].", ), ( "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + 'Exchange rate between TAO and subnet dTAO token. Calculated as the reserve ratio: (TAO Pool (τ_in) / Alpha Pool (α_in)). Note that the terms relative price, alpha token price, alpha price are the same as exchange rate. This rate can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#rate-%CF%84_in%CE%B1_in[/blue].', ), ( "[bold tan]Tempo (k/n)[/bold tan]", - 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#tempo-kn[/blue].', ), ( "[bold tan]Local weight coeff (γ)[/bold tan]", - "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + "This is the global_split coefficient. It is a multiplication factor between 0 and 1, and it controls the balance between a validator's normalized global and local weights. In effect, the global_split parameter controls the balance between the validator hotkey's local and global influence. This is a subnet parameter. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#global-split[/blue].", ), ] From e7d12c877b326241ba721ac7d990634a6ed7d275 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 18 Dec 2024 12:24:29 -0800 Subject: [PATCH 142/157] Bumps websocket to 13.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2e891bcf..25066875 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,6 @@ rich~=13.7 scalecodec==1.2.11 substrate-interface~=1.7.9 typer~=0.12 -websockets>=12.0 +websockets==13.0 bittensor-wallet>=2.0.2 bt-decode==0.2.0a0 \ No newline at end of file From 4c555333a82de99038cbbe0e60d607ae18d8ff01 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 18 Dec 2024 15:47:52 -0800 Subject: [PATCH 143/157] lock_cost -> burn_cost --- bittensor_cli/cli.py | 14 +++++++------- bittensor_cli/src/commands/subnets.py | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index f7ae8f20..69f3b8c5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -725,8 +725,8 @@ def __init__(self): "list", rich_help_panel=HELP_PANELS["SUBNETS"]["INFO"] )(self.subnets_list) self.subnets_app.command( - "lock-cost", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"] - )(self.subnets_lock_cost) + "burn-cost", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"] + )(self.subnets_burn_cost) self.subnets_app.command( "create", rich_help_panel=HELP_PANELS["SUBNETS"]["CREATION"] )(self.subnets_create) @@ -787,7 +787,7 @@ def __init__(self): )(self.wallet_get_id) # Subnets - self.subnets_app.command("lock_cost", hidden=True)(self.subnets_lock_cost) + self.subnets_app.command("burn_cost", hidden=True)(self.subnets_burn_cost) self.subnets_app.command("pow_register", hidden=True)(self.subnets_pow_register) # Sudo @@ -3480,23 +3480,23 @@ def subnets_show( ) ) - def subnets_lock_cost( + def subnets_burn_cost( self, network: Optional[list[str]] = Options.network, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): """ - Shows the required amount of TAO to be locked for creating a new subnet, i.e., cost of registering a new subnet. + Shows the required amount of TAO to be recycled for creating a new subnet, i.e., cost of registering a new subnet. The current implementation anneals the cost of creating a subnet over a period of two days. If the displayed cost is unappealing to you, check back in a day or two to see if it has decreased to a more affordable level. EXAMPLE - [green]$[/green] btcli subnets lock_cost + [green]$[/green] btcli subnets burn_cost """ self.verbosity_handler(quiet, verbose) - return self._run_command(subnets.lock_cost(self.initialize_chain(network))) + return self._run_command(subnets.burn_cost(self.initialize_chain(network))) def subnets_create( self, diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 0d31ce08..5c1f3818 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -94,8 +94,8 @@ async def _find_event_attributes_in_extrinsic_receipt( your_balance_ = await subtensor.get_balance(wallet.coldkeypub.ss58_address) your_balance = your_balance_[wallet.coldkeypub.ss58_address] - print_verbose("Fetching lock_cost") - burn_cost = await lock_cost(subtensor) + print_verbose("Fetching burn_cost") + burn_cost = await burn_cost(subtensor) if burn_cost > your_balance: err_console.print( f"Your balance of: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}[{COLOR_PALETTE['POOLS']['TAO']}] is not enough to pay the subnet lock cost of: " @@ -824,7 +824,7 @@ async def show_subnet(netuid_: int): await show_subnet(netuid) -async def lock_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: +async def burn_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: """View locking cost of creating a new subnetwork""" with console.status( f":satellite:Retrieving lock cost from {subtensor.network}...", @@ -836,13 +836,13 @@ async def lock_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: params=[], ) if lc: - lock_cost_ = Balance(lc) + burn_cost_ = Balance(lc) console.print( - f"Subnet lock cost: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{lock_cost_}" + f"Subnet burn cost: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{burn_cost_}" ) - return lock_cost_ + return burn_cost_ else: - err_console.print("Subnet lock cost: [red]Failed to get subnet lock cost[/red]") + err_console.print("Subnet burn cost: [red]Failed to get subnet burn cost[/red]") return None From d0bc140233328e8953fb40b5e0731d4eb12fa068 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 19 Dec 2024 09:39:38 -0800 Subject: [PATCH 144/157] Various improvements and changes --- bittensor_cli/cli.py | 3 +- bittensor_cli/src/__init__.py | 155 ++++++++++++----- bittensor_cli/src/commands/stake/stake.py | 6 +- bittensor_cli/src/commands/subnets.py | 195 ++++++++++++++-------- 4 files changed, 242 insertions(+), 117 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 69f3b8c5..6d112c5f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -598,7 +598,7 @@ def __init__(self): ) # utils app - self.app.add_typer(self.utils_app, name="utils", no_args_is_help=True) + self.app.add_typer(self.utils_app, name="utils", no_args_is_help=True, hidden=True) # config commands self.config_app.command("set")(self.set_config) @@ -3476,6 +3476,7 @@ def subnets_show( subnets.show( subtensor, netuid, + verbose=verbose, prompt=prompt, ) ) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index b3077d8e..b44f65ce 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -553,59 +553,122 @@ class WalletValidationTypes(Enum): COLOR_PALETTE = { "GENERAL": { - "HEADER": "#4196D6", - "LINKS": "#8CB9E9", - "HINT": "#A2E5B8", - "COLDKEY": "#9EF5E4", - "HOTKEY": "#ECC39D", - "SUBHEADING_MAIN": "#7ECFEC", - "SUBHEADING": "#AFEFFF", - "SUBHEADING_EXTRA_1": "#96A3C5", - "SUBHEADING_EXTRA_2": "#6D7BAF", - "CONFIRMATION_Y_N_Q": "#EE8DF8", - "SYMBOL": "#E7CC51", - "BALANCE": "#4F91C6", - "COST": "#53B5A0", - "SUCCESS": "#53B5A0", - "NETUID": "#CBA880", - "NETUID_EXTRA": "#DDD5A9", - "TEMPO": "#67A3A5", + "HEADER": "#4196D6", # Light Blue + "LINKS": "#8CB9E9", # Sky Blue + "HINT": "#A2E5B8", # Mint Green + "COLDKEY": "#9EF5E4", # Aqua + "HOTKEY": "#ECC39D", # Light Orange/Peach + "SUBHEADING_MAIN": "#7ECFEC", # Light Cyan + "SUBHEADING": "#AFEFFF", # Pale Blue + "SUBHEADING_EXTRA_1": "#96A3C5", # Grayish Blue + "SUBHEADING_EXTRA_2": "#6D7BAF", # Slate Blue + "CONFIRMATION_Y_N_Q": "#EE8DF8", # Light Purple/Pink + "SYMBOL": "#E7CC51", # Gold + "BALANCE": "#4F91C6", # Medium Blue + "COST": "#53B5A0", # Teal + "SUCCESS": "#53B5A0", # Teal + "NETUID": "#CBA880", # Tan + "NETUID_EXTRA": "#DDD5A9", # Light Khaki + "TEMPO": "#67A3A5", # Grayish Teal }, "STAKE": { - "STAKE_AMOUNT": "#53B5A0", - "STAKE_ALPHA": "#53B5A0", - "STAKE_SWAP": "#67A3A5", - "TAO": "#4F91C6", - "SLIPPAGE_TEXT": "#C25E7C", - "SLIPPAGE_PERCENT": "#E7B195", - "NOT_REGISTERED": "#EB6A6C", - "EXTRA_1": "#D781BB", + "STAKE_AMOUNT": "#53B5A0", # Teal + "STAKE_ALPHA": "#53B5A0", # Teal + "STAKE_SWAP": "#67A3A5", # Grayish Teal + "TAO": "#4F91C6", # Medium Blue + "SLIPPAGE_TEXT": "#C25E7C", # Rose + "SLIPPAGE_PERCENT": "#E7B195", # Light Coral + "NOT_REGISTERED": "#EB6A6C", # Salmon Red + "EXTRA_1": "#D781BB", # Pink }, "POOLS": { - "TAO": "#4F91C6", - "ALPHA_IN": "#D09FE9", - "ALPHA_OUT": "#AB7CC8", - "RATE": "#F8D384", - "TAO_EQUIV": "#8CB9E9", - "EMISSION": "#F8D384", - "EXTRA_1": "#CAA8FB", - "EXTRA_2": "#806DAF", + "TAO": "#4F91C6", # Medium Blue + "ALPHA_IN": "#D09FE9", # Light Purple + "ALPHA_OUT": "#AB7CC8", # Medium Purple + "RATE": "#F8D384", # Light Orange + "TAO_EQUIV": "#8CB9E9", # Sky Blue + "EMISSION": "#F8D384", # Light Orange + "EXTRA_1": "#CAA8FB", # Lavender + "EXTRA_2": "#806DAF", # Dark Purple }, "GREY": { - "GREY_100": "#F8F9FA", - "GREY_200": "#F1F3F4", - "GREY_300": "#DBDDE1", - "GREY_400": "#BDC1C6", - "GREY_500": "#5F6368", - "GREY_600": "#2E3134", - "GREY_700": "#282A2D", - "GREY_800": "#17181B", - "GREY_900": "#0E1013", - "BLACK": "#000000", + "GREY_100": "#F8F9FA", # Almost White + "GREY_200": "#F1F3F4", # Very Light Grey + "GREY_300": "#DBDDE1", # Light Grey + "GREY_400": "#BDC1C6", # Medium Light Grey + "GREY_500": "#5F6368", # Medium Grey + "GREY_600": "#2E3134", # Medium Dark Grey + "GREY_700": "#282A2D", # Dark Grey + "GREY_800": "#17181B", # Very Dark Grey + "GREY_900": "#0E1013", # Almost Black + "BLACK": "#000000", # Pure Black }, "SUDO": { - "HYPERPARAMETER": "#4F91C6", - "VALUE": "#D09FE9", - "NORMALIZED": "#AB7CC8", + "HYPERPARAMETER": "#4F91C6", # Medium Blue + "VALUE": "#D09FE9", # Light Purple + "NORMALIZED": "#AB7CC8", # Medium Purple }, } + + +SUBNETS = { + 0: "root", + 1: "apex", + 2: "omron", + 3: "templar", + 4: "targon", + 5: "kaito", + 6: "infinite", + 7: "subvortex", + 8: "rpn", + 9: "pretrain", + 10: "sturday", + 11: "dippy", + 12: "horde", + 13: "dataverse", + 14: "palaidn", + 15: "deval", + 16: "bitrads", + 17: "3gen", + 18: "cortex", + 19: "inference", + 20: "bitagent", + 21: "any-any", + 22: "meta", + 23: "social", + 24: "omega", + 25: "protein", + 26: "alchemy", + 27: "compute", + 28: "oracle", + 29: "coldint", + 30: "bet", + 31: "naschain", + 32: "itsai", + 33: "ready", + 34: "mind", + 35: "logic", + 36: "automata", + 37: "tuning", + 38: "distributed", + 39: "edge", + 40: "chunk", + 41: "sportsensor", + 42: "masa", + 43: "graphite", + 44: "score", + 45: "gen42", + 46: "neural", + 47: "condense", + 48: "nextplace", + 49: "automl", + 50: "audio", + 51: "celium", + 52: "dojo", + 53: "frontier", + 54: "docs-insight", + 56: "gradients", + 57: "gaia", + 58: "dippy-speech", + 59: "agent-arena" +} diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 38fec1be..7dabba6c 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1799,8 +1799,10 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): if substake_.is_registered else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered str(Balance.from_tao(per_block_emission).set_unit(netuid)) - if substake_.is_registered - else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block) + # Removing this flag for now, TODO: Confirm correct values are here w.r.t CHKs + + # if substake_.is_registered + # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block) ] ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 5c1f3818..5d6582b5 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -14,7 +14,7 @@ from rich.table import Column, Table from rich import box -from bittensor_cli.src import COLOR_PALETTE +from bittensor_cli.src import COLOR_PALETTE, SUBNETS from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import SubnetState from bittensor_cli.src.bittensor.extrinsics.registration import ( @@ -181,7 +181,7 @@ async def fetch_subnet_data(): ) return subnets, global_weights, identities - def define_table(total_emissions: float, total_rate: float): + def define_table(total_emissions: float, total_rate: float, total_netuids: int): table = Table( title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnets" f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}\n\n", @@ -195,13 +195,13 @@ def define_table(total_emissions: float, total_rate: float): pad_edge=True, ) - table.add_column("[bold white]Netuid", style="grey89", justify="center") + table.add_column("[bold white]Netuid", style="grey89", justify="center", footer=str(total_netuids)) table.add_column( "[bold white]Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"], justify="right", ) - table.add_column("[bold white]Owner", style="cyan", justify="left") + table.add_column("[bold white]Name", style="cyan", justify="left") table.add_column( f"[bold white]Emission ({Balance.get_unit(0)})", style=COLOR_PALETTE["POOLS"]["EMISSION"], @@ -209,9 +209,10 @@ def define_table(total_emissions: float, total_rate: float): footer=f"τ {total_emissions:.4f}", ) table.add_column( - f"[bold white]Stake ({Balance.get_unit(1)}_out)", - style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], + f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", + style="#AB7CC8", justify="left", + footer=f"τ {total_rate:.4f}", ) table.add_column( f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", @@ -224,10 +225,9 @@ def define_table(total_emissions: float, total_rate: float): justify="left", ) table.add_column( - f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", - style=COLOR_PALETTE["POOLS"]["RATE"], + f"[bold white]Stake ({Balance.get_unit(1)}_out)", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="left", - footer=f"τ {total_rate:.4f}", ) table.add_column( "[bold white]Tempo (k/n)", @@ -258,12 +258,12 @@ def create_table(subnets, global_weights, identities): # Prepare cells netuid_cell = str(netuid) symbol_cell = f"{subnet.symbol}" - identity_cell = identity + subnet_name_cell = SUBNETS.get(netuid, "~") emission_cell = f"{emission_tao:,.4f}" - alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" + price_cell = f"{subnet.price.tao:.4f} τ/{symbol}" tao_in_cell = f"{subnet.tao_in.tao:,.4f} τ" alpha_in_cell = f"{subnet.alpha_in.tao:,.4f} {symbol}" - price_cell = f"{subnet.price.tao:.4f} τ/{symbol}" + alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" tempo_cell = f"{subnet.blocks_since_last_step}/{subnet.tempo}" global_weight_cell = ( f"{global_weight:.4f}" if global_weight is not None else "N/A" @@ -271,16 +271,16 @@ def create_table(subnets, global_weights, identities): rows.append( ( - netuid_cell, # Netuid - symbol_cell, # Symbol - identity_cell, # Identity - emission_cell, # Emission (τ) - alpha_out_cell, # Stake α_out - tao_in_cell, # TAO Pool τ_in - alpha_in_cell, # Alpha Pool α_in - price_cell, # Rate τ_in/α_in - tempo_cell, # Tempo k/n - global_weight_cell, # Local weight coeff. (γ) + netuid_cell, # Netuid + symbol_cell, # Symbol + subnet_name_cell, # Name + emission_cell, # Emission (τ) + price_cell, # Rate τ_in/α_in + tao_in_cell, # TAO Pool τ_in + alpha_in_cell, # Alpha Pool α_in + alpha_out_cell, # Stake α_out + tempo_cell, # Tempo k/n + global_weight_cell,# Local weight coeff. (γ) ) ) @@ -290,11 +290,12 @@ def create_table(subnets, global_weights, identities): total_rate = sum( float(subnet.price.tao) for subnet in subnets if subnet.netuid != 0 ) - table = define_table(total_emissions, total_rate) + total_netuids = len(subnets) + table = define_table(total_emissions, total_rate, total_netuids) - # Sort rows by emission, keeping the root subnet in the first position + # Sort rows by stake, keeping the root subnet in the first position sorted_rows = [rows[0]] + sorted( - rows[1:], key=lambda x: float(str(x[3]).replace(",", "")), reverse=True + rows[1:], key=lambda x: float(str(x[7]).split()[0].replace(",", "")), reverse=True ) for row in sorted_rows: @@ -349,16 +350,13 @@ def format_cell(value, previous_value, unit="", precision=4): # Prepare cells netuid_cell = str(netuid) - identity_cell = identity symbol_cell = f"{subnet.symbol}" + subnet_name_cell = SUBNETS.get(netuid, "~") emission_cell = format_cell( emission_tao, prev.get("emission_tao"), unit="", precision=4 ) - alpha_out_cell = format_cell( - subnet.alpha_out.tao, - prev.get("alpha_out"), - unit=f" {symbol}", - precision=5, + price_cell = format_cell( + subnet.price.tao, prev.get("price"), unit=f" τ/{symbol}", precision=4 ) tao_in_cell = format_cell( subnet.tao_in.tao, prev.get("tao_in"), unit=" τ", precision=4 @@ -369,8 +367,11 @@ def format_cell(value, previous_value, unit="", precision=4): unit=f" {symbol}", precision=4, ) - price_cell = format_cell( - subnet.price.tao, prev.get("price"), unit=f" τ/{symbol}", precision=4 + alpha_out_cell = format_cell( + subnet.alpha_out.tao, + prev.get("alpha_out"), + unit=f" {symbol}", + precision=5, ) # Tempo cell @@ -422,16 +423,16 @@ def format_cell(value, previous_value, unit="", precision=4): rows.append( ( - netuid_cell, # Netuid - symbol_cell, # Symbol - identity_cell, # Identity - emission_cell, # Emission (τ) - alpha_out_cell, # Stake α_out - tao_in_cell, # TAO Pool τ_in - alpha_in_cell, # Alpha Pool α_in - price_cell, # Rate τ_in/α_in - tempo_cell, # Tempo k/n - global_weight_cell, # Local weight coeff. (γ) + netuid_cell, # Netuid + symbol_cell, # Symbol + subnet_name_cell, # Name + emission_cell, # Emission (τ) + price_cell, # Rate τ_in/α_in + tao_in_cell, # TAO Pool τ_in + alpha_in_cell, # Alpha Pool α_in + alpha_out_cell, # Stake α_out + tempo_cell, # Tempo k/n + global_weight_cell,# Local weight coeff. (γ) ) ) @@ -441,13 +442,12 @@ def format_cell(value, previous_value, unit="", precision=4): total_rate = sum( float(subnet.price.tao) for subnet in subnets if subnet.netuid != 0 ) - table = define_table(total_emissions, total_rate) + total_netuids = len(subnets) + table = define_table(total_emissions, total_rate, total_netuids) - # Sort rows by emission, keeping the first subnet in the first position + # Sort rows by stake, keeping the first subnet in the first position sorted_rows = [rows[0]] + sorted( - rows[1:], - key=lambda x: float(str(x[3]).split()[0].replace(",", "")), - reverse=True, + rows[1:], key=lambda x: float(str(x[7]).split()[0].replace(",", "")), reverse=True ) for row in sorted_rows: table.add_row(*row) @@ -466,21 +466,43 @@ def format_cell(value, previous_value, unit="", precision=4): ) progress_task = progress.add_task("Updating:", total=refresh_interval) + previous_block = None + current_block = None previous_data = None + with Live(console=console, screen=True, auto_refresh=True) as live: try: while True: - subnets, global_weights, identities = await fetch_subnet_data() + subnets = await subtensor.get_all_subnet_dynamic_info() + global_weights, identities, block_number = await asyncio.gather( + subtensor.get_global_weights([subnet.netuid for subnet in subnets]), + subtensor.query_all_identities(), + subtensor.substrate.get_block_number(None) + ) + + # Update block numbers + previous_block = current_block + current_block = block_number + new_blocks = "N/A" if previous_block is None else str(current_block - previous_block) + table, current_data = create_table_live( subnets, global_weights, identities, previous_data ) previous_data = current_data progress.reset(progress_task) start_time = asyncio.get_event_loop().time() - message = "\nLive view active. Press [bold red]Ctrl + C[/bold red] to exit" - live_render = Group(table, progress, message) + block_info = ( + f"Previous: [dark_sea_green]{previous_block if previous_block else 'N/A'}[/dark_sea_green] " + f"Current: [dark_sea_green]{current_block}[/dark_sea_green] " + f"New: [dark_sea_green]{new_blocks}[/dark_sea_green] " + ) + + message = f"Live view active. Press [bold red]Ctrl + C[/bold red] to exit\n{block_info}" + + live_render = Group(message, progress, table) live.update(live_render) + while not progress.finished: await asyncio.sleep(0.1) elapsed = asyncio.get_event_loop().time() - start_time @@ -556,15 +578,20 @@ def format_cell(value, previous_value, unit="", precision=4): console.print(description_table) -async def show(subtensor: "SubtensorInterface", netuid: int, prompt: bool = True): +async def show(subtensor: "SubtensorInterface", netuid: int, verbose: bool = False, prompt: bool = True): async def show_root(): all_subnets = await subtensor.get_all_subnet_dynamic_info() - hex_bytes_result = await subtensor.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", - method="get_subnet_state", - params=[0], + hex_bytes_result, identities, old_identities = await asyncio.gather( + subtensor.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_state", + params=[0], + ), + subtensor.query_all_identities(), + subtensor.get_delegate_identities(), ) + if (bytes_result := hex_bytes_result) is None: err_console.print("The root subnet does not exist") return @@ -616,6 +643,11 @@ async def show_root(): style=COLOR_PALETTE["GENERAL"]["COLDKEY"], justify="center", ) + table.add_column( + "[bold white]Identity", + style=COLOR_PALETTE["GENERAL"]["SYMBOL"], + justify="left", + ) sorted_hotkeys = sorted( enumerate(root_state.hotkeys), @@ -632,13 +664,20 @@ async def show_root(): total_emission_per_block += subnet.alpha_to_tao( Balance.from_rao(emission_on_subnet) ) + + # Get identity for this validator + coldkey_identity = identities.get(root_state.coldkeys[idx], {}).get("name", "") + hotkey_identity = old_identities.get(root_state.hotkeys[idx]) + validator_identity = coldkey_identity if coldkey_identity else (hotkey_identity.display if hotkey_identity else "") + table.add_row( str((pos + 1)), str(root_state.global_stake[idx]), str(root_state.local_stake[idx]), f"{(total_emission_per_block)}", - f"{root_state.hotkeys[idx]}", - f"{root_state.coldkeys[idx]}", + f"{root_state.hotkeys[idx][:6]}" if not verbose else f"{root_state.hotkeys[idx]}", + f"{root_state.coldkeys[idx][:6]}" if not verbose else f"{root_state.coldkeys[idx]}", + validator_identity, ) # Print the table @@ -658,12 +697,19 @@ async def show_root(): ) async def show_subnet(netuid_: int): - subnet_info = await subtensor.get_subnet_dynamic_info(netuid_) - hex_bytes_result = await subtensor.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", - method="get_subnet_state", - params=[netuid_], + subnet_info, hex_bytes_result, identities, old_identities = await asyncio.gather( + subtensor.get_subnet_dynamic_info(netuid_), + subtensor.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_state", + params=[netuid_], + ), + subtensor.query_all_identities(), + subtensor.get_delegate_identities(), ) + owner_ss58 = subnet_info.owner if subnet_info else "" + owner_identity = identities.get(owner_ss58, {}).get("name", old_identities.get(owner_ss58).display if old_identities.get(owner_ss58) else "") + if (bytes_result := hex_bytes_result) is None: err_console.print(f"Subnet {netuid_} does not exist") return @@ -683,7 +729,8 @@ async def show_subnet(netuid_: int): # Define table properties table = Table( - title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnet [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnet [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}" + f"{': ' + SUBNETS.get(netuid_, '') if SUBNETS.get(netuid_) else ''}" f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", show_footer=True, show_edge=False, @@ -715,6 +762,12 @@ async def show_subnet(netuid_: int): tao_sum += subnet_state.global_stake[idx] stake_sum += subnet_state.local_stake[idx] stake_weight_sum += subnet_state.stake_weight[idx] + + # Get identity for this uid + coldkey_identity = identities.get(subnet_state.coldkeys[idx], {}).get("name", "") + hotkey_identity = old_identities.get(subnet_state.hotkeys[idx]) + uid_identity = coldkey_identity if coldkey_identity else (hotkey_identity.display if hotkey_identity else "~") + rows.append( ( str(idx), # UID @@ -726,8 +779,9 @@ async def show_subnet(netuid_: int): str(subnet_state.incentives[idx]), # Incentive # f"{Balance.from_tao(hotkey_block_emission).set_unit(netuid_).tao:.5f}", # Emissions relative f"{Balance.from_tao(subnet_state.emission[idx].tao).set_unit(netuid_).tao:.5f} {subnet_info.symbol}", # Emissions - f"{subnet_state.hotkeys[idx]}", # Hotkey - f"{subnet_state.coldkeys[idx]}", # Coldkey + f"{subnet_state.hotkeys[idx][:6]}" if not verbose else f"{subnet_state.hotkeys[idx]}", # Hotkey + f"{subnet_state.coldkeys[idx][:6]}" if not verbose else f"{subnet_state.coldkeys[idx]}", # Coldkey + uid_identity, # Identity ) ) # Add columns to the table @@ -789,6 +843,12 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="center", ) + table.add_column( + "Identity", + style=COLOR_PALETTE["GENERAL"]["SYMBOL"], + no_wrap=True, + justify="left", + ) for row in rows: table.add_row(*row) @@ -797,8 +857,7 @@ async def show_subnet(netuid_: int): console.print(table) console.print("\n") console.print( - f"Subnet: {netuid_}:\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" - f" Total Locked: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{subnet_info.total_locked}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f"Subnet: {netuid_}:\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" f" Owner Locked: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{subnet_info.owner_locked}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" ) console.print( From 1f70cf5b421f165568b4dff6088fa6f4e24756a9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 19 Dec 2024 17:01:42 -0800 Subject: [PATCH 145/157] s list changes --- bittensor_cli/src/commands/subnets.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 5d6582b5..eb0cee47 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -202,18 +202,18 @@ def define_table(total_emissions: float, total_rate: float, total_netuids: int): justify="right", ) table.add_column("[bold white]Name", style="cyan", justify="left") - table.add_column( - f"[bold white]Emission ({Balance.get_unit(0)})", - style=COLOR_PALETTE["POOLS"]["EMISSION"], - justify="left", - footer=f"τ {total_emissions:.4f}", - ) table.add_column( f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", style="#AB7CC8", justify="left", footer=f"τ {total_rate:.4f}", ) + table.add_column( + f"[bold white]Emission ({Balance.get_unit(0)})", + style=COLOR_PALETTE["POOLS"]["EMISSION"], + justify="left", + footer=f"τ {total_emissions:.4f}", + ) table.add_column( f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", style=COLOR_PALETTE["STAKE"]["TAO"], @@ -274,8 +274,8 @@ def create_table(subnets, global_weights, identities): netuid_cell, # Netuid symbol_cell, # Symbol subnet_name_cell, # Name - emission_cell, # Emission (τ) price_cell, # Rate τ_in/α_in + emission_cell, # Emission (τ) tao_in_cell, # TAO Pool τ_in alpha_in_cell, # Alpha Pool α_in alpha_out_cell, # Stake α_out @@ -426,8 +426,8 @@ def format_cell(value, previous_value, unit="", precision=4): netuid_cell, # Netuid symbol_cell, # Symbol subnet_name_cell, # Name - emission_cell, # Emission (τ) price_cell, # Rate τ_in/α_in + emission_cell, # Emission (τ) tao_in_cell, # TAO Pool τ_in alpha_in_cell, # Alpha Pool α_in alpha_out_cell, # Stake α_out @@ -495,7 +495,7 @@ def format_cell(value, previous_value, unit="", precision=4): block_info = ( f"Previous: [dark_sea_green]{previous_block if previous_block else 'N/A'}[/dark_sea_green] " f"Current: [dark_sea_green]{current_block}[/dark_sea_green] " - f"New: [dark_sea_green]{new_blocks}[/dark_sea_green] " + f"Diff: [dark_sea_green]{new_blocks}[/dark_sea_green] " ) message = f"Live view active. Press [bold red]Ctrl + C[/bold red] to exit\n{block_info}" From b352dd735772a4df998756e737908ed2e46f5142 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 19 Dec 2024 18:08:23 -0800 Subject: [PATCH 146/157] s show changes --- bittensor_cli/src/commands/subnets.py | 40 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index eb0cee47..a21f07d4 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -581,6 +581,7 @@ def format_cell(value, previous_value, unit="", precision=4): async def show(subtensor: "SubtensorInterface", netuid: int, verbose: bool = False, prompt: bool = True): async def show_root(): all_subnets = await subtensor.get_all_subnet_dynamic_info() + root_info = all_subnets[0] hex_bytes_result, identities, old_identities = await asyncio.gather( subtensor.query_runtime_api( @@ -682,6 +683,17 @@ async def show_root(): # Print the table console.print(table) + console.print("\n") + + console.print( + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Root Network (Subnet 0)[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.price.tao:.4f} τ/{root_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]0 τ[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.tao_in.tao:,.4f} τ[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.alpha_in.tao:,.4f} {root_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.alpha_out.tao:,.5f} {root_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.blocks_since_last_step}/{root_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + ) console.print( """ Description: @@ -752,6 +764,7 @@ async def show_subnet(netuid_: int): stake_sum = Balance(0) relative_emissions_sum = 0 stake_weight_sum = 0 + for idx, hk in enumerate(subnet_state.hotkeys): hotkey_block_emission = ( subnet_state.emission[idx].tao / emission_sum @@ -784,7 +797,15 @@ async def show_subnet(netuid_: int): uid_identity, # Identity ) ) - # Add columns to the table + + # Sort rows by stake + sorted_rows = sorted( + rows, + key=lambda x: float(str(x[2]).split()[0].replace(",", "")), + reverse=True + ) + + # Add columns to the table table.add_column("UID", style="grey89", no_wrap=True, justify="center") table.add_column( f"TAO({Balance.get_unit(0)})", @@ -849,16 +870,26 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="left", ) - for row in rows: + for row in sorted_rows: table.add_row(*row) # Print the table console.print("\n\n") console.print(table) console.print("\n") + + subnet_name = SUBNETS.get(netuid_, '') + subnet_name_display = f": {subnet_name}" if subnet_name else "" + console.print( - f"Subnet: {netuid_}:\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" - f" Owner Locked: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{subnet_info.owner_locked}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Subnet {netuid_}{subnet_name_display}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" + f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.price.tao:.4f} τ/{subnet_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.emission.tao:,.4f} τ[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{subnet_info.tao_in.tao:,.4f} τ[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{subnet_info.alpha_in.tao:,.4f} {subnet_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.alpha_out.tao:,.5f} {subnet_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.blocks_since_last_step}/{subnet_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" ) console.print( """ @@ -877,6 +908,7 @@ async def show_subnet(netuid_: int): """ ) + if netuid == 0: await show_root() else: From 60ba785ee52c71d6374d85e729d0a39a9689c8f9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 20 Dec 2024 11:36:26 -0800 Subject: [PATCH 147/157] Enhancements: wallet, stake, s show --- bittensor_cli/cli.py | 57 ++++++++++++++----- .../src/bittensor/subtensor_interface.py | 4 +- bittensor_cli/src/commands/stake/stake.py | 24 ++------ bittensor_cli/src/commands/subnets.py | 8 +-- bittensor_cli/src/commands/wallets.py | 5 +- 5 files changed, 57 insertions(+), 41 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 6d112c5f..d698039c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2116,7 +2116,7 @@ def wallet_balance( """ self.verbosity_handler(quiet, verbose) - + wallet = None if ss58_addresses: valid_ss58s = [ ss58 for ss58 in set(ss58_addresses) if is_valid_ss58_address(ss58) @@ -2127,20 +2127,44 @@ def wallet_balance( print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.") if valid_ss58s: - wallet = None ss58_addresses = valid_ss58s else: raise typer.Exit() else: - ask_for = [WO.PATH] if all_balances else [WO.NAME, WO.PATH] - validate = WV.NONE if all_balances else WV.WALLET - wallet = self.wallet_ask( - wallet_name, - wallet_path, - wallet_hotkey, - ask_for=ask_for, - validate=validate, - ) + if wallet_name: + coldkey_or_ss58 = wallet_name + else: + coldkey_or_ss58 = Prompt.ask( + "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 addresses[/blue] (comma-separated)", + default=self.config.get("wallet_name") or defaults.wallet.name, + ) + # Split by comma and strip whitespace + coldkey_or_ss58_list = [x.strip() for x in coldkey_or_ss58.split(",")] + + # Check if any entry is a valid SS58 address + if any(is_valid_ss58_address(x) for x in coldkey_or_ss58_list): + valid_ss58s = [ + ss58 for ss58 in coldkey_or_ss58_list if is_valid_ss58_address(ss58) + ] + invalid_ss58s = set(coldkey_or_ss58_list) - set(valid_ss58s) + for invalid_ss58 in invalid_ss58s: + print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.") + + if valid_ss58s: + ss58_addresses = valid_ss58s + else: + raise typer.Exit() + else: + wallet_name = coldkey_or_ss58_list[0] if coldkey_or_ss58_list else wallet_name + ask_for = [WO.PATH] if all_balances else [WO.NAME, WO.PATH] + validate = WV.NONE if all_balances else WV.WALLET + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=ask_for, + validate=validate, + ) subtensor = self.initialize_chain(network) return self._run_command( wallets.wallet_balance(wallet, subtensor, all_balances, ss58_addresses) @@ -2430,10 +2454,13 @@ def stake_list( print_error("You entered an invalid ss58 address") raise typer.Exit() else: - coldkey_or_ss58 = Prompt.ask( - "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]", - default=self.config.get("wallet_name") or defaults.wallet.name, - ) + if wallet_name: + coldkey_or_ss58 = wallet_name + else: + coldkey_or_ss58 = Prompt.ask( + "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 address[/blue]", + default=self.config.get("wallet_name") or defaults.wallet.name, + ) if is_valid_ss58_address(coldkey_or_ss58): coldkey_ss58 = coldkey_or_ss58 else: diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 87f3c250..7bd01d42 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -901,6 +901,9 @@ async def fetch_coldkey_hotkey_identities( """ coldkey_identities = await self.query_all_identities() + identities = {"coldkeys": {}, "hotkeys": {}} + if not coldkey_identities: + return identities query = await self.substrate.query_multiple( params=[(ss58) for ss58, _ in coldkey_identities.items()], module="SubtensorModule", @@ -909,7 +912,6 @@ async def fetch_coldkey_hotkey_identities( reuse_block_hash=reuse_block, ) - identities = {"coldkeys": {}, "hotkeys": {}} for coldkey_ss58, hotkeys in query.items(): coldkey_identity = coldkey_identities.get(coldkey_ss58) hotkeys = [decode_account_id(hotkey[0]) for hotkey in hotkeys or []] diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 7dabba6c..cb1a9202 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1788,10 +1788,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): str(netuid), # Number symbol, # Symbol f"{substake_.stake.tao:,.4f} {symbol}", # Stake (a) - f"{pool.tao_in.tao:,.4f} τ", # TAO Reserves (t_in) - f"{pool.alpha_in.tao:,.4f} {symbol}", # Alpha Reserves a_in f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) - f"{pool.alpha_out.tao:,.4f} {symbol}", # Alpha out (a_out) f"{tao_ownership}", # TAO equiv f"{tao_value}", # Exchange Value (α x τ/α) f"{swapped_tao_value} ({slippage_percentage})", # Swap(α) -> τ @@ -1817,7 +1814,11 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): show_lines=False, pad_edge=True, ) - table.add_column("[white]Netuid", footer_style="overline white", style="grey89") + table.add_column("[white]Netuid", + footer=f"{len(rows)}", + footer_style="overline white", + style="grey89" + ) table.add_column( "[white]Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"], @@ -1830,27 +1831,12 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], justify="center", ) - table.add_column( - f"[white]TAO Reserves ({Balance.unit}_in)", - style=COLOR_PALETTE["STAKE"]["TAO"], - justify="right", - ) - table.add_column( - f"[white]Alpha Reserves ({Balance.get_unit(1)}_in)", - style=COLOR_PALETTE["POOLS"]["ALPHA_IN"], - justify="right", - ) table.add_column( f"[white]Rate \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", footer_style="white", style=COLOR_PALETTE["POOLS"]["RATE"], justify="center", ) - table.add_column( - f"[white]Alpha out ({Balance.get_unit(1)}_out)", - style=COLOR_PALETTE["POOLS"]["ALPHA_OUT"], - justify="right", - ) table.add_column( f"[white]TAO equiv \n({Balance.unit}_in x {Balance.get_unit(1)}/{Balance.get_unit(1)}_out)", style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index a21f07d4..9ee22d96 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -95,11 +95,11 @@ async def _find_event_attributes_in_extrinsic_receipt( your_balance = your_balance_[wallet.coldkeypub.ss58_address] print_verbose("Fetching burn_cost") - burn_cost = await burn_cost(subtensor) - if burn_cost > your_balance: + sn_burn_cost = await burn_cost(subtensor) + if sn_burn_cost > your_balance: err_console.print( f"Your balance of: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}[{COLOR_PALETTE['POOLS']['TAO']}] is not enough to pay the subnet lock cost of: " - f"[{COLOR_PALETTE['POOLS']['TAO']}]{burn_cost}[{COLOR_PALETTE['POOLS']['TAO']}]" + f"[{COLOR_PALETTE['POOLS']['TAO']}]{sn_burn_cost}[{COLOR_PALETTE['POOLS']['TAO']}]" ) return False @@ -108,7 +108,7 @@ async def _find_event_attributes_in_extrinsic_receipt( f"Your balance is: [{COLOR_PALETTE['POOLS']['TAO']}]{your_balance}" ) if not Confirm.ask( - f"Do you want to register a subnet for [{COLOR_PALETTE['POOLS']['TAO']}]{burn_cost}?" + f"Do you want to register a subnet for [{COLOR_PALETTE['POOLS']['TAO']}]{sn_burn_cost}?" ): return False diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 5def5405..120e6a05 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -229,8 +229,9 @@ async def wallet_balance( """Retrieves the current balance of the specified wallet""" if ss58_addresses: coldkeys = ss58_addresses - wallet_names = [f"Provided Address {i + 1}" for i in range(len(ss58_addresses))] - + identities = await subtensor.query_all_identities() + wallet_names = [f"{identities.get(coldkey, {'name': f'Provided address {i}'})['name']}" for i, coldkey in enumerate(coldkeys)] + elif not all_balances: if not wallet.coldkeypub_file.exists_on_device(): err_console.print("[bold red]No wallets found.[/bold red]") From c1195a631593a1909364b8a853c9c62449d17e38 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 20 Dec 2024 11:48:34 -0800 Subject: [PATCH 148/157] Fix sn names --- bittensor_cli/src/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index b44f65ce..1627db0c 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -620,7 +620,7 @@ class WalletValidationTypes(Enum): 5: "kaito", 6: "infinite", 7: "subvortex", - 8: "rpn", + 8: "ptn", 9: "pretrain", 10: "sturday", 11: "dippy", @@ -628,7 +628,7 @@ class WalletValidationTypes(Enum): 13: "dataverse", 14: "palaidn", 15: "deval", - 16: "bitrads", + 16: "bitads", 17: "3gen", 18: "cortex", 19: "inference", @@ -670,5 +670,6 @@ class WalletValidationTypes(Enum): 56: "gradients", 57: "gaia", 58: "dippy-speech", - 59: "agent-arena" + 59: "agent-arena", + 61: "red-team", } From 96448c3bb37a3aa4ec11279974e696c92beb6dea Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 20 Dec 2024 16:53:36 -0800 Subject: [PATCH 149/157] Improvements to subnets, stake, sudo --- bittensor_cli/src/commands/stake/stake.py | 4 +- bittensor_cli/src/commands/subnets.py | 52 +++++++++++++---------- bittensor_cli/src/commands/sudo.py | 8 ++-- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index cb1a9202..d58afeb4 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1786,8 +1786,8 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): rows.append( [ str(netuid), # Number - symbol, # Symbol - f"{substake_.stake.tao:,.4f} {symbol}", # Stake (a) + symbol if netuid != 0 else "\u03A4", # Symbol + f"{substake_.stake.tao:,.4f} {symbol}" if netuid != 0 else f"{symbol} {substake_.stake.tao:,.4f}", # Stake (a) f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) f"{tao_ownership}", # TAO equiv f"{tao_value}", # Exchange Value (α x τ/α) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 9ee22d96..71e012f3 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -257,13 +257,13 @@ def create_table(subnets, global_weights, identities): # Prepare cells netuid_cell = str(netuid) - symbol_cell = f"{subnet.symbol}" + symbol_cell = f"{subnet.symbol}" if netuid != 0 else "\u03A4" subnet_name_cell = SUBNETS.get(netuid, "~") - emission_cell = f"{emission_tao:,.4f}" + emission_cell = f"τ {emission_tao:,.4f}" price_cell = f"{subnet.price.tao:.4f} τ/{symbol}" - tao_in_cell = f"{subnet.tao_in.tao:,.4f} τ" - alpha_in_cell = f"{subnet.alpha_in.tao:,.4f} {symbol}" - alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" + tao_in_cell = f"τ {subnet.tao_in.tao:,.4f}" + alpha_in_cell = f"{subnet.alpha_in.tao:,.4f} {symbol}" if netuid != 0 else f"{symbol} {subnet.alpha_in.tao:,.4f}" + alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" if netuid != 0 else f"{symbol} {subnet.alpha_out.tao:,.5f}" tempo_cell = f"{subnet.blocks_since_last_step}/{subnet.tempo}" global_weight_cell = ( f"{global_weight:.4f}" if global_weight is not None else "N/A" @@ -304,22 +304,22 @@ def create_table(subnets, global_weights, identities): # Live mode def create_table_live(subnets, global_weights, identities, previous_data): - def format_cell(value, previous_value, unit="", precision=4): + def format_cell(value, previous_value, unit="", unit_first=False, precision=4): if previous_value is not None: change = value - previous_value - if change > 0: + if change > 0.01: change_text = ( - f" [pale_green3](+{change:.{precision}f}{unit})[/pale_green3]" + f" [pale_green3](+{change:.2f})[/pale_green3]" ) - elif change < 0: + elif change < -0.01: change_text = ( - f" [hot_pink3]({change:.{precision}f}{unit})[/hot_pink3]" + f" [hot_pink3]({change:.2f})[/hot_pink3]" ) else: change_text = "" else: change_text = "" - return f"{value:,.{precision}f}{unit}{change_text}" + return f"{value:,.{precision}f} {unit}{change_text}" if not unit_first else f"{unit} {value:,.{precision}f}{change_text}" rows = [] current_data = {} # To store current values for comparison in the next update @@ -350,27 +350,33 @@ def format_cell(value, previous_value, unit="", precision=4): # Prepare cells netuid_cell = str(netuid) - symbol_cell = f"{subnet.symbol}" + symbol_cell = f"{subnet.symbol}" if netuid != 0 else "\u03A4" subnet_name_cell = SUBNETS.get(netuid, "~") + if netuid is 0: + unit_first = True + else: + unit_first = False emission_cell = format_cell( - emission_tao, prev.get("emission_tao"), unit="", precision=4 + emission_tao, prev.get("emission_tao"), unit="τ", unit_first=True, precision=4 ) price_cell = format_cell( - subnet.price.tao, prev.get("price"), unit=f" τ/{symbol}", precision=4 + subnet.price.tao, prev.get("price"), unit=f"τ/{symbol}", precision=4 ) tao_in_cell = format_cell( - subnet.tao_in.tao, prev.get("tao_in"), unit=" τ", precision=4 + subnet.tao_in.tao, prev.get("tao_in"), unit="τ", unit_first=True, precision=4 ) alpha_in_cell = format_cell( subnet.alpha_in.tao, prev.get("alpha_in"), - unit=f" {symbol}", + unit=f"{symbol}", + unit_first=unit_first, precision=4, ) alpha_out_cell = format_cell( subnet.alpha_out.tao, prev.get("alpha_out"), - unit=f" {symbol}", + unit=f"{symbol}", + unit_first=unit_first, precision=5, ) @@ -688,10 +694,10 @@ async def show_root(): console.print( f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Root Network (Subnet 0)[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.price.tao:.4f} τ/{root_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]0 τ[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.tao_in.tao:,.4f} τ[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" - f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.alpha_in.tao:,.4f} {root_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" - f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.alpha_out.tao:,.5f} {root_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.symbol} 0[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.symbol} {root_info.tao_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.symbol}{root_info.alpha_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.symbol} {root_info.alpha_out.tao:,.5f}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.blocks_since_last_step}/{root_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" ) console.print( @@ -885,8 +891,8 @@ async def show_subnet(netuid_: int): f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Subnet {netuid_}{subnet_name_display}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" f"\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.price.tao:.4f} τ/{subnet_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.emission.tao:,.4f} τ[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{subnet_info.tao_in.tao:,.4f} τ[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]τ {subnet_info.emission.tao:,.4f}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]τ {subnet_info.tao_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{subnet_info.alpha_in.tao:,.4f} {subnet_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.alpha_out.tao:,.5f} {subnet_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.blocks_since_last_step}/{subnet_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 7a6b1132..4a9ee5da 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -8,7 +8,7 @@ from rich.prompt import Confirm from scalecodec import GenericCall -from bittensor_cli.src import HYPERPARAMS, DelegatesDetails, COLOR_PALETTE +from bittensor_cli.src import HYPERPARAMS, DelegatesDetails, COLOR_PALETTE, SUBNETS from bittensor_cli.src.bittensor.chain_data import decode_account_id from bittensor_cli.src.bittensor.utils import ( console, @@ -500,8 +500,10 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int): Column("[white]VALUE", style=COLOR_PALETTE['SUDO']['VALUE']), Column("[white]NORMALIZED", style=COLOR_PALETTE['SUDO']['NORMALIZED']), title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]\nSubnet Hyperparameters\n NETUID: " - f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" - f" - Network: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}" + f"{f' ({SUBNETS.get(netuid)})' if SUBNETS.get(netuid) else ''}" + f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f" - Network: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n", show_footer=True, width=None, pad_edge=False, From f75fa32e3d6879cccb6a017dfa21e45bb89eb207 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 20 Dec 2024 17:17:59 -0800 Subject: [PATCH 150/157] Bumps version --- bittensor_cli/__init__.py | 2 +- bittensor_cli/cli.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/__init__.py b/bittensor_cli/__init__.py index 8fff761c..c3c1da00 100644 --- a/bittensor_cli/__init__.py +++ b/bittensor_cli/__init__.py @@ -18,6 +18,6 @@ from .cli import CLIManager -__version__ = "8.2.0" +__version__ = "8.2.0+rao.1" __all__ = ["CLIManager", "__version__"] diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d698039c..04dcde4e 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -60,7 +60,7 @@ class GitError(Exception): pass -__version__ = "8.2.0" +__version__ = "8.2.0+rao.1" _core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0) From b54a1439921c1125816060006e05f5ee738973d7 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Sun, 22 Dec 2024 08:52:08 -0800 Subject: [PATCH 151/157] Remove warning --- bittensor_cli/src/commands/subnets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 71e012f3..9d5da41d 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -352,7 +352,7 @@ def format_cell(value, previous_value, unit="", unit_first=False, precision=4): netuid_cell = str(netuid) symbol_cell = f"{subnet.symbol}" if netuid != 0 else "\u03A4" subnet_name_cell = SUBNETS.get(netuid, "~") - if netuid is 0: + if netuid == 0: unit_first = True else: unit_first = False From 4b5e4f885a0d25bfba223a227696088b116fbef9 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Sun, 22 Dec 2024 09:28:21 -0800 Subject: [PATCH 152/157] fix --all flag balances --- bittensor_cli/cli.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 04dcde4e..ae8b1ad6 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2117,7 +2117,17 @@ def wallet_balance( """ self.verbosity_handler(quiet, verbose) wallet = None - if ss58_addresses: + if all_balances: + ask_for = [WO.PATH] + validate = WV.NONE + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=ask_for, + validate=validate, + ) + elif ss58_addresses: valid_ss58s = [ ss58 for ss58 in set(ss58_addresses) if is_valid_ss58_address(ss58) ] @@ -2156,8 +2166,8 @@ def wallet_balance( raise typer.Exit() else: wallet_name = coldkey_or_ss58_list[0] if coldkey_or_ss58_list else wallet_name - ask_for = [WO.PATH] if all_balances else [WO.NAME, WO.PATH] - validate = WV.NONE if all_balances else WV.WALLET + ask_for = [WO.NAME, WO.PATH] + validate = WV.WALLET wallet = self.wallet_ask( wallet_name, wallet_path, From 47da2f1b54456f96d752c7e50567817baedd5201 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 23 Dec 2024 14:45:28 -0800 Subject: [PATCH 153/157] Adds delegates to st add --- bittensor_cli/cli.py | 33 +++- bittensor_cli/src/commands/subnets.py | 216 ++++++++++++++++++-------- 2 files changed, 179 insertions(+), 70 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ae8b1ad6..7eaef510 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2573,11 +2573,34 @@ def stake_add( "Enter the [blue]wallet name[/blue]", default=self.config.get("wallet_name") or defaults.wallet.name, ) - hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, - ) - if is_valid_ss58_address(hotkey_or_ss58): + if netuid is not None: + hotkey_or_ss58 = Prompt.ask( + "Enter the [blue]wallet hotkey[/blue] name or [blue]ss58 address[/blue] to stake to [dim](or Press Enter to view delegates)[/dim]", + ) + else: + hotkey_or_ss58 = Prompt.ask( + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to stake to", + default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + ) + + if hotkey_or_ss58 == "": + wallet = self.wallet_ask( + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] + ) + selected_hotkey = self._run_command( + subnets.show( + subtensor=self.initialize_chain(network), + netuid=netuid, + max_rows=12, + prompt=False, + delegate_selection=True + ) + ) + if selected_hotkey is None: + print_error("No delegate selected. Exiting.") + raise typer.Exit() + include_hotkeys = selected_hotkey + elif is_valid_ss58_address(hotkey_or_ss58) : wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 9d5da41d..39050dfa 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -584,7 +584,14 @@ def format_cell(value, previous_value, unit="", unit_first=False, precision=4): console.print(description_table) -async def show(subtensor: "SubtensorInterface", netuid: int, verbose: bool = False, prompt: bool = True): +async def show( + subtensor: "SubtensorInterface", + netuid: int, + max_rows: Optional[int] = None, + delegate_selection: bool = False, + verbose: bool = False, + prompt: bool = True, +) -> Optional[str]: async def show_root(): all_subnets = await subtensor.get_all_subnet_dynamic_info() root_info = all_subnets[0] @@ -624,6 +631,8 @@ async def show_root(): show_lines=False, pad_edge=True, ) + # if delegate_selection: + # table.add_column("#", style="cyan", justify="right") table.add_column("[bold white]Position", style="white", justify="center") table.add_column( f"[bold white] TAO ({Balance.get_unit(0)})", @@ -661,6 +670,8 @@ async def show_root(): key=lambda x: root_state.global_stake[x[0]], reverse=True, ) + sorted_rows = [] + sorted_hks_delegation = [] for pos, (idx, hk) in enumerate(sorted_hotkeys): total_emission_per_block = 0 for netuid_ in range(len(all_subnets)): @@ -677,42 +688,78 @@ async def show_root(): hotkey_identity = old_identities.get(root_state.hotkeys[idx]) validator_identity = coldkey_identity if coldkey_identity else (hotkey_identity.display if hotkey_identity else "") - table.add_row( - str((pos + 1)), - str(root_state.global_stake[idx]), - str(root_state.local_stake[idx]), - f"{(total_emission_per_block)}", - f"{root_state.hotkeys[idx][:6]}" if not verbose else f"{root_state.hotkeys[idx]}", - f"{root_state.coldkeys[idx][:6]}" if not verbose else f"{root_state.coldkeys[idx]}", - validator_identity, + sorted_rows.append( + ( + str((pos + 1)), + str(root_state.global_stake[idx]), + str(root_state.local_stake[idx]), + f"{(total_emission_per_block)}", + f"{root_state.hotkeys[idx][:6]}" if not verbose else f"{root_state.hotkeys[idx]}", + f"{root_state.coldkeys[idx][:6]}" if not verbose else f"{root_state.coldkeys[idx]}", + validator_identity, + ) ) - + sorted_hks_delegation.append(root_state.hotkeys[idx]) + + for pos, row in enumerate(sorted_rows, 1): + table_row = [] + # if delegate_selection: + # table_row.append(str(pos)) + table_row.extend(row) + table.add_row(*table_row) + if delegate_selection and pos == max_rows: + break # Print the table console.print(table) console.print("\n") - console.print( - f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Root Network (Subnet 0)[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" - f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.price.tao:.4f} τ/{root_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.symbol} 0[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.symbol} {root_info.tao_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" - f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.symbol}{root_info.alpha_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" - f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.symbol} {root_info.alpha_out.tao:,.5f}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" - f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.blocks_since_last_step}/{root_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" - ) - console.print( - """ -Description: - The table displays the root subnet participants and their metrics. - The columns are as follows: - - Position: The sorted position of the hotkey by total TAO. - - TAO: The sum of all TAO balances for this hotkey accross all subnets. - - Stake: The stake balance of this hotkey on root (measured in TAO). - - Emission: The emission accrued to this hotkey across all subnets every block measured in TAO. - - Hotkey: The hotkey ss58 address. - - Coldkey: The coldkey ss58 address. -""" - ) + if not delegate_selection: + console.print( + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Root Network (Subnet 0)[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.price.tao:.4f} τ/{root_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{root_info.symbol} 0[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.symbol} {root_info.tao_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{root_info.symbol}{root_info.alpha_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.symbol} {root_info.alpha_out.tao:,.5f}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.blocks_since_last_step}/{root_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + ) + console.print( + """ + Description: + The table displays the root subnet participants and their metrics. + The columns are as follows: + - Position: The sorted position of the hotkey by total TAO. + - TAO: The sum of all TAO balances for this hotkey accross all subnets. + - Stake: The stake balance of this hotkey on root (measured in TAO). + - Emission: The emission accrued to this hotkey across all subnets every block measured in TAO. + - Hotkey: The hotkey ss58 address. + - Coldkey: The coldkey ss58 address. + """ + ) + if delegate_selection: + while True: + selection = Prompt.ask( + "\nEnter the position of the delegate you want to stake to [dim](or press Enter to cancel)[/dim]", + default="" + ) + + if selection == "": + return None + + try: + idx = int(selection) + if 1 <= idx <= max_rows: + selected_hotkey = sorted_hks_delegation[idx - 1] + row_data = sorted_rows[idx - 1] + identity = row_data[6] + identity_str = f" ({identity})" if identity else "" + console.print(f"\nSelected delegate: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey}{identity_str}") + + return selected_hotkey + else: + console.print(f"[red]Invalid selection. Please enter a number between 1 and {max_rows}[/red]") + except ValueError: + console.print("[red]Please enter a valid number[/red]") async def show_subnet(netuid_: int): subnet_info, hex_bytes_result, identities, old_identities = await asyncio.gather( @@ -759,6 +806,11 @@ async def show_subnet(netuid_: int): show_lines=False, pad_edge=True, ) + + # Add index for selection if selecting delegates + if delegate_selection: + table.add_column("#", style="cyan", justify="right") + rows = [] emission_sum = sum( [ @@ -876,50 +928,84 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="left", ) - for row in sorted_rows: - table.add_row(*row) + for pos, row in enumerate(sorted_rows, 1): + table_row = [] + if delegate_selection: + table_row.append(str(pos)) + table_row.extend(row) + table.add_row(*table_row) + if delegate_selection and pos == max_rows: + break # Print the table console.print("\n\n") console.print(table) console.print("\n") - subnet_name = SUBNETS.get(netuid_, '') - subnet_name_display = f": {subnet_name}" if subnet_name else "" + if not delegate_selection: + subnet_name = SUBNETS.get(netuid_, '') + subnet_name_display = f": {subnet_name}" if subnet_name else "" - console.print( - f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Subnet {netuid_}{subnet_name_display}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" - f"\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" - f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.price.tao:.4f} τ/{subnet_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]τ {subnet_info.emission.tao:,.4f}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" - f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]τ {subnet_info.tao_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" - f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{subnet_info.alpha_in.tao:,.4f} {subnet_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" - f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.alpha_out.tao:,.5f} {subnet_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" - f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.blocks_since_last_step}/{subnet_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" - ) - console.print( - """ -Description: - The table displays the subnet participants and their metrics. - The columns are as follows: - - UID: The hotkey index in the subnet. - - TAO: The sum of all TAO balances for this hotkey accross all subnets. - - Stake: The stake balance of this hotkey on this subnet. - - Weight: The stake-weight of this hotkey on this subnet. Computed as an average of the normalized TAO and Stake columns of this subnet. - - Dividends: Validating dividends earned by the hotkey. - - Incentives: Mining incentives earned by the hotkey (always zero in the RAO demo.) - - Emission: The emission accrued to this hokey on this subnet every block (in staking units). - - Hotkey: The hotkey ss58 address. - - Coldkey: The coldkey ss58 address. -""" - ) + console.print( + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Subnet {netuid_}{subnet_name_display}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]" + f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.price.tao:.4f} τ/{subnet_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]τ {subnet_info.emission.tao:,.4f}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]" + f"\n TAO Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]τ {subnet_info.tao_in.tao:,.4f}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{subnet_info.alpha_in.tao:,.4f} {subnet_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]" + f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.alpha_out.tao:,.5f} {subnet_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.blocks_since_last_step}/{subnet_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]" + ) + console.print( + """ + Description: + The table displays the subnet participants and their metrics. + The columns are as follows: + - UID: The hotkey index in the subnet. + - TAO: The sum of all TAO balances for this hotkey accross all subnets. + - Stake: The stake balance of this hotkey on this subnet. + - Weight: The stake-weight of this hotkey on this subnet. Computed as an average of the normalized TAO and Stake columns of this subnet. + - Dividends: Validating dividends earned by the hotkey. + - Incentives: Mining incentives earned by the hotkey (always zero in the RAO demo.) + - Emission: The emission accrued to this hokey on this subnet every block (in staking units). + - Hotkey: The hotkey ss58 address. + - Coldkey: The coldkey ss58 address. + """ + ) + if delegate_selection: + while True: + selection = Prompt.ask( + "\nEnter the number of the delegate you want to stake to [dim](or press Enter to cancel)[/dim]", + default="" + ) + + if selection == "": + return None + + try: + idx = int(selection) + if 1 <= idx <= max_rows: + uid = int(sorted_rows[idx-1][0]) + hotkey = subnet_state.hotkeys[uid] + row_data = sorted_rows[idx-1] + identity = row_data[9] + identity_str = f" ({identity})" if identity else "" + console.print(f"\nSelected delegate: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{hotkey}{identity_str}") + return hotkey + else: + console.print(f"[red]Invalid selection. Please enter a number between 1 and {max_rows}[/red]") + except ValueError: + console.print("[red]Please enter a valid number[/red]") + + return None if netuid == 0: - await show_root() + result = await show_root() + return result else: - await show_subnet(netuid) - + result = await show_subnet(netuid) + return result async def burn_cost(subtensor: "SubtensorInterface") -> Optional[Balance]: """View locking cost of creating a new subnetwork""" From 14688db867132cd92287fc0d28b9f3eb4ee67079 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 23 Dec 2024 18:15:55 -0800 Subject: [PATCH 154/157] Improvement: stake remove --- bittensor_cli/cli.py | 19 +- bittensor_cli/src/commands/stake/stake.py | 222 +++++++++++++++++++--- 2 files changed, 208 insertions(+), 33 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 7eaef510..e358d335 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2769,7 +2769,7 @@ def stake_remove( ) raise typer.Exit() - if not interactive: + if not interactive and not unstake_all: netuid = get_optional_netuid(netuid, all_netuids) if all_hotkeys and include_hotkeys: err_console.print( @@ -2800,6 +2800,7 @@ def stake_remove( and not all_hotkeys and not include_hotkeys and not interactive + and not unstake_all ): if not wallet_name: wallet_name = Prompt.ask( @@ -2807,10 +2808,14 @@ def stake_remove( default=self.config.get("wallet_name") or defaults.wallet.name, ) hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, - ) - if is_valid_ss58_address(hotkey_or_ss58): + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from [dim](or Press Enter to view existing staked hotkeys)[/dim]", + ) + if hotkey_or_ss58 == "": + wallet = self.wallet_ask( + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] + ) + interactive = True + elif is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] @@ -2831,11 +2836,11 @@ def stake_remove( or exclude_hotkeys or hotkey_ss58_address or interactive + or unstake_all ): wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) - else: wallet = self.wallet_ask( wallet_name, @@ -2870,7 +2875,6 @@ def stake_remove( wallet, self.initialize_chain(network), hotkey_ss58_address, - netuid, all_hotkeys, included_hotkeys, excluded_hotkeys, @@ -2879,6 +2883,7 @@ def stake_remove( unstake_all, prompt, interactive, + netuid=netuid, ) ) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index d58afeb4..ebe26b02 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1158,6 +1158,7 @@ async def unstake_selection( dynamic_info, identities, old_identities, + netuid: Optional[int] = None, ): stake_infos = await subtensor.get_stake_info_for_coldkey( coldkey_ss58=wallet.coldkeypub.ss58_address @@ -1169,11 +1170,20 @@ async def unstake_selection( hotkey_stakes = {} for stake_info in stake_infos: + if netuid is not None and stake_info.netuid != netuid: + continue hotkey_ss58 = stake_info.hotkey_ss58 netuid_ = stake_info.netuid stake_amount = stake_info.stake if stake_amount.tao > 0: hotkey_stakes.setdefault(hotkey_ss58, {})[netuid_] = stake_amount + + if not hotkey_stakes: + if netuid is not None: + print_error(f"You have no stakes to unstake in subnet {netuid}.") + else: + print_error("You have no stakes to unstake.") + return hotkeys_info = [] for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()): @@ -1197,8 +1207,9 @@ async def unstake_selection( ) # Display existing hotkeys, id, and staked netuids. + subnet_filter = f" for Subnet {netuid}" if netuid is not None else "" table = Table( - title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes{subnet_filter}\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1258,33 +1269,36 @@ async def unstake_selection( symbol = dynamic_info[netuid_].symbol rate = f"{dynamic_info[netuid_].price.tao:.4f} τ/{symbol}" table.add_row(str(netuid_), symbol, str(stake_amount), rate) - console.print("\n", table) + console.print("\n", table, "\n") # Ask which netuids to unstake from for the selected hotkey. - while True: - netuid_input = Prompt.ask( - "\nEnter the netuids of the [blue]subnets to unstake[/blue] from (comma-separated), or '[blue]all[/blue]' to unstake from all", - default="all", - ) + if netuid is not None: + selected_netuids = [netuid] + else: + while True: + netuid_input = Prompt.ask( + "\nEnter the netuids of the [blue]subnets to unstake[/blue] from (comma-separated), or '[blue]all[/blue]' to unstake from all", + default="all", + ) - if netuid_input.lower() == "all": - selected_netuids = list(netuid_stakes.keys()) - break - else: - try: - netuid_list = [int(n.strip()) for n in netuid_input.split(",")] - invalid_netuids = [n for n in netuid_list if n not in netuid_stakes] - if invalid_netuids: + if netuid_input.lower() == "all": + selected_netuids = list(netuid_stakes.keys()) + break + else: + try: + netuid_list = [int(n.strip()) for n in netuid_input.split(",")] + invalid_netuids = [n for n in netuid_list if n not in netuid_stakes] + if invalid_netuids: + print_error( + f"The following netuids are invalid or not available: {', '.join(map(str, invalid_netuids))}. Please try again." + ) + else: + selected_netuids = netuid_list + break + except ValueError: print_error( - f"The following netuids are invalid or not available: {', '.join(map(str, invalid_netuids))}. Please try again." + "Please enter valid netuids (numbers), separated by commas, or 'all'." ) - else: - selected_netuids = netuid_list - break - except ValueError: - print_error( - "Please enter valid netuids (numbers), separated by commas, or 'all'." - ) hotkeys_to_unstake_from = [] for netuid_ in selected_netuids: @@ -1353,11 +1367,162 @@ def ask_unstake_amount( console.print("[red]Invalid input. Please enter 'y', 'n', or 'q'.[/red]") +async def _unstake_all( + wallet: Wallet, + subtensor: "SubtensorInterface", + prompt: bool = True, +) -> bool: + """Unstakes all stakes from all hotkeys in all subnets.""" + + with console.status( + f"Retrieving stake information & identities from {subtensor.network}...", + spinner="earth", + ): + stake_info, ck_hk_identities, old_identities, all_sn_dynamic_info_, current_wallet_balance = await asyncio.gather( + subtensor.get_stake_info_for_coldkey(wallet.coldkeypub.ss58_address), + subtensor.fetch_coldkey_hotkey_identities(), + subtensor.get_delegate_identities(), + subtensor.get_all_subnet_dynamic_info(), + subtensor.get_balance(wallet.coldkeypub.ss58_address) + ) + + if not stake_info: + console.print("[red]No stakes found to unstake[/red]") + return False + + all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} + + # Calculate total value and slippage for all stakes + total_received_value = Balance(0) + table = Table( + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Unstaking Summary - All Stakes\nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) + table.add_column("Netuid", justify="center", style="grey89") + table.add_column( + "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"] + ) + table.add_column( + f"Current Stake ({Balance.get_unit(1)})", + justify="center", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], + ) + table.add_column( + f"Rate ({Balance.unit}/{Balance.get_unit(1)})", + justify="center", + style=COLOR_PALETTE["POOLS"]["RATE"], + ) + table.add_column( + f"Recieved ({Balance.unit})", + justify="center", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + ) + table.add_column( + "Slippage", + justify="center", + style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] + ) + max_slippage = 0.0 + for stake in stake_info: + if stake.stake.rao == 0: + continue + + dynamic_info = all_sn_dynamic_info.get(stake.netuid) + stake_amount = stake.stake + received_amount, slippage = dynamic_info.alpha_to_tao_with_slippage(stake_amount) + + total_received_value += received_amount + + # Get hotkey identity + identity = ck_hk_identities["hotkeys"].get(stake.hotkey_ss58) or old_identities.get(stake.hotkey_ss58) + hotkey_display = stake.hotkey_ss58 + if identity: + hotkey_name = identity.get("identity", {}).get("name", "") or identity.get("display", "~") + hotkey_display = f"{hotkey_name}" + + if dynamic_info.is_dynamic: + slippage_pct_float = ( + 100 * float(slippage) / float(slippage + received_amount) + if slippage + received_amount != 0 + else 0 + ) + slippage_pct = f"{slippage_pct_float:.4f} %" + else: + slippage_pct_float = 0 + slippage_pct = "[red]N/A[/red]" + + max_slippage = max(max_slippage, slippage_pct_float) + + table.add_row( + str(stake.netuid), + hotkey_display, + str(stake_amount), + str(float(dynamic_info.price)) + + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})", + str(received_amount), + slippage_pct, + ) + console.print(table) + message = "" + if max_slippage > 5: + message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n" + 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" + message += "-------------------------------------------------------------------------------------------------------------------\n" + console.print(message) + + console.print( + f"Expected return after slippage: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{total_received_value}" + ) + + if prompt and not Confirm.ask("\nDo you want to proceed with unstaking everything?"): + return False + + try: + wallet.unlock_coldkey() + except KeyFileError: + err_console.print("Error decrypting coldkey (possibly incorrect password)") + return False + + with console.status(":satellite: Unstaking all stakes..."): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="unstake_all", + call_params={}, + ) + + success, error_message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=True, + wait_for_finalization=False, + ) + + if success: + console.print(":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]") + new_balance_ = await subtensor.get_balance( + wallet.coldkeypub.ss58_address + ) + new_balance = new_balance_[wallet.coldkeypub.ss58_address] + console.print( + f"Balance:\n [blue]{current_wallet_balance[wallet.coldkeypub.ss58_address]}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" + ) + return True + else: + err_console.print(f":cross_mark: [red]Failed to unstake[/red]: {error_message}") + return False + + async def unstake( wallet: Wallet, subtensor: "SubtensorInterface", hotkey_ss58_address: str, - netuid: Optional[int], all_hotkeys: bool, include_hotkeys: list[str], exclude_hotkeys: list[str], @@ -1366,8 +1531,13 @@ async def unstake( unstake_all: bool, prompt: bool, interactive: bool = False, + netuid: Optional[int] = None, ): """Unstake tokens from hotkey(s).""" + + if unstake_all: + return await _unstake_all(wallet, subtensor, prompt) + with console.status( f"Retrieving subnet data & identities from {subtensor.network}...", spinner="earth", @@ -1381,7 +1551,7 @@ async def unstake( if interactive: hotkeys_to_unstake_from = await unstake_selection( - subtensor, wallet, all_sn_dynamic_info, ck_hk_identities, old_identities + subtensor, wallet, all_sn_dynamic_info, ck_hk_identities, old_identities, netuid=netuid ) if not hotkeys_to_unstake_from: console.print("[red]No unstake operations to perform.[/red]") @@ -1537,7 +1707,7 @@ async def unstake( slippage_pct = f"{slippage_pct_float:.4f} %" else: slippage_pct_float = 0 - slippage_pct = f"{slippage_pct_float}%" + slippage_pct = "[red]N/A[/red]" max_float_slippage = max(max_float_slippage, slippage_pct_float) unstake_operations.append( From be875e22ba60e7ed3197eb7558d1c00c62aca503 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 23 Dec 2024 21:31:16 -0800 Subject: [PATCH 155/157] Feat: wallet with uris --- bittensor_cli/cli.py | 53 +++++++++----- bittensor_cli/src/bittensor/utils.py | 11 +++ bittensor_cli/src/commands/wallets.py | 99 +++++++++++++++++++-------- 3 files changed, 118 insertions(+), 45 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e358d335..0e2ae56f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -46,6 +46,7 @@ is_rao_network, get_effective_network, prompt_for_identity, + validate_uri, ) from typing_extensions import Annotated from textwrap import dedent @@ -227,6 +228,12 @@ class Options: "--live", help="Display live view of the table", ) + uri = typer.Option( + None, + "--uri", + help="Create wallet from uri (e.g. 'Alice', 'Bob', 'Charlie', 'Dave', 'Eve')", + callback=validate_uri, + ) def list_prompt(init_var: list, list_type: type, help_text: str) -> list: @@ -598,7 +605,9 @@ def __init__(self): ) # utils app - self.app.add_typer(self.utils_app, name="utils", no_args_is_help=True, hidden=True) + self.app.add_typer( + self.utils_app, name="utils", no_args_is_help=True, hidden=True + ) # config commands self.config_app.command("set")(self.set_config) @@ -1902,6 +1911,7 @@ def wallet_new_hotkey( is_flag=True, flag_value=True, ), + uri: Optional[str] = Options.uri, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -1940,8 +1950,9 @@ def wallet_new_hotkey( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET, ) - n_words = get_n_words(n_words) - return self._run_command(wallets.new_hotkey(wallet, n_words, use_password)) + if not uri: + n_words = get_n_words(n_words) + return self._run_command(wallets.new_hotkey(wallet, n_words, use_password, uri)) def wallet_new_coldkey( self, @@ -1955,6 +1966,7 @@ def wallet_new_coldkey( help="The number of words used in the mnemonic. Options: [12, 15, 18, 21, 24]", ), use_password: Optional[bool] = Options.use_password, + uri: Optional[str] = Options.uri, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -1991,8 +2003,11 @@ def wallet_new_coldkey( ask_for=[WO.NAME, WO.PATH], validate=WV.NONE, ) - n_words = get_n_words(n_words) - return self._run_command(wallets.new_coldkey(wallet, n_words, use_password)) + if not uri: + n_words = get_n_words(n_words) + return self._run_command( + wallets.new_coldkey(wallet, n_words, use_password, uri) + ) def wallet_check_ck_swap( self, @@ -2026,6 +2041,7 @@ def wallet_create_wallet( wallet_hotkey: Optional[str] = Options.wallet_hotkey, n_words: Optional[int] = None, use_password: bool = Options.use_password, + uri: Optional[str] = Options.uri, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2066,12 +2082,14 @@ def wallet_create_wallet( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.NONE, ) - n_words = get_n_words(n_words) + if not uri: + n_words = get_n_words(n_words) return self._run_command( wallets.wallet_create( wallet, n_words, use_password, + uri, ) ) @@ -2148,10 +2166,9 @@ def wallet_balance( "Enter the [blue]wallet name[/blue] or [blue]coldkey ss58 addresses[/blue] (comma-separated)", default=self.config.get("wallet_name") or defaults.wallet.name, ) - # Split by comma and strip whitespace + + # Split and validate ss58 addresses coldkey_or_ss58_list = [x.strip() for x in coldkey_or_ss58.split(",")] - - # Check if any entry is a valid SS58 address if any(is_valid_ss58_address(x) for x in coldkey_or_ss58_list): valid_ss58s = [ ss58 for ss58 in coldkey_or_ss58_list if is_valid_ss58_address(ss58) @@ -2159,13 +2176,15 @@ def wallet_balance( invalid_ss58s = set(coldkey_or_ss58_list) - set(valid_ss58s) for invalid_ss58 in invalid_ss58s: print_error(f"Incorrect ss58 address: {invalid_ss58}. Skipping.") - + if valid_ss58s: ss58_addresses = valid_ss58s else: raise typer.Exit() else: - wallet_name = coldkey_or_ss58_list[0] if coldkey_or_ss58_list else wallet_name + wallet_name = ( + coldkey_or_ss58_list[0] if coldkey_or_ss58_list else wallet_name + ) ask_for = [WO.NAME, WO.PATH] validate = WV.WALLET wallet = self.wallet_ask( @@ -2588,19 +2607,19 @@ def stake_add( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) selected_hotkey = self._run_command( - subnets.show( + subnets.show( subtensor=self.initialize_chain(network), netuid=netuid, max_rows=12, - prompt=False, - delegate_selection=True + prompt=False, + delegate_selection=True, ) ) if selected_hotkey is None: print_error("No delegate selected. Exiting.") raise typer.Exit() include_hotkeys = selected_hotkey - elif is_valid_ss58_address(hotkey_or_ss58) : + elif is_valid_ss58_address(hotkey_or_ss58): wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) @@ -2808,8 +2827,8 @@ def stake_remove( default=self.config.get("wallet_name") or defaults.wallet.name, ) hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from [dim](or Press Enter to view existing staked hotkeys)[/dim]", - ) + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake from [dim](or Press Enter to view existing staked hotkeys)[/dim]", + ) if hotkey_or_ss58 == "": wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 95cf5371..7987db00 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -986,6 +986,17 @@ def validate_netuid(value: int) -> int: return value +def validate_uri(uri: str) -> str: + if not uri: + raise ValueError("URI cannot be empty") + clean_uri = uri.lstrip("/").lower() + if not clean_uri.isalnum(): + raise typer.BadParameter( + f"Invalid URI format: {uri}. URI must contain only alphanumeric characters (e.g. 'alice', 'bob')" + ) + return f"//{clean_uri.capitalize()}" + + def get_effective_network(config, network: Optional[list[str]]) -> str: """ Determines the effective network to be used, considering the network parameter, diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 120e6a05..34e5399a 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -10,7 +10,7 @@ from typing import Any, Collection, Generator, Optional import aiohttp -from bittensor_wallet import Wallet +from bittensor_wallet import Wallet, Keypair from bittensor_wallet.errors import KeyFileError from bittensor_wallet.keyfile import Keyfile from fuzzywuzzy import fuzz @@ -135,14 +135,26 @@ async def new_hotkey( wallet: Wallet, n_words: int, use_password: bool, + uri: Optional[str] = None, ): """Creates a new hotkey under this wallet.""" try: - wallet.create_new_hotkey( - n_words=n_words, - use_password=use_password, - overwrite=False, - ) + if uri: + try: + keypair = Keypair.create_from_uri(uri) + except Exception as e: + print_error(f"Failed to create keypair from URI {uri}: {str(e)}") + wallet.set_hotkey(keypair=keypair, encrypt=use_password) + console.print( + f"[dark_sea_green]Hotkey created from URI: {uri}[/dark_sea_green]" + ) + else: + wallet.create_new_hotkey( + n_words=n_words, + use_password=use_password, + overwrite=False, + ) + console.print(f"[dark_sea_green]Hotkey created[/dark_sea_green]") except KeyFileError: print_error("KeyFileError: File is not writable") @@ -151,14 +163,27 @@ async def new_coldkey( wallet: Wallet, n_words: int, use_password: bool, + uri: Optional[str] = None, ): """Creates a new coldkey under this wallet.""" try: - wallet.create_new_coldkey( - n_words=n_words, - use_password=use_password, - overwrite=False, - ) + if uri: + try: + keypair = Keypair.create_from_uri(uri) + except Exception as e: + print_error(f"Failed to create keypair from URI {uri}: {str(e)}") + wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=False) + wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=False) + console.print( + f"[dark_sea_green]Coldkey created from URI: {uri}[/dark_sea_green]" + ) + else: + wallet.create_new_coldkey( + n_words=n_words, + use_password=use_password, + overwrite=False, + ) + console.print(f"[dark_sea_green]Coldkey created[/dark_sea_green]") except KeyFileError: print_error("KeyFileError: File is not writable") @@ -167,25 +192,40 @@ async def wallet_create( wallet: Wallet, n_words: int = 12, use_password: bool = True, + uri: Optional[str] = None, ): """Creates a new wallet.""" - try: - wallet.create_new_coldkey( - n_words=n_words, - use_password=use_password, - overwrite=False, + if uri: + try: + keypair = Keypair.create_from_uri(uri) + except Exception as e: + print_error(f"Failed to create keypair from URI: {str(e)}") + wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=False) + wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=False) + wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=False) + console.print( + f"[dark_sea_green]Wallet created from URI: {uri}[/dark_sea_green]" ) - except KeyFileError: - print_error("KeyFileError: File is not writable") + else: + try: + wallet.create_new_coldkey( + n_words=n_words, + use_password=use_password, + overwrite=False, + ) + console.print(f"[dark_sea_green]Coldkey created[/dark_sea_green]") + except KeyFileError: + print_error("KeyFileError: File is not writable") - try: - wallet.create_new_hotkey( - n_words=n_words, - use_password=False, - overwrite=False, - ) - except KeyFileError: - print_error("KeyFileError: File is not writable") + try: + wallet.create_new_hotkey( + n_words=n_words, + use_password=False, + overwrite=False, + ) + console.print(f"[dark_sea_green]Hotkey created[/dark_sea_green]") + except KeyFileError: + print_error("KeyFileError: File is not writable") def get_coldkey_wallets_for_path(path: str) -> list[Wallet]: @@ -230,8 +270,11 @@ async def wallet_balance( if ss58_addresses: coldkeys = ss58_addresses identities = await subtensor.query_all_identities() - wallet_names = [f"{identities.get(coldkey, {'name': f'Provided address {i}'})['name']}" for i, coldkey in enumerate(coldkeys)] - + wallet_names = [ + f"{identities.get(coldkey, {'name': f'Provided address {i}'})['name']}" + for i, coldkey in enumerate(coldkeys) + ] + elif not all_balances: if not wallet.coldkeypub_file.exists_on_device(): err_console.print("[bold red]No wallets found.[/bold red]") From a44fa7ca29dd9b100e793e2cada3ed9266d82e3a Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 24 Dec 2024 12:41:00 -0800 Subject: [PATCH 156/157] Adds unstake_all_alpha --- bittensor_cli/cli.py | 17 +++- bittensor_cli/src/commands/stake/stake.py | 115 +++++++++++++++------- 2 files changed, 93 insertions(+), 39 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0e2ae56f..20e030f2 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2724,7 +2724,13 @@ def stake_remove( False, "--unstake-all", "--all", - help="When set, this command unstakes all staked TAO from the specified hotkeys.", + help="When set, this command unstakes all staked TAO + Alpha from the all hotkeys.", + ), + unstake_all_alpha: bool = typer.Option( + False, + "--unstake-all-alpha", + "--all-alpha", + help="When set, this command unstakes all staked Alpha from the all hotkeys.", ), amount: float = typer.Option( 0.0, "--amount", "-a", help="The amount of TAO to unstake." @@ -2788,7 +2794,11 @@ def stake_remove( ) raise typer.Exit() - if not interactive and not unstake_all: + if unstake_all and unstake_all_alpha: + err_console.print("Cannot specify both unstake-all and unstake-all-alpha.") + raise typer.Exit() + + if not interactive and not unstake_all and not unstake_all_alpha: netuid = get_optional_netuid(netuid, all_netuids) if all_hotkeys and include_hotkeys: err_console.print( @@ -2820,6 +2830,7 @@ def stake_remove( and not include_hotkeys and not interactive and not unstake_all + and not unstake_all_alpha ): if not wallet_name: wallet_name = Prompt.ask( @@ -2856,6 +2867,7 @@ def stake_remove( or hotkey_ss58_address or interactive or unstake_all + or unstake_all_alpha ): wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] @@ -2903,6 +2915,7 @@ def stake_remove( prompt, interactive, netuid=netuid, + unstake_all_alpha=unstake_all_alpha, ) ) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index ebe26b02..dc259192 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -1177,7 +1177,7 @@ async def unstake_selection( stake_amount = stake_info.stake if stake_amount.tao > 0: hotkey_stakes.setdefault(hotkey_ss58, {})[netuid_] = stake_amount - + if not hotkey_stakes: if netuid is not None: print_error(f"You have no stakes to unstake in subnet {netuid}.") @@ -1370,22 +1370,32 @@ def ask_unstake_amount( async def _unstake_all( wallet: Wallet, subtensor: "SubtensorInterface", + unstake_all_alpha: bool = False, prompt: bool = True, ) -> bool: """Unstakes all stakes from all hotkeys in all subnets.""" - + with console.status( f"Retrieving stake information & identities from {subtensor.network}...", spinner="earth", ): - stake_info, ck_hk_identities, old_identities, all_sn_dynamic_info_, current_wallet_balance = await asyncio.gather( + ( + stake_info, + ck_hk_identities, + old_identities, + all_sn_dynamic_info_, + current_wallet_balance, + ) = await asyncio.gather( subtensor.get_stake_info_for_coldkey(wallet.coldkeypub.ss58_address), subtensor.fetch_coldkey_hotkey_identities(), subtensor.get_delegate_identities(), subtensor.get_all_subnet_dynamic_info(), - subtensor.get_balance(wallet.coldkeypub.ss58_address) + subtensor.get_balance(wallet.coldkeypub.ss58_address), ) - + + if unstake_all_alpha: + stake_info = [stake for stake in stake_info if stake.netuid != 0] + if not stake_info: console.print("[red]No stakes found to unstake[/red]") return False @@ -1394,8 +1404,13 @@ async def _unstake_all( # Calculate total value and slippage for all stakes total_received_value = Balance(0) + table_title = ( + "Unstaking Summary - All Stakes" + if not unstake_all_alpha + else "Unstaking Summary - All Alpha Stakes" + ) table = Table( - title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Unstaking Summary - All Stakes\nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{table_title}\nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1427,26 +1442,32 @@ async def _unstake_all( table.add_column( "Slippage", justify="center", - style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"] + style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"], ) max_slippage = 0.0 for stake in stake_info: if stake.stake.rao == 0: continue - + dynamic_info = all_sn_dynamic_info.get(stake.netuid) stake_amount = stake.stake - received_amount, slippage = dynamic_info.alpha_to_tao_with_slippage(stake_amount) - + received_amount, slippage = dynamic_info.alpha_to_tao_with_slippage( + stake_amount + ) + total_received_value += received_amount - + # Get hotkey identity - identity = ck_hk_identities["hotkeys"].get(stake.hotkey_ss58) or old_identities.get(stake.hotkey_ss58) + identity = ck_hk_identities["hotkeys"].get( + stake.hotkey_ss58 + ) or old_identities.get(stake.hotkey_ss58) hotkey_display = stake.hotkey_ss58 if identity: - hotkey_name = identity.get("identity", {}).get("name", "") or identity.get("display", "~") + hotkey_name = identity.get("identity", {}).get( + "name", "" + ) or identity.get("display", "~") hotkey_display = f"{hotkey_name}" - + if dynamic_info.is_dynamic: slippage_pct_float = ( 100 * float(slippage) / float(slippage + received_amount) @@ -1457,9 +1478,9 @@ async def _unstake_all( else: slippage_pct_float = 0 slippage_pct = "[red]N/A[/red]" - + max_slippage = max(max_slippage, slippage_pct_float) - + table.add_row( str(stake.netuid), hotkey_display, @@ -1476,12 +1497,14 @@ async def _unstake_all( 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" message += "-------------------------------------------------------------------------------------------------------------------\n" console.print(message) - + console.print( f"Expected return after slippage: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{total_received_value}" ) - - if prompt and not Confirm.ask("\nDo you want to proceed with unstaking everything?"): + + if prompt and not Confirm.ask( + "\nDo you want to proceed with unstaking everything?" + ): return False try: @@ -1490,13 +1513,18 @@ async def _unstake_all( err_console.print("Error decrypting coldkey (possibly incorrect password)") return False - with console.status(":satellite: Unstaking all stakes..."): + console_status = ( + ":satellite: Unstaking all Alpha stakes..." + if unstake_all_alpha + else ":satellite: Unstaking all stakes..." + ) + with console.status(console_status): + call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all" call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="unstake_all", + call_function=call_function, call_params={}, ) - success, error_message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -1505,17 +1533,22 @@ async def _unstake_all( ) if success: - console.print(":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]") - new_balance_ = await subtensor.get_balance( - wallet.coldkeypub.ss58_address + success_message = ( + ":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]" + if not unstake_all_alpha + else ":white_heavy_check_mark: [green]Successfully unstaked all Alpha stakes[/green]" ) + console.print(success_message) + new_balance_ = await subtensor.get_balance(wallet.coldkeypub.ss58_address) new_balance = new_balance_[wallet.coldkeypub.ss58_address] console.print( f"Balance:\n [blue]{current_wallet_balance[wallet.coldkeypub.ss58_address]}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" ) return True else: - err_console.print(f":cross_mark: [red]Failed to unstake[/red]: {error_message}") + err_console.print( + f":cross_mark: [red]Failed to unstake[/red]: {error_message}" + ) return False @@ -1532,11 +1565,12 @@ async def unstake( prompt: bool, interactive: bool = False, netuid: Optional[int] = None, + unstake_all_alpha: bool = False, ): """Unstake tokens from hotkey(s).""" - if unstake_all: - return await _unstake_all(wallet, subtensor, prompt) + if unstake_all or unstake_all_alpha: + return await _unstake_all(wallet, subtensor, unstake_all_alpha, prompt) with console.status( f"Retrieving subnet data & identities from {subtensor.network}...", @@ -1551,7 +1585,12 @@ async def unstake( if interactive: hotkeys_to_unstake_from = await unstake_selection( - subtensor, wallet, all_sn_dynamic_info, ck_hk_identities, old_identities, netuid=netuid + subtensor, + wallet, + all_sn_dynamic_info, + ck_hk_identities, + old_identities, + netuid=netuid, ) if not hotkeys_to_unstake_from: console.print("[red]No unstake operations to perform.[/red]") @@ -1956,8 +1995,10 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): rows.append( [ str(netuid), # Number - symbol if netuid != 0 else "\u03A4", # Symbol - f"{substake_.stake.tao:,.4f} {symbol}" if netuid != 0 else f"{symbol} {substake_.stake.tao:,.4f}", # Stake (a) + symbol if netuid != 0 else "\u03a4", # Symbol + f"{substake_.stake.tao:,.4f} {symbol}" + if netuid != 0 + else f"{symbol} {substake_.stake.tao:,.4f}", # Stake (a) f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) f"{tao_ownership}", # TAO equiv f"{tao_value}", # Exchange Value (α x τ/α) @@ -1965,9 +2006,8 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): "YES" if substake_.is_registered else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered - str(Balance.from_tao(per_block_emission).set_unit(netuid)) + str(Balance.from_tao(per_block_emission).set_unit(netuid)), # Removing this flag for now, TODO: Confirm correct values are here w.r.t CHKs - # if substake_.is_registered # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block) ] @@ -1984,10 +2024,11 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): show_lines=False, pad_edge=True, ) - table.add_column("[white]Netuid", - footer=f"{len(rows)}", - footer_style="overline white", - style="grey89" + table.add_column( + "[white]Netuid", + footer=f"{len(rows)}", + footer_style="overline white", + style="grey89", ) table.add_column( "[white]Symbol", From bc1c260c2bed7dedc6f985e8c337ae0d9d0ba256 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 24 Dec 2024 20:32:57 -0800 Subject: [PATCH 157/157] Live mode: stake list --- bittensor_cli/cli.py | 3 +- bittensor_cli/src/commands/stake/stake.py | 693 ++++++++++++++++------ 2 files changed, 514 insertions(+), 182 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 20e030f2..1781830f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2470,6 +2470,7 @@ def stake_list( "--coldkey.ss58", help="Coldkey address of the wallet", ), + live: bool = Options.live, quiet: bool = Options.quiet, verbose: bool = Options.verbose, # TODO add: all-wallets, reuse_last, html_output @@ -2499,7 +2500,7 @@ def stake_list( ) return self._run_command( - stake.stake_list(wallet, coldkey_ss58, self.initialize_chain(network)) + stake.stake_list(wallet, coldkey_ss58, self.initialize_chain(network), live) ) def stake_add( diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index dc259192..b22a7513 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -9,6 +9,9 @@ from rich.prompt import Confirm, FloatPrompt, Prompt from rich.table import Table from rich import box +from rich.progress import Progress, BarColumn, TextColumn +from rich.console import Console, Group +from rich.live import Live from substrateinterface.exceptions import SubstrateRequestException from bittensor_cli.src import COLOR_PALETTE @@ -1916,29 +1919,116 @@ async def unstake( async def stake_list( - wallet: Wallet, coldkey_ss58: str, subtensor: "SubtensorInterface" + wallet: Wallet, + coldkey_ss58: str, + subtensor: "SubtensorInterface", + live: bool = False, ): coldkey_address = coldkey_ss58 if coldkey_ss58 else wallet.coldkeypub.ss58_address - sub_stakes = ( - await subtensor.get_stake_info_for_coldkeys(coldkey_ss58_list=[coldkey_address]) - )[coldkey_address] - - # Get registered delegates details. - registered_delegate_info = await subtensor.get_delegate_identities() - - # Token pricing info. - dynamic_info = await subtensor.get_all_subnet_dynamic_info() - emission_drain_tempo = int( - await subtensor.substrate.query("SubtensorModule", "HotkeyEmissionTempo") - ) - balance = (await subtensor.get_balance(coldkey_address))[coldkey_address] - - # Iterate over substakes and aggregate them by hotkey. - hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} + async def get_stake_data(block_hash: str = None): + ( + substakes, + registered_delegate_info, + dynamic_info, + emission_drain_tempo, + ) = await asyncio.gather( + subtensor.get_stake_info_for_coldkeys( + coldkey_ss58_list=[coldkey_address], block_hash=block_hash + ), + subtensor.get_delegate_identities(block_hash=block_hash), + subtensor.get_all_subnet_dynamic_info(), + subtensor.substrate.query( + "SubtensorModule", "HotkeyEmissionTempo", block_hash=block_hash + ), + ) + sub_stakes = substakes[coldkey_address] + return ( + sub_stakes, + registered_delegate_info, + dynamic_info, + emission_drain_tempo, + ) + + def define_table( + hotkey_name: str, + rows: list[list[str]], + total_tao_ownership: Balance, + total_tao_value: Balance, + total_swapped_tao_value: Balance, + live: bool = False, + ): + title = f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {hotkey_name}\nNetwork: {subtensor.network}\n\n" + if not live: + title += f"[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n" + table = Table( + title=title, + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) + table.add_column( + "[white]Netuid", + footer=f"{len(rows)}", + footer_style="overline white", + style="grey89", + ) + table.add_column( + "[white]Symbol", + style=COLOR_PALETTE["GENERAL"]["SYMBOL"], + justify="center", + no_wrap=True, + ) + table.add_column( + f"[white]Stake ({Balance.get_unit(1)})", + footer_style="overline white", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], + justify="center", + ) + table.add_column( + f"[white]Rate \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", + footer_style="white", + style=COLOR_PALETTE["POOLS"]["RATE"], + justify="center", + ) + table.add_column( + f"[white]TAO equiv \n({Balance.unit}_in x {Balance.get_unit(1)}/{Balance.get_unit(1)}_out)", + style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], + justify="right", + footer=f"{total_tao_ownership}", + ) + table.add_column( + f"[white]Exchange Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", + footer_style="overline white", + style=COLOR_PALETTE["STAKE"]["TAO"], + justify="right", + footer=f"{total_tao_value}", + ) + table.add_column( + f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", + footer_style="overline white", + style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], + justify="right", + footer=f"{total_swapped_tao_value}", + ) + table.add_column( + "[white]Registered", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], + justify="right", + ) + table.add_column( + f"[white]Emission \n({Balance.get_unit(1)}/block)", + style=COLOR_PALETTE["POOLS"]["EMISSION"], + justify="right", + ) + return table - def table_substakes(hotkey_: str, substakes: list[StakeInfo]): - # Create table structure. + def create_table(hotkey_: str, substakes: list[StakeInfo]): name = ( f"{registered_delegate_info[hotkey_].display} ({hotkey_})" if hotkey_ in registered_delegate_info @@ -1952,6 +2042,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): netuid = substake_.netuid pool = dynamic_info[netuid] symbol = f"{Balance.get_unit(netuid)}\u200e" + # TODO: what is this price var for? price = ( "{:.4f}{}".format( @@ -1960,13 +2051,21 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): if pool.is_dynamic else (f" 1.0000 τ/{symbol} ") ) + + # Alpha value cell alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid) + + # TAO value cell tao_value = pool.alpha_to_tao(alpha_value) total_tao_value += tao_value + + # Swapped TAO value and slippage cell swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage( substake_.stake ) total_swapped_tao_value += swapped_tao_value + + # Slippage percentage cell if pool.is_dynamic: slippage_percentage_ = ( 100 * float(slippage) / float(slippage + swapped_tao_value) @@ -1976,9 +2075,17 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]" else: slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]" + + # TAO locked cell tao_locked = pool.tao_in + + # Issuance cell issuance = pool.alpha_out if pool.is_dynamic else tao_locked + + # Per block emission cell per_block_emission = substake_.emission.tao / emission_drain_tempo + + # Alpha ownership and TAO ownership cells if alpha_value.tao > 0.00009: if issuance.tao != 0: alpha_ownership = "{:.4f}".format( @@ -1992,6 +2099,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): # TODO what's this var for? alpha_ownership = "0.0000" tao_ownership = "0.0000" + rows.append( [ str(netuid), # Number @@ -2012,77 +2120,192 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block) ] ) - # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") - table = Table( - title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {name}\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n\n[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, - ) - table.add_column( - "[white]Netuid", - footer=f"{len(rows)}", - footer_style="overline white", - style="grey89", - ) - table.add_column( - "[white]Symbol", - style=COLOR_PALETTE["GENERAL"]["SYMBOL"], - justify="center", - no_wrap=True, - ) - table.add_column( - f"[white]Stake ({Balance.get_unit(1)})", - footer_style="overline white", - style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], - justify="center", - ) - table.add_column( - f"[white]Rate \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", - footer_style="white", - style=COLOR_PALETTE["POOLS"]["RATE"], - justify="center", - ) - table.add_column( - f"[white]TAO equiv \n({Balance.unit}_in x {Balance.get_unit(1)}/{Balance.get_unit(1)}_out)", - style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"], - justify="right", - footer=f"{total_tao_ownership}", - ) - table.add_column( - f"[white]Exchange Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", - footer_style="overline white", - style=COLOR_PALETTE["STAKE"]["TAO"], - justify="right", - footer=f"{total_tao_value}", - ) - table.add_column( - f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", - footer_style="overline white", - style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], - justify="right", - footer=f"{total_swapped_tao_value}", - ) - table.add_column( - "[white]Registered", - style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], - justify="right", - ) - table.add_column( - f"[white]Emission \n({Balance.get_unit(1)}/block)", - style=COLOR_PALETTE["POOLS"]["EMISSION"], - justify="right", + table = define_table( + name, rows, total_tao_ownership, total_tao_value, total_swapped_tao_value ) for row in rows: table.add_row(*row) console.print(table) return total_tao_ownership, total_tao_value + def create_live_table( + substakes: list, + registered_delegate_info: dict, + dynamic_info: dict, + emission_drain_tempo: int, + hotkey_name: str, + previous_data: Optional[dict] = None, + ) -> tuple[Table, dict, Balance, Balance, Balance]: + rows = [] + current_data = {} + + total_tao_ownership = Balance(0) + total_tao_value = Balance(0) + total_swapped_tao_value = Balance(0) + + def format_cell(value, previous_value, unit="", unit_first=False, precision=4): + if previous_value is not None: + change = value - previous_value + if abs(change) > 10 ** (-precision): + change_text = ( + f" [pale_green3](+{change:.{precision}f})[/pale_green3]" + if change > 0 + else f" [hot_pink3]({change:.{precision}f})[/hot_pink3]" + ) + else: + change_text = "" + else: + change_text = "" + return ( + f"{value:,.{precision}f} {unit}{change_text}" + if not unit_first + else f"{unit} {value:,.{precision}f}{change_text}" + ) + + # Process each stake + for substake in substakes: + netuid = substake.netuid + pool = dynamic_info.get(netuid) + if substake.stake.rao == 0 or not pool: + continue + + # Calculate base values + symbol = f"{Balance.get_unit(netuid)}\u200e" + alpha_value = Balance.from_rao(int(substake.stake.rao)).set_unit(netuid) + tao_value = pool.alpha_to_tao(alpha_value) + total_tao_value += tao_value + swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage( + substake.stake + ) + total_swapped_tao_value += swapped_tao_value + + # Calculate TAO ownership + tao_locked = pool.tao_in + issuance = pool.alpha_out if pool.is_dynamic else tao_locked + if alpha_value.tao > 0.00009 and issuance.tao != 0: + tao_ownership = Balance.from_tao( + (alpha_value.tao / issuance.tao) * tao_locked.tao + ) + total_tao_ownership += tao_ownership + else: + tao_ownership = Balance.from_tao(0) + + # Store current values for future delta tracking + current_data[netuid] = { + "stake": alpha_value.tao, + "price": pool.price.tao, + "tao_value": tao_value.tao, + "swapped_value": swapped_tao_value.tao, + "emission": substake.emission.tao / emission_drain_tempo, + "tao_ownership": tao_ownership.tao, + } + + # Get previous values for delta tracking + prev = previous_data.get(netuid, {}) if previous_data else {} + unit_first = True if netuid == 0 else False + + stake_cell = format_cell( + alpha_value.tao, + prev.get("stake"), + unit=symbol, + unit_first=unit_first, + precision=4, + ) + + rate_cell = format_cell( + pool.price.tao, + prev.get("price"), + unit=f"τ/{symbol}", + unit_first=False, + precision=5, + ) + + tao_ownership_cell = format_cell( + tao_ownership.tao, + prev.get("tao_ownership"), + unit="τ", + unit_first=True, + precision=4, + ) + + exchange_cell = format_cell( + tao_value.tao, + prev.get("tao_value"), + unit="τ", + unit_first=True, + precision=4, + ) + + if pool.is_dynamic: + slippage_pct = ( + 100 * float(slippage) / float(slippage + swapped_tao_value) + if slippage + swapped_tao_value != 0 + else 0 + ) + else: + slippage_pct = 0 + + swap_cell = ( + format_cell( + swapped_tao_value.tao, + prev.get("swapped_value"), + unit="τ", + unit_first=True, + precision=4, + ) + + f" ({slippage_pct:.2f}%)" + ) + + emission_value = substake.emission.tao / emission_drain_tempo + emission_cell = format_cell( + emission_value, + prev.get("emission"), + unit=symbol, + unit_first=unit_first, + precision=4, + ) + + rows.append( + [ + str(netuid), # Netuid + symbol if netuid != 0 else "\u03a4", # Symbol + stake_cell, # Stake amount + rate_cell, # Rate + tao_ownership_cell, # TAO equivalent + exchange_cell, # Exchange value + swap_cell, # Swap value with slippage + "YES" + if substake.is_registered + else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status + emission_cell, # Emission rate + ] + ) + + table = define_table( + hotkey_name, + rows, + total_tao_ownership, + total_tao_value, + total_swapped_tao_value, + live=True, + ) + + for row in rows: + table.add_row(*row) + + return table, current_data + + # Main execution + ( + sub_stakes, + registered_delegate_info, + dynamic_info, + emission_drain_tempo, + ) = await get_stake_data() + + # Iterate over substakes and aggregate them by hotkey. + hotkeys_to_substakes: dict[str, list[StakeInfo]] = {} + for substake in sub_stakes: hotkey = substake.hotkey_ss58 if substake.stake.rao == 0: @@ -2091,109 +2314,217 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): hotkeys_to_substakes[hotkey] = [] hotkeys_to_substakes[hotkey].append(substake) - # Iterate over each hotkey and make a table - counter = 0 - num_hotkeys = len(hotkeys_to_substakes) - all_hotkeys_total_global_tao = Balance(0) - all_hotkeys_total_tao_value = Balance(0) - for hotkey in hotkeys_to_substakes.keys(): - counter += 1 - stake, value = table_substakes(hotkey, hotkeys_to_substakes[hotkey]) - all_hotkeys_total_global_tao += stake - all_hotkeys_total_tao_value += value - - if num_hotkeys > 1 and counter < num_hotkeys: - console.print("\nPress Enter to continue to the next hotkey...") - input() - - console.print("\n\n") - console.print( - f"Wallet:\n" - f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" - f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" - f" Total TAO ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{all_hotkeys_total_global_tao}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" - f" Total Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{all_hotkeys_total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" - ) - if not sub_stakes: - console.print(f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})") - else: - display_table = Prompt.ask( - "\nPress Enter to view column descriptions or type 'q' to skip:", - choices=["", "q"], - default="", - show_choices=True, - ).lower() - - if display_table == "q": + if live: + # Select one hokkey for live monitoring + if len(hotkeys_to_substakes) > 1: console.print( - f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped." + "\n[bold]Multiple hotkeys found. Please select one for live monitoring:[/bold]" ) - else: - header = """ - [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: - """ - console.print(header) - description_table = Table( - show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + for idx, hotkey in enumerate(hotkeys_to_substakes.keys()): + name = ( + f"{registered_delegate_info[hotkey].display} ({hotkey})" + if hotkey in registered_delegate_info + else hotkey + ) + console.print(f"[{idx}] [{COLOR_PALETTE['GENERAL']['HEADER']}]{name}") + + selected_idx = Prompt.ask( + "Enter hotkey index", + choices=[str(i) for i in range(len(hotkeys_to_substakes))], ) + selected_hotkey = list(hotkeys_to_substakes.keys())[int(selected_idx)] + selected_stakes = hotkeys_to_substakes[selected_hotkey] + else: + selected_hotkey = list(hotkeys_to_substakes.keys())[0] + selected_stakes = hotkeys_to_substakes[selected_hotkey] - fields = [ - ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), - ( - "[bold tan]Symbol[/bold tan]", - "The symbol for the subnet's dynamic TAO token.", - ), - ( - "[bold tan]Stake (α)[/bold tan]", - "The stake amount this hotkey holds in the subnet, expressed in subnet's alpha token currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#staking[/blue].", - ), - ( - "[bold tan]TAO Reserves (τ_in)[/bold tan]", - 'Number of TAO in the TAO reserves of the pool for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].', - ), - ( - "[bold tan]Alpha Reserves (α_in)[/bold tan]", - "Number of subnet alpha tokens in the alpha reserves of the pool for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].", - ), - ( - "[bold tan]RATE (τ_in/α_in)[/bold tan]", - "Exchange rate between TAO and subnet dTAO token. Calculated as the reserve ratio: (TAO Pool (τ_in) / Alpha Pool (α_in)). Note that the terms relative price, alpha token price, alpha price are the same as exchange rate. This rate can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#rate-%CF%84_in%CE%B1_in[/blue].", - ), - ( - "[bold tan]Alpha out (α_out)[/bold tan]", - "Total stake in the subnet, expressed in subnet's alpha token currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#stake-%CE%B1_out-or-alpha-out-%CE%B1_out", - ), - ( - "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", - 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#local-weight-or-tao-equiv-%CF%84_in-x-%CE%B1%CE%B1_out[/blue].', - ), - ( - "[bold tan]Exchange Value (α x τ/α)[/bold tan]", - "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].", - ), - ( - "[bold tan]Swap (α → τ)[/bold tan]", - "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].", - ), - ( - "[bold tan]Registered[/bold tan]", - "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", - ), - ( - "[bold tan]Emission (α/block)[/bold tan]", - "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#emissions[/blue].", - ), - ] + hotkey_name = ( + f"{registered_delegate_info[selected_hotkey].display} ({selected_hotkey})" + if selected_hotkey in registered_delegate_info + else selected_hotkey + ) + + refresh_interval = 10 # seconds + progress = Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(bar_width=20), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + console=console, + ) + progress_task = progress.add_task("Updating: ", total=refresh_interval) + + previous_block = None + current_block = None + previous_data = None + + with Live(console=console, screen=True, auto_refresh=True) as live: + try: + while True: + block_hash = await subtensor.substrate.get_chain_head() + ( + sub_stakes, + registered_delegate_info, + dynamic_info_, + emission_drain_tempo, + ) = await get_stake_data(block_hash) + selected_stakes = [ + stake + for stake in sub_stakes + if stake.hotkey_ss58 == selected_hotkey + ] - description_table.add_column( - "Field", - no_wrap=True, - style="bold tan", + dynamic_info = {info.netuid: info for info in dynamic_info_} + block_number = await subtensor.substrate.get_block_number(None) + + previous_block = current_block + current_block = block_number + new_blocks = ( + "N/A" + if previous_block is None + else str(current_block - previous_block) + ) + + table, current_data = create_live_table( + selected_stakes, + registered_delegate_info, + dynamic_info, + emission_drain_tempo, + hotkey_name, + previous_data, + ) + + previous_data = current_data + progress.reset(progress_task) + start_time = asyncio.get_event_loop().time() + + block_info = ( + f"Previous: [dark_sea_green]{previous_block}[/dark_sea_green] " + f"Current: [dark_sea_green]{current_block}[/dark_sea_green] " + f"Diff: [dark_sea_green]{new_blocks}[/dark_sea_green]" + ) + + message = f"\nLive stake view - Press [bold red]Ctrl+C[/bold red] to exit\n{block_info}" + live_render = Group(message, progress, table) + live.update(live_render) + + while not progress.finished: + await asyncio.sleep(0.1) + elapsed = asyncio.get_event_loop().time() - start_time + progress.update( + progress_task, completed=min(elapsed, refresh_interval) + ) + + except KeyboardInterrupt: + console.print("\n[bold]Stopped live updates[/bold]") + return + + else: + # Iterate over each hotkey and make a table + counter = 0 + num_hotkeys = len(hotkeys_to_substakes) + all_hotkeys_total_global_tao = Balance(0) + all_hotkeys_total_tao_value = Balance(0) + for hotkey in hotkeys_to_substakes.keys(): + counter += 1 + stake, value = create_table(hotkey, hotkeys_to_substakes[hotkey]) + all_hotkeys_total_global_tao += stake + all_hotkeys_total_tao_value += value + + if num_hotkeys > 1 and counter < num_hotkeys: + console.print("\nPress Enter to continue to the next hotkey...") + input() + + balance = await subtensor.get_balance(coldkey_address) + console.print("\n\n") + console.print( + f"Wallet:\n" + f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" + f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance[coldkey_address]}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f" Total TAO ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{all_hotkeys_total_global_tao}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f" Total Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{all_hotkeys_total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" + ) + if not sub_stakes: + console.print( + f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})" ) - description_table.add_column("Description", overflow="fold") - for field_name, description in fields: - description_table.add_row(field_name, description) - console.print(description_table) + else: + display_table = Prompt.ask( + "\nPress Enter to view column descriptions or type 'q' to skip:", + choices=["", "q"], + default="", + show_choices=True, + ).lower() + + if display_table == "q": + console.print( + f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped." + ) + else: + header = """ + [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: + """ + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) + + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Stake (α)[/bold tan]", + "The stake amount this hotkey holds in the subnet, expressed in subnet's alpha token currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#staking[/blue].", + ), + ( + "[bold tan]TAO Reserves (τ_in)[/bold tan]", + 'Number of TAO in the TAO reserves of the pool for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].', + ), + ( + "[bold tan]Alpha Reserves (α_in)[/bold tan]", + "Number of subnet alpha tokens in the alpha reserves of the pool for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#subnet-pool[/blue].", + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as the reserve ratio: (TAO Pool (τ_in) / Alpha Pool (α_in)). Note that the terms relative price, alpha token price, alpha price are the same as exchange rate. This rate can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#rate-%CF%84_in%CE%B1_in[/blue].", + ), + ( + "[bold tan]Alpha out (α_out)[/bold tan]", + "Total stake in the subnet, expressed in subnet's alpha token currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#stake-%CE%B1_out-or-alpha-out-%CE%B1_out", + ), + ( + "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#local-weight-or-tao-equiv-%CF%84_in-x-%CE%B1%CE%B1_out[/blue].', + ), + ( + "[bold tan]Exchange Value (α x τ/α)[/bold tan]", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].", + ), + ( + "[bold tan]Swap (α → τ)[/bold tan]", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].", + ), + ( + "[bold tan]Registered[/bold tan]", + "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Emission (α/block)[/bold tan]", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://new-docs-50g07lci2-rajkaramchedus-projects.vercel.app/dynamic-tao/dtao-guide#emissions[/blue].", + ), + ] + + description_table.add_column( + "Field", + no_wrap=True, + style="bold tan", + ) + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def move_stake(