bittensor-cli 9.8.7__py3-none-any.whl → 9.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,7 @@ from bittensor_cli.src.bittensor.utils import (
39
39
  print_error,
40
40
  unlock_key,
41
41
  hex_to_bytes,
42
+ get_hotkey_pub_ss58,
42
43
  )
43
44
 
44
45
  if typing.TYPE_CHECKING:
@@ -490,7 +491,7 @@ async def register_extrinsic(
490
491
 
491
492
  async def get_neuron_for_pubkey_and_subnet():
492
493
  uid = await subtensor.query(
493
- "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
494
+ "SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
494
495
  )
495
496
  if uid is None:
496
497
  return NeuronInfo.get_null_neuron()
@@ -525,7 +526,7 @@ async def register_extrinsic(
525
526
  if not Confirm.ask(
526
527
  f"Continue Registration?\n"
527
528
  f" hotkey [{COLOR_PALETTE.G.HK}]({wallet.hotkey_str})[/{COLOR_PALETTE.G.HK}]:"
528
- f"\t[{COLOR_PALETTE.G.HK}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE.G.HK}]\n"
529
+ f"\t[{COLOR_PALETTE.G.HK}]{get_hotkey_pub_ss58(wallet)}[/{COLOR_PALETTE.G.HK}]\n"
529
530
  f" coldkey [{COLOR_PALETTE.G.CK}]({wallet.name})[/{COLOR_PALETTE.G.CK}]:"
530
531
  f"\t[{COLOR_PALETTE.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE.G.CK}]\n"
531
532
  f" network:\t\t[{COLOR_PALETTE.G.LINKS}]{subtensor.network}[/{COLOR_PALETTE.G.LINKS}]\n"
@@ -577,7 +578,7 @@ async def register_extrinsic(
577
578
  if not pow_result:
578
579
  # might be registered already on this subnet
579
580
  is_registered = await is_hotkey_registered(
580
- subtensor, netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address
581
+ subtensor, netuid=netuid, hotkey_ss58=get_hotkey_pub_ss58(wallet)
581
582
  )
582
583
  if is_registered:
583
584
  err_console.print(
@@ -598,7 +599,7 @@ async def register_extrinsic(
598
599
  "block_number": pow_result.block_number,
599
600
  "nonce": pow_result.nonce,
600
601
  "work": [int(byte_) for byte_ in pow_result.seal],
601
- "hotkey": wallet.hotkey.ss58_address,
602
+ "hotkey": get_hotkey_pub_ss58(wallet),
602
603
  "coldkey": wallet.coldkeypub.ss58_address,
603
604
  },
604
605
  )
@@ -639,7 +640,7 @@ async def register_extrinsic(
639
640
  is_registered = await is_hotkey_registered(
640
641
  subtensor,
641
642
  netuid=netuid,
642
- hotkey_ss58=wallet.hotkey.ss58_address,
643
+ hotkey_ss58=get_hotkey_pub_ss58(wallet),
643
644
  )
644
645
  if is_registered:
645
646
  console.print(
@@ -704,7 +705,7 @@ async def burned_register_extrinsic(
704
705
  spinner="aesthetic",
705
706
  ) as status:
706
707
  my_uid = await subtensor.query(
707
- "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
708
+ "SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
708
709
  )
709
710
  block_hash = await subtensor.substrate.get_chain_head()
710
711
 
@@ -751,7 +752,7 @@ async def burned_register_extrinsic(
751
752
  call_function="burned_register",
752
753
  call_params={
753
754
  "netuid": netuid,
754
- "hotkey": wallet.hotkey.ss58_address,
755
+ "hotkey": get_hotkey_pub_ss58(wallet),
755
756
  },
756
757
  )
757
758
  success, err_msg = await subtensor.sign_and_send_extrinsic(
@@ -773,10 +774,10 @@ async def burned_register_extrinsic(
773
774
  reuse_block=False,
774
775
  ),
775
776
  subtensor.get_netuids_for_hotkey(
776
- wallet.hotkey.ss58_address, block_hash=block_hash
777
+ get_hotkey_pub_ss58(wallet), block_hash=block_hash
777
778
  ),
778
779
  subtensor.query(
779
- "SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
780
+ "SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
780
781
  ),
781
782
  )
782
783
 
@@ -1146,7 +1147,7 @@ async def _block_solver(
1146
1147
 
1147
1148
  timeout = 0.15 if cuda else 0.15
1148
1149
  while netuid == -1 or not await is_hotkey_registered(
1149
- subtensor, netuid, wallet.hotkey.ss58_address
1150
+ subtensor, netuid, get_hotkey_pub_ss58(wallet)
1150
1151
  ):
1151
1152
  # Wait until a solver finds a solution
1152
1153
  try:
@@ -1755,37 +1756,39 @@ async def swap_hotkey_extrinsic(
1755
1756
  :return: Success
1756
1757
  """
1757
1758
  block_hash = await subtensor.substrate.get_chain_head()
1759
+ hk_ss58 = get_hotkey_pub_ss58(wallet)
1758
1760
  netuids_registered = await subtensor.get_netuids_for_hotkey(
1759
- wallet.hotkey.ss58_address, block_hash=block_hash
1761
+ hk_ss58, block_hash=block_hash
1760
1762
  )
1761
1763
  netuids_registered_new_hotkey = await subtensor.get_netuids_for_hotkey(
1762
- new_wallet.hotkey.ss58_address, block_hash=block_hash
1764
+ hk_ss58, block_hash=block_hash
1763
1765
  )
1764
1766
 
1765
1767
  if netuid is not None and netuid not in netuids_registered:
1766
1768
  err_console.print(
1767
- f":cross_mark: [red]Failed[/red]: Original hotkey {wallet.hotkey.ss58_address} is not registered on subnet {netuid}"
1769
+ f":cross_mark: [red]Failed[/red]: Original hotkey {hk_ss58} is not registered on subnet {netuid}"
1768
1770
  )
1769
1771
  return False
1770
1772
 
1771
1773
  elif not len(netuids_registered) > 0:
1772
1774
  err_console.print(
1773
- f"Original hotkey [dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] is not registered on any subnet. "
1775
+ f"Original hotkey [dark_orange]{hk_ss58}[/dark_orange] is not registered on any subnet. "
1774
1776
  f"Please register and try again"
1775
1777
  )
1776
1778
  return False
1777
1779
 
1780
+ new_hk_ss58 = get_hotkey_pub_ss58(new_wallet)
1778
1781
  if netuid is not None:
1779
1782
  if netuid in netuids_registered_new_hotkey:
1780
1783
  err_console.print(
1781
- f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} "
1784
+ f":cross_mark: [red]Failed[/red]: New hotkey {new_hk_ss58} "
1782
1785
  f"is already registered on subnet {netuid}"
1783
1786
  )
1784
1787
  return False
1785
1788
  else:
1786
1789
  if len(netuids_registered_new_hotkey) > 0:
1787
1790
  err_console.print(
1788
- f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} "
1791
+ f":cross_mark: [red]Failed[/red]: New hotkey {new_hk_ss58} "
1789
1792
  f"is already registered on subnet(s) {netuids_registered_new_hotkey}"
1790
1793
  )
1791
1794
  return False
@@ -1798,28 +1801,28 @@ async def swap_hotkey_extrinsic(
1798
1801
  if netuid is not None:
1799
1802
  confirm_message = (
1800
1803
  f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
1801
- f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
1802
- f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n"
1804
+ f"[dark_orange]{hk_ss58} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
1805
+ f"[dark_orange]{new_hk_ss58} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n"
1803
1806
  "This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]"
1804
1807
  )
1805
1808
  else:
1806
1809
  confirm_message = (
1807
1810
  f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
1808
- f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
1809
- f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n"
1811
+ f"[dark_orange]{hk_ss58} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
1812
+ f"[dark_orange]{new_hk_ss58} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n"
1810
1813
  "This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]"
1811
1814
  )
1812
1815
 
1813
1816
  if not Confirm.ask(confirm_message):
1814
1817
  return False
1815
1818
  print_verbose(
1816
- f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address} - {wallet.hotkey_str}) with "
1817
- f"{new_wallet.name}'s hotkey ({new_wallet.hotkey.ss58_address} - {new_wallet.hotkey_str})"
1819
+ f"Swapping {wallet.name}'s hotkey ({hk_ss58} - {wallet.hotkey_str}) with "
1820
+ f"{new_wallet.name}'s hotkey ({new_hk_ss58} - {new_wallet.hotkey_str})"
1818
1821
  )
1819
1822
  with console.status(":satellite: Swapping hotkeys...", spinner="aesthetic"):
1820
1823
  call_params = {
1821
- "hotkey": wallet.hotkey.ss58_address,
1822
- "new_hotkey": new_wallet.hotkey.ss58_address,
1824
+ "hotkey": hk_ss58,
1825
+ "new_hotkey": new_hk_ss58,
1823
1826
  "netuid": netuid,
1824
1827
  }
1825
1828
 
@@ -1832,7 +1835,8 @@ async def swap_hotkey_extrinsic(
1832
1835
 
1833
1836
  if success:
1834
1837
  console.print(
1835
- f"Hotkey {wallet.hotkey.ss58_address} ({wallet.hotkey_str}) swapped for new hotkey: {new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})"
1838
+ f"Hotkey {hk_ss58} ({wallet.hotkey_str}) swapped for new hotkey: "
1839
+ f"{new_hk_ss58} ({new_wallet.hotkey_str})"
1836
1840
  )
1837
1841
  return True
1838
1842
  else:
@@ -37,6 +37,7 @@ from bittensor_cli.src.bittensor.utils import (
37
37
  print_verbose,
38
38
  format_error_message,
39
39
  unlock_key,
40
+ get_hotkey_pub_ss58,
40
41
  )
41
42
 
42
43
  if TYPE_CHECKING:
@@ -310,7 +311,7 @@ async def root_register_extrinsic(
310
311
 
311
312
  print_verbose(f"Checking if hotkey ({wallet.hotkey_str}) is registered on root")
312
313
  is_registered = await is_hotkey_registered(
313
- subtensor, netuid=0, hotkey_ss58=wallet.hotkey.ss58_address
314
+ subtensor, netuid=0, hotkey_ss58=get_hotkey_pub_ss58(wallet)
314
315
  )
315
316
  if is_registered:
316
317
  console.print(
@@ -322,7 +323,7 @@ async def root_register_extrinsic(
322
323
  call = await subtensor.substrate.compose_call(
323
324
  call_module="SubtensorModule",
324
325
  call_function="root_register",
325
- call_params={"hotkey": wallet.hotkey.ss58_address},
326
+ call_params={"hotkey": get_hotkey_pub_ss58(wallet)},
326
327
  )
327
328
  success, err_msg = await subtensor.sign_and_send_extrinsic(
328
329
  call,
@@ -341,7 +342,7 @@ async def root_register_extrinsic(
341
342
  uid = await subtensor.query(
342
343
  module="SubtensorModule",
343
344
  storage_function="Uids",
344
- params=[0, wallet.hotkey.ss58_address],
345
+ params=[0, get_hotkey_pub_ss58(wallet)],
345
346
  )
346
347
  if uid is not None:
347
348
  console.print(
@@ -391,7 +392,7 @@ async def set_root_weights_extrinsic(
391
392
  "weights": weight_vals,
392
393
  "netuid": 0,
393
394
  "version_key": version_key,
394
- "hotkey": wallet.hotkey.ss58_address,
395
+ "hotkey": get_hotkey_pub_ss58(wallet),
395
396
  },
396
397
  )
397
398
  # Period dictates how long the extrinsic will stay as part of waiting pool
@@ -415,7 +416,7 @@ async def set_root_weights_extrinsic(
415
416
  return False, await response.error_message
416
417
 
417
418
  my_uid = await subtensor.query(
418
- "SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address]
419
+ "SubtensorModule", "Uids", [0, get_hotkey_pub_ss58(wallet)]
419
420
  )
420
421
 
421
422
  if my_uid is None:
@@ -26,9 +26,9 @@ async def transfer_extrinsic(
26
26
  amount: Balance,
27
27
  era: int = 3,
28
28
  transfer_all: bool = False,
29
+ allow_death: bool = False,
29
30
  wait_for_inclusion: bool = True,
30
31
  wait_for_finalization: bool = False,
31
- keep_alive: bool = True,
32
32
  prompt: bool = False,
33
33
  ) -> bool:
34
34
  """Transfers funds from this wallet to the destination public key address.
@@ -39,11 +39,11 @@ async def transfer_extrinsic(
39
39
  :param amount: Amount to stake as Bittensor balance.
40
40
  :param era: Length (in blocks) for which the transaction should be valid.
41
41
  :param transfer_all: Whether to transfer all funds from this wallet to the destination address.
42
+ :param allow_death: Whether to allow for falling below the existential deposit when performing this transfer.
42
43
  :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
43
44
  or returns `False` if the extrinsic fails to enter the block within the timeout.
44
45
  :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning
45
46
  `True`, or returns `False` if the extrinsic fails to be finalized within the timeout.
46
- :param keep_alive: If set, keeps the account alive by keeping the balance above the existential deposit.
47
47
  :param prompt: If `True`, the call waits for confirmation from the user before proceeding.
48
48
  :return: success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
49
49
  finalization / inclusion, the response is `True`, regardless of its inclusion.
@@ -57,8 +57,8 @@ async def transfer_extrinsic(
57
57
  """
58
58
  call = await subtensor.substrate.compose_call(
59
59
  call_module="Balances",
60
- call_function="transfer_keep_alive",
61
- call_params={"dest": destination, "value": amount.rao},
60
+ call_function=call_function,
61
+ call_params=call_params,
62
62
  )
63
63
 
64
64
  try:
@@ -82,8 +82,8 @@ async def transfer_extrinsic(
82
82
  """
83
83
  call = await subtensor.substrate.compose_call(
84
84
  call_module="Balances",
85
- call_function="transfer_keep_alive",
86
- call_params={"dest": destination, "value": amount.rao},
85
+ call_function=call_function,
86
+ call_params=call_params,
87
87
  )
88
88
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
89
89
  call=call, keypair=wallet.coldkey, era={"period": era}
@@ -115,6 +115,20 @@ async def transfer_extrinsic(
115
115
  if not unlock_key(wallet).success:
116
116
  return False
117
117
 
118
+ call_params = {"dest": destination}
119
+ if transfer_all:
120
+ call_function = "transfer_all"
121
+ if allow_death:
122
+ call_params["keep_alive"] = False
123
+ else:
124
+ call_params["keep_alive"] = True
125
+ else:
126
+ call_params["value"] = amount.rao
127
+ if allow_death:
128
+ call_function = "transfer_allow_death"
129
+ else:
130
+ call_function = "transfer_keep_alive"
131
+
118
132
  # Check balance.
119
133
  with console.status(
120
134
  f":satellite: Checking balance and fees on chain [white]{subtensor.network}[/white]",
@@ -131,23 +145,26 @@ async def transfer_extrinsic(
131
145
  )
132
146
  fee = await get_transfer_fee()
133
147
 
134
- if not keep_alive:
135
- # Check if the transfer should keep_alive the account
148
+ if allow_death:
149
+ # Check if the transfer should keep alive the account
136
150
  existential_deposit = Balance(0)
137
151
 
138
- # Check if we have enough balance.
139
- if transfer_all is True:
140
- amount = account_balance - fee - existential_deposit
141
- if amount < Balance(0):
142
- print_error("Not enough balance to transfer")
143
- return False
144
-
145
- if account_balance < (amount + fee + existential_deposit):
152
+ if account_balance < (amount + fee + existential_deposit) and not allow_death:
146
153
  err_console.print(
147
154
  ":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
148
155
  f" balance: [bright_cyan]{account_balance}[/bright_cyan]\n"
149
156
  f" amount: [bright_cyan]{amount}[/bright_cyan]\n"
150
- f" for fee: [bright_cyan]{fee}[/bright_cyan]"
157
+ f" for fee: [bright_cyan]{fee}[/bright_cyan]\n"
158
+ f" would bring you under the existential deposit: [bright_cyan]{existential_deposit}[/bright_cyan].\n"
159
+ f"You can try again with `--allow-death`."
160
+ )
161
+ return False
162
+ elif account_balance < (amount + fee) and allow_death:
163
+ print_error(
164
+ ":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
165
+ f" balance: [bright_red]{account_balance}[/bright_red]\n"
166
+ f" amount: [bright_red]{amount}[/bright_red]\n"
167
+ f" for fee: [bright_red]{fee}[/bright_red]"
151
168
  )
152
169
  return False
153
170
 
@@ -155,7 +172,7 @@ async def transfer_extrinsic(
155
172
  if prompt:
156
173
  if not Confirm.ask(
157
174
  "Do you want to transfer:[bold white]\n"
158
- f" amount: [bright_cyan]{amount}[/bright_cyan]\n"
175
+ f" amount: [bright_cyan]{amount if not transfer_all else account_balance}[/bright_cyan]\n"
159
176
  f" from: [light_goldenrod2]{wallet.name}[/light_goldenrod2] : "
160
177
  f"[bright_magenta]{wallet.coldkey.ss58_address}\n[/bright_magenta]"
161
178
  f" to: [bright_magenta]{destination}[/bright_magenta]\n for fee: [bright_cyan]{fee}[/bright_cyan]"
@@ -3,7 +3,9 @@ import os
3
3
  from typing import Optional, Any, Union, TypedDict, Iterable
4
4
 
5
5
  import aiohttp
6
+ from async_substrate_interface.utils.storage import StorageKey
6
7
  from bittensor_wallet import Wallet
8
+ from bittensor_wallet.bittensor_wallet import Keypair
7
9
  from bittensor_wallet.utils import SS58_FORMAT
8
10
  from scalecodec import GenericCall
9
11
  from async_substrate_interface.errors import SubstrateRequestException
@@ -37,6 +39,7 @@ from bittensor_cli.src.bittensor.utils import (
37
39
  validate_chain_endpoint,
38
40
  u16_normalized_float,
39
41
  U16_MAX,
42
+ get_hotkey_pub_ss58,
40
43
  )
41
44
 
42
45
  SubstrateClass = (
@@ -664,7 +667,7 @@ class SubtensorInterface:
664
667
  for sublist in await asyncio.gather(
665
668
  *[
666
669
  self.get_netuids_for_hotkey(
667
- wallet.hotkey.ss58_address,
670
+ get_hotkey_pub_ss58(wallet),
668
671
  reuse_block=reuse_block,
669
672
  block_hash=block_hash,
670
673
  )
@@ -881,9 +884,10 @@ class SubtensorInterface:
881
884
  storage_function="IdentitiesV2",
882
885
  block_hash=block_hash,
883
886
  reuse_block_hash=reuse_block,
887
+ fully_exhaust=True,
884
888
  )
885
889
  all_identities = {}
886
- async for ss58_address, identity in identities:
890
+ for ss58_address, identity in identities.records:
887
891
  all_identities[decode_account_id(ss58_address[0])] = decode_hex_identity(
888
892
  identity.value
889
893
  )
@@ -939,22 +943,22 @@ class SubtensorInterface:
939
943
  :param reuse_block: Whether to reuse the last-used blockchain block hash.
940
944
  :return: Dict with 'coldkeys' and 'hotkeys' as keys.
941
945
  """
942
-
943
- coldkey_identities = await self.query_all_identities()
946
+ if block_hash is None:
947
+ block_hash = await self.substrate.get_chain_head()
948
+ coldkey_identities = await self.query_all_identities(block_hash=block_hash)
944
949
  identities = {"coldkeys": {}, "hotkeys": {}}
945
- if not coldkey_identities:
946
- return identities
947
- query = await self.substrate.query_multiple( # TODO probably more efficient to do this with query_multi
948
- params=list(coldkey_identities.keys()),
949
- module="SubtensorModule",
950
- storage_function="OwnedHotkeys",
951
- block_hash=block_hash,
952
- reuse_block_hash=reuse_block,
953
- )
950
+ sks = [
951
+ await self.substrate.create_storage_key(
952
+ "SubtensorModule", "OwnedHotkeys", [ck], block_hash=block_hash
953
+ )
954
+ for ck in coldkey_identities.keys()
955
+ ]
956
+ query = await self.substrate.query_multi(sks, block_hash=block_hash)
954
957
 
955
- for coldkey_ss58, hotkeys in query.items():
958
+ storage_key: StorageKey
959
+ for storage_key, hotkeys in query:
960
+ coldkey_ss58 = storage_key.params[0]
956
961
  coldkey_identity = coldkey_identities.get(coldkey_ss58)
957
- hotkeys = [decode_account_id(hotkey[0]) for hotkey in hotkeys or []]
958
962
 
959
963
  identities["coldkeys"][coldkey_ss58] = {
960
964
  "identity": coldkey_identity,
@@ -1455,6 +1459,8 @@ class SubtensorInterface:
1455
1459
  ),
1456
1460
  self.get_subnet_price(netuid=netuid, block_hash=block_hash),
1457
1461
  )
1462
+ if not result:
1463
+ raise ValueError(f"Subnet {netuid} not found")
1458
1464
  subnet_ = DynamicInfo.from_any(result)
1459
1465
  subnet_.price = price
1460
1466
  return subnet_
@@ -1484,6 +1490,19 @@ class SubtensorInterface:
1484
1490
 
1485
1491
  return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1486
1492
 
1493
+ async def get_extrinsic_fee(self, call: GenericCall, keypair: Keypair) -> Balance:
1494
+ """
1495
+ Determines the fee for the extrinsic call.
1496
+ Args:
1497
+ call: Created extrinsic call
1498
+ keypair: The keypair that would sign the extrinsic (usually you would just want to use the *pub for this)
1499
+
1500
+ Returns:
1501
+ Balance object representing the fee for this extrinsic.
1502
+ """
1503
+ fee_dict = await self.substrate.get_payment_info(call, keypair)
1504
+ return Balance.from_rao(fee_dict["partial_fee"])
1505
+
1487
1506
  async def get_stake_fee(
1488
1507
  self,
1489
1508
  origin_hotkey_ss58: Optional[str],
@@ -275,7 +275,7 @@ def get_hotkey_wallets_for_wallet(
275
275
  (exists := hotkey_for_name.hotkey_file.exists_on_device())
276
276
  and not hotkey_for_name.hotkey_file.is_encrypted()
277
277
  # and hotkey_for_name.coldkeypub.ss58_address
278
- and hotkey_for_name.hotkey.ss58_address
278
+ and get_hotkey_pub_ss58(hotkey_for_name)
279
279
  ):
280
280
  hotkey_wallets.append(hotkey_for_name)
281
281
  elif (
@@ -1431,3 +1431,15 @@ def blocks_to_duration(blocks: int) -> str:
1431
1431
  results.append(f"{unit_count}{unit}")
1432
1432
  # Return only the first two non-zero units
1433
1433
  return " ".join(results[:2]) or "0s"
1434
+
1435
+
1436
+ def get_hotkey_pub_ss58(wallet: Wallet) -> str:
1437
+ """
1438
+ Helper fn to retrieve the hotkeypub ss58 of a wallet that may have been created before
1439
+ bt-wallet 3.1.1 and thus not have a wallet hotkeypub. In this case, it will return the hotkey
1440
+ SS58.
1441
+ """
1442
+ try:
1443
+ return wallet.hotkeypub.ss58_address
1444
+ except KeyFileError:
1445
+ return wallet.hotkey.ss58_address
@@ -20,6 +20,7 @@ from bittensor_cli.src.bittensor.utils import (
20
20
  print_verbose,
21
21
  unlock_key,
22
22
  json_console,
23
+ get_hotkey_pub_ss58,
23
24
  )
24
25
  from bittensor_wallet import Wallet
25
26
 
@@ -65,6 +66,45 @@ async def stake_add(
65
66
  bool: True if stake operation is successful, False otherwise
66
67
  """
67
68
 
69
+ async def get_stake_extrinsic_fee(
70
+ netuid_: int,
71
+ amount_: Balance,
72
+ staking_address_: str,
73
+ safe_staking_: bool,
74
+ price_limit: Optional[Balance] = None,
75
+ ):
76
+ """
77
+ Quick method to get the extrinsic fee for adding stake depending on the args supplied.
78
+ Args:
79
+ netuid_: The netuid where the stake will be added
80
+ amount_: the amount of stake to add
81
+ staking_address_: the hotkey ss58 to stake to
82
+ safe_staking_: whether to use safe staking
83
+ price_limit: rate with tolerance
84
+
85
+ Returns:
86
+ Balance object representing the extrinsic fee for adding stake.
87
+ """
88
+ call_fn = "add_stake" if not safe_staking_ else "add_stake_limit"
89
+ call_params = {
90
+ "hotkey": staking_address_,
91
+ "netuid": netuid_,
92
+ "amount_staked": amount_.rao,
93
+ }
94
+ if safe_staking_:
95
+ call_params.update(
96
+ {
97
+ "limit_price": price_limit,
98
+ "allow_partial": allow_partial_stake,
99
+ }
100
+ )
101
+ call = await subtensor.substrate.compose_call(
102
+ call_module="SubtensorModule",
103
+ call_function=call_fn,
104
+ call_params=call_params,
105
+ )
106
+ return await subtensor.get_extrinsic_fee(call, wallet.coldkeypub)
107
+
68
108
  async def safe_stake_extrinsic(
69
109
  netuid_: int,
70
110
  amount_: Balance,
@@ -87,7 +127,7 @@ async def stake_add(
87
127
  "hotkey": hotkey_ss58_,
88
128
  "netuid": netuid_,
89
129
  "amount_staked": amount_.rao,
90
- "limit_price": price_limit,
130
+ "limit_price": price_limit.rao,
91
131
  "allow_partial": allow_partial_stake,
92
132
  },
93
133
  ),
@@ -332,19 +372,6 @@ async def stake_add(
332
372
  # Temporary workaround - calculations without slippage
333
373
  current_price_float = float(subnet_info.price.tao)
334
374
  rate = 1.0 / current_price_float
335
- received_amount = rate * amount_to_stake
336
-
337
- # Add rows for the table
338
- base_row = [
339
- str(netuid), # netuid
340
- f"{hotkey[1]}", # hotkey
341
- str(amount_to_stake), # amount
342
- str(rate)
343
- + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
344
- str(received_amount.set_unit(netuid)), # received
345
- str(stake_fee), # fee
346
- # str(slippage_pct), # slippage
347
- ]
348
375
 
349
376
  # If we are staking safe, add price tolerance
350
377
  if safe_staking:
@@ -356,21 +383,45 @@ async def stake_add(
356
383
  rate_with_tolerance = f"{_rate_with_tolerance:.4f}"
357
384
  price_with_tolerance = Balance.from_tao(
358
385
  price_with_tolerance
359
- ).rao # Actual price to pass to extrinsic
386
+ ) # Actual price to pass to extrinsic
360
387
  else:
361
388
  rate_with_tolerance = "1"
362
389
  price_with_tolerance = Balance.from_rao(1)
390
+ extrinsic_fee = await get_stake_extrinsic_fee(
391
+ netuid_=netuid,
392
+ amount_=amount_to_stake,
393
+ staking_address_=hotkey[1],
394
+ safe_staking_=safe_staking,
395
+ price_limit=price_with_tolerance,
396
+ )
363
397
  prices_with_tolerance.append(price_with_tolerance)
364
-
365
- base_row.extend(
366
- [
367
- f"{rate_with_tolerance} {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ",
368
- f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
369
- # safe staking
370
- f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
371
- ]
398
+ row_extension = [
399
+ f"{rate_with_tolerance} {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ",
400
+ f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
401
+ # safe staking
402
+ f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
403
+ ]
404
+ else:
405
+ extrinsic_fee = await get_stake_extrinsic_fee(
406
+ netuid_=netuid,
407
+ amount_=amount_to_stake,
408
+ staking_address_=hotkey[1],
409
+ safe_staking_=safe_staking,
372
410
  )
373
-
411
+ row_extension = []
412
+ received_amount = rate * (amount_to_stake - stake_fee - extrinsic_fee)
413
+ # Add rows for the table
414
+ base_row = [
415
+ str(netuid), # netuid
416
+ f"{hotkey[1]}", # hotkey
417
+ str(amount_to_stake), # amount
418
+ str(rate)
419
+ + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
420
+ str(received_amount.set_unit(netuid)), # received
421
+ str(stake_fee), # fee
422
+ str(extrinsic_fee),
423
+ # str(slippage_pct), # slippage
424
+ ] + row_extension
374
425
  rows.append(tuple(base_row))
375
426
 
376
427
  # Define and print stake table + slippage warning
@@ -502,7 +553,7 @@ def _get_hotkeys_to_stake_to(
502
553
  # Stake to all hotkeys except excluded ones
503
554
  all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
504
555
  return [
505
- (wallet.hotkey_str, wallet.hotkey.ss58_address)
556
+ (wallet.hotkey_str, get_hotkey_pub_ss58(wallet))
506
557
  for wallet in all_hotkeys_
507
558
  if wallet.hotkey_str not in (exclude_hotkeys or [])
508
559
  ]
@@ -522,7 +573,7 @@ def _get_hotkeys_to_stake_to(
522
573
  name=wallet.name,
523
574
  hotkey=hotkey_ss58_or_hotkey_name,
524
575
  )
525
- hotkeys.append((wallet_.hotkey_str, wallet_.hotkey.ss58_address))
576
+ hotkeys.append((wallet_.hotkey_str, get_hotkey_pub_ss58(wallet_)))
526
577
 
527
578
  return hotkeys
528
579
 
@@ -531,7 +582,7 @@ def _get_hotkeys_to_stake_to(
531
582
  f"Staking to hotkey: ({wallet.hotkey_str}) in wallet: ({wallet.name})"
532
583
  )
533
584
  assert wallet.hotkey is not None
534
- return [(None, wallet.hotkey.ss58_address)]
585
+ return [(None, get_hotkey_pub_ss58(wallet))]
535
586
 
536
587
 
537
588
  def _define_stake_table(
@@ -569,17 +620,17 @@ def _define_stake_table(
569
620
  "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
570
621
  )
571
622
  table.add_column(
572
- f"Amount ({Balance.get_unit(0)})",
623
+ "Amount (τ)",
573
624
  justify="center",
574
625
  style=COLOR_PALETTE["POOLS"]["TAO"],
575
626
  )
576
627
  table.add_column(
577
- f"Rate (per {Balance.get_unit(0)})",
628
+ "Rate (per τ)",
578
629
  justify="center",
579
630
  style=COLOR_PALETTE["POOLS"]["RATE"],
580
631
  )
581
632
  table.add_column(
582
- "Received",
633
+ "Est. Received",
583
634
  justify="center",
584
635
  style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
585
636
  )
@@ -588,6 +639,11 @@ def _define_stake_table(
588
639
  justify="center",
589
640
  style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
590
641
  )
642
+ table.add_column(
643
+ "Extrinsic Fee (τ)",
644
+ justify="center",
645
+ style=COLOR_PALETTE.STAKE.TAO,
646
+ )
591
647
  # TODO: Uncomment when slippage is reimplemented for v3
592
648
  # table.add_column(
593
649
  # "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]