bittensor-cli 9.8.6__tar.gz → 9.9.0__tar.gz

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.
Files changed (57) hide show
  1. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/PKG-INFO +2 -2
  2. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/cli.py +64 -1
  3. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/extrinsics/transfer.py +34 -17
  4. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/subtensor_interface.py +32 -14
  5. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/utils.py +19 -14
  6. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/stake/add.py +82 -27
  7. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/stake/move.py +101 -81
  8. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/stake/remove.py +114 -7
  9. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/wallets.py +91 -3
  10. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli.egg-info/PKG-INFO +2 -2
  11. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli.egg-info/requires.txt +1 -1
  12. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/pyproject.toml +2 -2
  13. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/MANIFEST.in +0 -0
  14. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/README.md +0 -0
  15. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/__init__.py +0 -0
  16. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/doc_generation_helper.py +0 -0
  17. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/__init__.py +0 -0
  18. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/__init__.py +0 -0
  19. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/balances.py +0 -0
  20. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/chain_data.py +0 -0
  21. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/extrinsics/__init__.py +0 -0
  22. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/extrinsics/registration.py +0 -0
  23. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/extrinsics/root.py +0 -0
  24. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/minigraph.py +0 -0
  25. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/networking.py +0 -0
  26. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/main-filters.j2 +0 -0
  27. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/main-header.j2 +0 -0
  28. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/neuron-details.j2 +0 -0
  29. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/price-multi.j2 +0 -0
  30. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/price-single.j2 +0 -0
  31. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/subnet-details-header.j2 +0 -0
  32. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/subnet-details.j2 +0 -0
  33. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/subnet-metrics.j2 +0 -0
  34. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/subnets-table.j2 +0 -0
  35. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/table.j2 +0 -0
  36. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/view.css +0 -0
  37. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/view.j2 +0 -0
  38. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/bittensor/templates/view.js +0 -0
  39. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/__init__.py +0 -0
  40. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/liquidity/__init__.py +0 -0
  41. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/liquidity/liquidity.py +0 -0
  42. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/liquidity/utils.py +0 -0
  43. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/stake/__init__.py +0 -0
  44. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/stake/children_hotkeys.py +0 -0
  45. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/stake/list.py +0 -0
  46. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/subnets/__init__.py +0 -0
  47. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/subnets/price.py +0 -0
  48. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/subnets/subnets.py +0 -0
  49. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/sudo.py +0 -0
  50. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/view.py +0 -0
  51. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/src/commands/weights.py +0 -0
  52. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli/version.py +0 -0
  53. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli.egg-info/SOURCES.txt +0 -0
  54. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli.egg-info/dependency_links.txt +0 -0
  55. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli.egg-info/entry_points.txt +0 -0
  56. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/bittensor_cli.egg-info/top_level.txt +0 -0
  57. {bittensor_cli-9.8.6 → bittensor_cli-9.9.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bittensor-cli
3
- Version: 9.8.6
3
+ Version: 9.9.0
4
4
  Summary: Bittensor CLI
5
5
  Author: bittensor.com
6
6
  Project-URL: homepage, https://github.com/opentensor/btcli
@@ -8,7 +8,7 @@ Project-URL: Repository, https://github.com/opentensor/btcli
8
8
  Requires-Python: <3.14,>=3.9
9
9
  Description-Content-Type: text/markdown
10
10
  Requires-Dist: wheel
11
- Requires-Dist: async-substrate-interface>=1.1.0
11
+ Requires-Dist: async-substrate-interface>=1.4.2
12
12
  Requires-Dist: aiohttp~=3.10.2
13
13
  Requires-Dist: backoff~=2.2.1
14
14
  Requires-Dist: click<8.2.0
@@ -817,6 +817,9 @@ class CLIManager:
817
817
  self.wallet_app.command(
818
818
  "sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
819
819
  )(self.wallet_sign)
820
+ self.wallet_app.command(
821
+ "verify", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
822
+ )(self.wallet_verify)
820
823
 
821
824
  # stake commands
822
825
  self.stake_app.command(
@@ -1889,6 +1892,12 @@ class CLIManager:
1889
1892
  transfer_all: bool = typer.Option(
1890
1893
  False, "--all", prompt=False, help="Transfer all available balance."
1891
1894
  ),
1895
+ allow_death: bool = typer.Option(
1896
+ False,
1897
+ "--allow-death",
1898
+ prompt=False,
1899
+ help="Transfer balance even if the resulting balance falls below the existential deposit.",
1900
+ ),
1892
1901
  period: int = Options.period,
1893
1902
  wallet_name: str = Options.wallet_name,
1894
1903
  wallet_path: str = Options.wallet_path,
@@ -1932,7 +1941,7 @@ class CLIManager:
1932
1941
  subtensor = self.initialize_chain(network)
1933
1942
  if transfer_all and amount:
1934
1943
  print_error("Cannot specify an amount and '--all' flag.")
1935
- raise typer.Exit()
1944
+ return False
1936
1945
  elif transfer_all:
1937
1946
  amount = 0
1938
1947
  elif not amount:
@@ -1944,6 +1953,7 @@ class CLIManager:
1944
1953
  destination=destination_ss58_address,
1945
1954
  amount=amount,
1946
1955
  transfer_all=transfer_all,
1956
+ allow_death=allow_death,
1947
1957
  era=period,
1948
1958
  prompt=prompt,
1949
1959
  json_output=json_output,
@@ -3091,6 +3101,59 @@ class CLIManager:
3091
3101
 
3092
3102
  return self._run_command(wallets.sign(wallet, message, use_hotkey, json_output))
3093
3103
 
3104
+ def wallet_verify(
3105
+ self,
3106
+ message: Optional[str] = typer.Option(
3107
+ None, "--message", "-m", help="The message that was signed"
3108
+ ),
3109
+ signature: Optional[str] = typer.Option(
3110
+ None, "--signature", "-s", help="The signature to verify (hex format)"
3111
+ ),
3112
+ public_key_or_ss58: Optional[str] = typer.Option(
3113
+ None,
3114
+ "--address",
3115
+ "-a",
3116
+ "--public-key",
3117
+ "-p",
3118
+ help="SS58 address or public key (hex) of the signer",
3119
+ ),
3120
+ quiet: bool = Options.quiet,
3121
+ verbose: bool = Options.verbose,
3122
+ json_output: bool = Options.json_output,
3123
+ ):
3124
+ """
3125
+ Verify a message signature using the signer's public key or SS58 address.
3126
+
3127
+ This command allows you to verify that a message was signed by the owner of a specific address.
3128
+
3129
+ USAGE
3130
+
3131
+ Provide the original message, the signature (in hex format), and either the SS58 address
3132
+ or public key of the signer to verify the signature.
3133
+
3134
+ EXAMPLES
3135
+
3136
+ [green]$[/green] btcli wallet verify --message "Hello world" --signature "0xabc123..." --address "5GrwvaEF..."
3137
+
3138
+ [green]$[/green] btcli wallet verify -m "Test message" -s "0xdef456..." -p "0x1234abcd..."
3139
+ """
3140
+ self.verbosity_handler(quiet, verbose, json_output)
3141
+
3142
+ if not public_key_or_ss58:
3143
+ public_key_or_ss58 = Prompt.ask(
3144
+ "Enter the [blue]address[/blue] (SS58 or hex format)"
3145
+ )
3146
+
3147
+ if not message:
3148
+ message = Prompt.ask("Enter the [blue]message[/blue]")
3149
+
3150
+ if not signature:
3151
+ signature = Prompt.ask("Enter the [blue]signature[/blue]")
3152
+
3153
+ return self._run_command(
3154
+ wallets.verify(message, signature, public_key_or_ss58, json_output)
3155
+ )
3156
+
3094
3157
  def wallet_swap_coldkey(
3095
3158
  self,
3096
3159
  wallet_name: Optional[str] = Options.wallet_name,
@@ -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
 
@@ -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
@@ -881,9 +883,10 @@ class SubtensorInterface:
881
883
  storage_function="IdentitiesV2",
882
884
  block_hash=block_hash,
883
885
  reuse_block_hash=reuse_block,
886
+ fully_exhaust=True,
884
887
  )
885
888
  all_identities = {}
886
- async for ss58_address, identity in identities:
889
+ for ss58_address, identity in identities.records:
887
890
  all_identities[decode_account_id(ss58_address[0])] = decode_hex_identity(
888
891
  identity.value
889
892
  )
@@ -939,22 +942,22 @@ class SubtensorInterface:
939
942
  :param reuse_block: Whether to reuse the last-used blockchain block hash.
940
943
  :return: Dict with 'coldkeys' and 'hotkeys' as keys.
941
944
  """
942
-
943
- coldkey_identities = await self.query_all_identities()
945
+ if block_hash is None:
946
+ block_hash = await self.substrate.get_chain_head()
947
+ coldkey_identities = await self.query_all_identities(block_hash=block_hash)
944
948
  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
- )
949
+ sks = [
950
+ await self.substrate.create_storage_key(
951
+ "SubtensorModule", "OwnedHotkeys", [ck], block_hash=block_hash
952
+ )
953
+ for ck in coldkey_identities.keys()
954
+ ]
955
+ query = await self.substrate.query_multi(sks, block_hash=block_hash)
954
956
 
955
- for coldkey_ss58, hotkeys in query.items():
957
+ storage_key: StorageKey
958
+ for storage_key, hotkeys in query:
959
+ coldkey_ss58 = storage_key.params[0]
956
960
  coldkey_identity = coldkey_identities.get(coldkey_ss58)
957
- hotkeys = [decode_account_id(hotkey[0]) for hotkey in hotkeys or []]
958
961
 
959
962
  identities["coldkeys"][coldkey_ss58] = {
960
963
  "identity": coldkey_identity,
@@ -1455,6 +1458,8 @@ class SubtensorInterface:
1455
1458
  ),
1456
1459
  self.get_subnet_price(netuid=netuid, block_hash=block_hash),
1457
1460
  )
1461
+ if not result:
1462
+ raise ValueError(f"Subnet {netuid} not found")
1458
1463
  subnet_ = DynamicInfo.from_any(result)
1459
1464
  subnet_.price = price
1460
1465
  return subnet_
@@ -1484,6 +1489,19 @@ class SubtensorInterface:
1484
1489
 
1485
1490
  return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1486
1491
 
1492
+ async def get_extrinsic_fee(self, call: GenericCall, keypair: Keypair) -> Balance:
1493
+ """
1494
+ Determines the fee for the extrinsic call.
1495
+ Args:
1496
+ call: Created extrinsic call
1497
+ keypair: The keypair that would sign the extrinsic (usually you would just want to use the *pub for this)
1498
+
1499
+ Returns:
1500
+ Balance object representing the fee for this extrinsic.
1501
+ """
1502
+ fee_dict = await self.substrate.get_payment_info(call, keypair)
1503
+ return Balance.from_rao(fee_dict["partial_fee"])
1504
+
1487
1505
  async def get_stake_fee(
1488
1506
  self,
1489
1507
  origin_hotkey_ss58: Optional[str],
@@ -622,20 +622,23 @@ def decode_hex_identity_dict(info_dictionary) -> dict[str, Any]:
622
622
 
623
623
  Examples:
624
624
  input_dict = {
625
- ... "name": {"value": "0x6a6f686e"},
626
- ... "additional": [
627
- ... [{"data": "0x64617461"}]
628
- ... ]
629
- ... }
625
+ "name": {"value": "0x6a6f686e"},
626
+ "additional": [
627
+ {"data1": "0x64617461"},
628
+ ("data2", "0x64617461")
629
+ ]
630
+ }
630
631
  decode_hex_identity_dict(input_dict)
631
- {'name': 'john', 'additional': [('data', 'data')]}
632
+ {'name': 'john', 'additional': [('data1', 'data'), ('data2', 'data')]}
632
633
  """
633
634
 
634
- def get_decoded(data: str) -> str:
635
+ def get_decoded(data: Optional[str]) -> str:
635
636
  """Decodes a hex-encoded string."""
637
+ if data is None:
638
+ return ""
636
639
  try:
637
640
  return hex_to_bytes(data).decode()
638
- except UnicodeDecodeError:
641
+ except (UnicodeDecodeError, ValueError):
639
642
  print(f"Could not decode: {key}: {item}")
640
643
 
641
644
  for key, value in info_dictionary.items():
@@ -651,12 +654,14 @@ def decode_hex_identity_dict(info_dictionary) -> dict[str, Any]:
651
654
  if key == "additional":
652
655
  additional = []
653
656
  for item in value:
654
- additional.append(
655
- tuple(
656
- get_decoded(data=next(iter(sub_item.values())))
657
- for sub_item in item
658
- )
659
- )
657
+ if isinstance(item, dict):
658
+ for k, v in item.items():
659
+ additional.append((k, get_decoded(v)))
660
+ else:
661
+ if isinstance(item, (tuple, list)) and len(item) == 2:
662
+ k_, v = item
663
+ k = k_ if k_ is not None else ""
664
+ additional.append((k, get_decoded(v)))
660
665
  info_dictionary[key] = additional
661
666
 
662
667
  return info_dictionary
@@ -65,6 +65,45 @@ async def stake_add(
65
65
  bool: True if stake operation is successful, False otherwise
66
66
  """
67
67
 
68
+ async def get_stake_extrinsic_fee(
69
+ netuid_: int,
70
+ amount_: Balance,
71
+ staking_address_: str,
72
+ safe_staking_: bool,
73
+ price_limit: Optional[Balance] = None,
74
+ ):
75
+ """
76
+ Quick method to get the extrinsic fee for adding stake depending on the args supplied.
77
+ Args:
78
+ netuid_: The netuid where the stake will be added
79
+ amount_: the amount of stake to add
80
+ staking_address_: the hotkey ss58 to stake to
81
+ safe_staking_: whether to use safe staking
82
+ price_limit: rate with tolerance
83
+
84
+ Returns:
85
+ Balance object representing the extrinsic fee for adding stake.
86
+ """
87
+ call_fn = "add_stake" if not safe_staking_ else "add_stake_limit"
88
+ call_params = {
89
+ "hotkey": staking_address_,
90
+ "netuid": netuid_,
91
+ "amount_staked": amount_.rao,
92
+ }
93
+ if safe_staking_:
94
+ call_params.update(
95
+ {
96
+ "limit_price": price_limit,
97
+ "allow_partial": allow_partial_stake,
98
+ }
99
+ )
100
+ call = await subtensor.substrate.compose_call(
101
+ call_module="SubtensorModule",
102
+ call_function=call_fn,
103
+ call_params=call_params,
104
+ )
105
+ return await subtensor.get_extrinsic_fee(call, wallet.coldkeypub)
106
+
68
107
  async def safe_stake_extrinsic(
69
108
  netuid_: int,
70
109
  amount_: Balance,
@@ -87,7 +126,7 @@ async def stake_add(
87
126
  "hotkey": hotkey_ss58_,
88
127
  "netuid": netuid_,
89
128
  "amount_staked": amount_.rao,
90
- "limit_price": price_limit,
129
+ "limit_price": price_limit.rao,
91
130
  "allow_partial": allow_partial_stake,
92
131
  },
93
132
  ),
@@ -332,19 +371,6 @@ async def stake_add(
332
371
  # Temporary workaround - calculations without slippage
333
372
  current_price_float = float(subnet_info.price.tao)
334
373
  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
374
 
349
375
  # If we are staking safe, add price tolerance
350
376
  if safe_staking:
@@ -356,21 +382,45 @@ async def stake_add(
356
382
  rate_with_tolerance = f"{_rate_with_tolerance:.4f}"
357
383
  price_with_tolerance = Balance.from_tao(
358
384
  price_with_tolerance
359
- ).rao # Actual price to pass to extrinsic
385
+ ) # Actual price to pass to extrinsic
360
386
  else:
361
387
  rate_with_tolerance = "1"
362
388
  price_with_tolerance = Balance.from_rao(1)
389
+ extrinsic_fee = await get_stake_extrinsic_fee(
390
+ netuid_=netuid,
391
+ amount_=amount_to_stake,
392
+ staking_address_=hotkey[1],
393
+ safe_staking_=safe_staking,
394
+ price_limit=price_with_tolerance,
395
+ )
363
396
  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
- ]
397
+ row_extension = [
398
+ f"{rate_with_tolerance} {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ",
399
+ f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
400
+ # safe staking
401
+ f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
402
+ ]
403
+ else:
404
+ extrinsic_fee = await get_stake_extrinsic_fee(
405
+ netuid_=netuid,
406
+ amount_=amount_to_stake,
407
+ staking_address_=hotkey[1],
408
+ safe_staking_=safe_staking,
372
409
  )
373
-
410
+ row_extension = []
411
+ received_amount = rate * (amount_to_stake - stake_fee - extrinsic_fee)
412
+ # Add rows for the table
413
+ base_row = [
414
+ str(netuid), # netuid
415
+ f"{hotkey[1]}", # hotkey
416
+ str(amount_to_stake), # amount
417
+ str(rate)
418
+ + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
419
+ str(received_amount.set_unit(netuid)), # received
420
+ str(stake_fee), # fee
421
+ str(extrinsic_fee),
422
+ # str(slippage_pct), # slippage
423
+ ] + row_extension
374
424
  rows.append(tuple(base_row))
375
425
 
376
426
  # Define and print stake table + slippage warning
@@ -569,17 +619,17 @@ def _define_stake_table(
569
619
  "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
570
620
  )
571
621
  table.add_column(
572
- f"Amount ({Balance.get_unit(0)})",
622
+ "Amount (τ)",
573
623
  justify="center",
574
624
  style=COLOR_PALETTE["POOLS"]["TAO"],
575
625
  )
576
626
  table.add_column(
577
- f"Rate (per {Balance.get_unit(0)})",
627
+ "Rate (per τ)",
578
628
  justify="center",
579
629
  style=COLOR_PALETTE["POOLS"]["RATE"],
580
630
  )
581
631
  table.add_column(
582
- "Received",
632
+ "Est. Received",
583
633
  justify="center",
584
634
  style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
585
635
  )
@@ -588,6 +638,11 @@ def _define_stake_table(
588
638
  justify="center",
589
639
  style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
590
640
  )
641
+ table.add_column(
642
+ "Extrinsic Fee (τ)",
643
+ justify="center",
644
+ style=COLOR_PALETTE.STAKE.TAO,
645
+ )
591
646
  # TODO: Uncomment when slippage is reimplemented for v3
592
647
  # table.add_column(
593
648
  # "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]