bittensor-cli 9.10.2__py3-none-any.whl → 9.11.1__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.
@@ -23,6 +23,7 @@ class Constants:
23
23
  dev_entrypoint = "wss://dev.chain.opentensor.ai:443"
24
24
  local_entrypoint = "ws://127.0.0.1:9944"
25
25
  latent_lite_entrypoint = "wss://lite.sub.latent.to:443"
26
+ lite_nodes = [finney_entrypoint, subvortex_entrypoint, latent_lite_entrypoint]
26
27
  network_map = {
27
28
  "finney": finney_entrypoint,
28
29
  "test": finney_test_entrypoint,
@@ -88,12 +89,14 @@ class Defaults:
88
89
  class config:
89
90
  base_path = "~/.bittensor"
90
91
  path = "~/.bittensor/config.yml"
92
+ debug_file_path = "~/.bittensor/debug.txt"
91
93
  dictionary = {
92
94
  "network": None,
93
95
  "wallet_path": None,
94
96
  "wallet_name": None,
95
97
  "wallet_hotkey": None,
96
98
  "use_cache": True,
99
+ "disk_cache": False,
97
100
  "metagraph_cols": {
98
101
  "UID": True,
99
102
  "GLOBAL_STAKE": True,
@@ -638,7 +641,7 @@ HYPERPARAMS = {
638
641
  "adjustment_interval": ("sudo_set_adjustment_interval", True),
639
642
  "activity_cutoff": ("sudo_set_activity_cutoff", False),
640
643
  "target_regs_per_interval": ("sudo_set_target_registrations_per_interval", True),
641
- "min_burn": ("sudo_set_min_burn", True),
644
+ "min_burn": ("sudo_set_min_burn", False),
642
645
  "max_burn": ("sudo_set_max_burn", True),
643
646
  "bonds_moving_avg": ("sudo_set_bonds_moving_average", False),
644
647
  "max_regs_per_block": ("sudo_set_max_registrations_per_block", True),
@@ -1193,3 +1193,20 @@ class MetagraphInfo(InfoBase):
1193
1193
  for adphk in decoded["alpha_dividends_per_hotkey"]
1194
1194
  ],
1195
1195
  )
1196
+
1197
+
1198
+ @dataclass
1199
+ class SimSwapResult:
1200
+ tao_amount: Balance
1201
+ alpha_amount: Balance
1202
+ tao_fee: Balance
1203
+ alpha_fee: Balance
1204
+
1205
+ @classmethod
1206
+ def from_dict(cls, d: dict, netuid: int) -> "SimSwapResult":
1207
+ return cls(
1208
+ tao_amount=Balance.from_rao(d["tao_amount"]).set_unit(0),
1209
+ alpha_amount=Balance.from_rao(d["alpha_amount"]).set_unit(netuid),
1210
+ tao_fee=Balance.from_rao(d["tao_fee"]).set_unit(0),
1211
+ alpha_fee=Balance.from_rao(d["alpha_fee"]).set_unit(netuid),
1212
+ )
@@ -175,7 +175,9 @@ async def transfer_extrinsic(
175
175
  f" amount: [bright_cyan]{amount if not transfer_all else account_balance}[/bright_cyan]\n"
176
176
  f" from: [light_goldenrod2]{wallet.name}[/light_goldenrod2] : "
177
177
  f"[bright_magenta]{wallet.coldkey.ss58_address}\n[/bright_magenta]"
178
- f" to: [bright_magenta]{destination}[/bright_magenta]\n for fee: [bright_cyan]{fee}[/bright_cyan]"
178
+ f" to: [bright_magenta]{destination}[/bright_magenta]\n for fee: [bright_cyan]{fee}[/bright_cyan]\n"
179
+ f":warning:[bright_yellow]Transferring is not the same as staking. To instead stake, use "
180
+ f"[dark_orange]btcli stake add[/dark_orange] instead[/bright_yellow]:warning:"
179
181
  ):
180
182
  return False
181
183
 
@@ -1,21 +1,22 @@
1
1
  import asyncio
2
2
  import os
3
+ import time
3
4
  from typing import Optional, Any, Union, TypedDict, Iterable
4
5
 
5
6
  import aiohttp
7
+ from async_substrate_interface.async_substrate import (
8
+ DiskCachedAsyncSubstrateInterface,
9
+ AsyncSubstrateInterface,
10
+ )
11
+ from async_substrate_interface.errors import SubstrateRequestException
6
12
  from async_substrate_interface.utils.storage import StorageKey
7
13
  from bittensor_wallet import Wallet
8
14
  from bittensor_wallet.bittensor_wallet import Keypair
9
15
  from bittensor_wallet.utils import SS58_FORMAT
10
16
  from scalecodec import GenericCall
11
- from async_substrate_interface.errors import SubstrateRequestException
12
17
  import typer
18
+ import websockets
13
19
 
14
-
15
- from async_substrate_interface.async_substrate import (
16
- DiskCachedAsyncSubstrateInterface,
17
- AsyncSubstrateInterface,
18
- )
19
20
  from bittensor_cli.src.bittensor.chain_data import (
20
21
  DelegateInfo,
21
22
  StakeInfo,
@@ -27,6 +28,7 @@ from bittensor_cli.src.bittensor.chain_data import (
27
28
  DynamicInfo,
28
29
  SubnetState,
29
30
  MetagraphInfo,
31
+ SimSwapResult,
30
32
  )
31
33
  from bittensor_cli.src import DelegatesDetails
32
34
  from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float
@@ -42,12 +44,6 @@ from bittensor_cli.src.bittensor.utils import (
42
44
  get_hotkey_pub_ss58,
43
45
  )
44
46
 
45
- SubstrateClass = (
46
- DiskCachedAsyncSubstrateInterface
47
- if os.getenv("DISK_CACHE", "0") == "1"
48
- else AsyncSubstrateInterface
49
- )
50
-
51
47
 
52
48
  class ParamWithTypes(TypedDict):
53
49
  name: str # Name of the parameter.
@@ -81,7 +77,7 @@ class SubtensorInterface:
81
77
  Thin layer for interacting with Substrate Interface. Mostly a collection of frequently-used calls.
82
78
  """
83
79
 
84
- def __init__(self, network):
80
+ def __init__(self, network, use_disk_cache: bool = False):
85
81
  if network in Constants.network_map:
86
82
  self.chain_endpoint = Constants.network_map[network]
87
83
  self.network = network
@@ -111,8 +107,12 @@ class SubtensorInterface:
111
107
  )
112
108
  self.chain_endpoint = Constants.network_map[defaults.subtensor.network]
113
109
  self.network = defaults.subtensor.network
114
-
115
- self.substrate = SubstrateClass(
110
+ substrate_class = (
111
+ DiskCachedAsyncSubstrateInterface
112
+ if (use_disk_cache or os.getenv("DISK_CACHE", "0") == "1")
113
+ else AsyncSubstrateInterface
114
+ )
115
+ self.substrate = substrate_class(
116
116
  url=self.chain_endpoint,
117
117
  ss58_format=SS58_FORMAT,
118
118
  type_registry=TYPE_REGISTRY,
@@ -1503,59 +1503,79 @@ class SubtensorInterface:
1503
1503
  fee_dict = await self.substrate.get_payment_info(call, keypair)
1504
1504
  return Balance.from_rao(fee_dict["partial_fee"])
1505
1505
 
1506
- async def get_stake_fee(
1506
+ async def sim_swap(
1507
1507
  self,
1508
- origin_hotkey_ss58: Optional[str],
1509
- origin_netuid: Optional[int],
1510
- origin_coldkey_ss58: str,
1511
- destination_hotkey_ss58: Optional[str],
1512
- destination_netuid: Optional[int],
1513
- destination_coldkey_ss58: str,
1508
+ origin_netuid: int,
1509
+ destination_netuid: int,
1514
1510
  amount: int,
1515
1511
  block_hash: Optional[str] = None,
1516
- ) -> Balance:
1512
+ ) -> SimSwapResult:
1517
1513
  """
1518
- Calculates the fee for a staking operation.
1519
-
1520
- :param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake)
1521
- :param origin_netuid: Netuid of source subnet (None for new stake)
1522
- :param origin_coldkey_ss58: SS58 address of source coldkey
1523
- :param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake)
1524
- :param destination_netuid: Netuid of destination subnet (None for removing stake)
1525
- :param destination_coldkey_ss58: SS58 address of destination coldkey
1526
- :param amount: Amount of stake to transfer in RAO
1527
- :param block_hash: Optional block hash at which to perform the calculation
1528
-
1529
- :return: The calculated stake fee as a Balance object
1530
-
1531
- When to use None:
1532
-
1533
- 1. Adding new stake (default fee):
1534
- - origin_hotkey_ss58 = None
1535
- - origin_netuid = None
1536
- - All other fields required
1514
+ Hits the SimSwap Runtime API to calculate the fee and result for a given transaction. This should be used
1515
+ instead of get_stake_fee for staking fee calculations. The SimSwapResult contains the staking fees and expected
1516
+ returned amounts of a given transaction. This does not include the transaction (extrinsic) fee.
1537
1517
 
1538
- 2. Removing stake (default fee):
1539
- - destination_hotkey_ss58 = None
1540
- - destination_netuid = None
1541
- - All other fields required
1518
+ Args:
1519
+ origin_netuid: Netuid of the source subnet (0 if new stake)
1520
+ destination_netuid: Netuid of the destination subnet
1521
+ amount: Amount to transfer in Rao
1522
+ block_hash: The hash of the blockchain block number for the query.
1542
1523
 
1543
- For all other operations, no None values - provide all parameters:
1544
- 3. Moving between subnets
1545
- 4. Moving between hotkeys
1546
- 5. Moving between coldkeys
1524
+ Returns:
1525
+ SimSwapResult object representing the result
1547
1526
  """
1548
-
1549
- if origin_netuid is None:
1550
- origin_netuid = 0
1551
-
1552
- fee_rate = await self.query("Swap", "FeeRate", [origin_netuid])
1553
- fee = amount * (fee_rate / U16_MAX)
1554
-
1555
- result = Balance.from_rao(fee)
1556
- result.set_unit(origin_netuid)
1557
-
1558
- return result
1527
+ block_hash = block_hash or await self.substrate.get_chain_head()
1528
+ if origin_netuid > 0 and destination_netuid > 0:
1529
+ # for cross-subnet moves where neither origin nor destination is root
1530
+ intermediate_result_, sn_price = await asyncio.gather(
1531
+ self.query_runtime_api(
1532
+ "SwapRuntimeApi",
1533
+ "sim_swap_alpha_for_tao",
1534
+ params={"netuid": origin_netuid, "alpha": amount},
1535
+ block_hash=block_hash,
1536
+ ),
1537
+ self.get_subnet_price(origin_netuid, block_hash=block_hash),
1538
+ )
1539
+ intermediate_result = SimSwapResult.from_dict(
1540
+ intermediate_result_, origin_netuid
1541
+ )
1542
+ result = SimSwapResult.from_dict(
1543
+ await self.query_runtime_api(
1544
+ "SwapRuntimeApi",
1545
+ "sim_swap_tao_for_alpha",
1546
+ params={
1547
+ "netuid": destination_netuid,
1548
+ "tao": intermediate_result.tao_amount,
1549
+ },
1550
+ block_hash=block_hash,
1551
+ ),
1552
+ destination_netuid,
1553
+ )
1554
+ secondary_fee = (result.tao_fee * sn_price).set_unit(origin_netuid)
1555
+ result.alpha_fee = result.alpha_fee + secondary_fee
1556
+ return result
1557
+ elif origin_netuid > 0:
1558
+ # dynamic to tao
1559
+ return SimSwapResult.from_dict(
1560
+ await self.query_runtime_api(
1561
+ "SwapRuntimeApi",
1562
+ "sim_swap_alpha_for_tao",
1563
+ params={"netuid": origin_netuid, "alpha": amount},
1564
+ block_hash=block_hash,
1565
+ ),
1566
+ origin_netuid,
1567
+ )
1568
+ else:
1569
+ # tao to dynamic or unstaked to staked tao (SN0)
1570
+ return SimSwapResult.from_dict(
1571
+ await self.query_runtime_api(
1572
+ "SwapRuntimeApi",
1573
+ "sim_swap_tao_for_alpha",
1574
+ params={"netuid": destination_netuid, "tao": amount},
1575
+ block_hash=block_hash,
1576
+ ),
1577
+ destination_netuid,
1578
+ )
1559
1579
 
1560
1580
  async def get_scheduled_coldkey_swap(
1561
1581
  self,
@@ -1656,3 +1676,32 @@ class SubtensorInterface:
1656
1676
  map_[netuid_] = Balance.from_rao(int(current_price * 1e9))
1657
1677
 
1658
1678
  return map_
1679
+
1680
+
1681
+ async def best_connection(networks: list[str]):
1682
+ """
1683
+ Basic function to compare the latency of a given list of websocket endpoints
1684
+ Args:
1685
+ networks: list of network URIs
1686
+
1687
+ Returns:
1688
+ {network_name: [end_to_end_latency, single_request_latency, chain_head_request_latency]}
1689
+
1690
+ """
1691
+ results = {}
1692
+ for network in networks:
1693
+ try:
1694
+ t1 = time.monotonic()
1695
+ async with websockets.connect(network) as websocket:
1696
+ pong = await websocket.ping()
1697
+ latency = await pong
1698
+ pt1 = time.monotonic()
1699
+ await websocket.send(
1700
+ "{'jsonrpc': '2.0', 'method': 'chain_getHead', 'params': [], 'id': '82'}"
1701
+ )
1702
+ await websocket.recv()
1703
+ t2 = time.monotonic()
1704
+ results[network] = [t2 - t1, latency, t2 - pt1]
1705
+ except Exception as e:
1706
+ err_console.print(f"Error attempting network {network}: {e}")
1707
+ return results
@@ -266,13 +266,29 @@ def get_hotkey_wallets_for_wallet(
266
266
  hotkeys_path = wallet_path / wallet.name / "hotkeys"
267
267
  try:
268
268
  hotkeys = [entry.name for entry in hotkeys_path.iterdir()]
269
- except FileNotFoundError:
269
+ except (FileNotFoundError, NotADirectoryError):
270
270
  hotkeys = []
271
271
  for h_name in hotkeys:
272
- hotkey_for_name = Wallet(path=str(wallet_path), name=wallet.name, hotkey=h_name)
272
+ if h_name.endswith("pub.txt"):
273
+ if h_name.split("pub.txt")[0] in hotkeys:
274
+ continue
275
+ else:
276
+ hotkey_for_name = Wallet(
277
+ path=str(wallet_path),
278
+ name=wallet.name,
279
+ hotkey=h_name.split("pub.txt")[0],
280
+ )
281
+ else:
282
+ hotkey_for_name = Wallet(
283
+ path=str(wallet_path), name=wallet.name, hotkey=h_name
284
+ )
273
285
  try:
286
+ exists = (
287
+ hotkey_for_name.hotkey_file.exists_on_device()
288
+ or hotkey_for_name.hotkeypub_file.exists_on_device()
289
+ )
274
290
  if (
275
- (exists := hotkey_for_name.hotkey_file.exists_on_device())
291
+ exists
276
292
  and not hotkey_for_name.hotkey_file.is_encrypted()
277
293
  # and hotkey_for_name.coldkeypub.ss58_address
278
294
  and get_hotkey_pub_ss58(hotkey_for_name)
@@ -291,6 +307,7 @@ def get_hotkey_wallets_for_wallet(
291
307
  AttributeError,
292
308
  TypeError,
293
309
  KeyFileError,
310
+ ValueError,
294
311
  ): # usually an unrelated file like .DS_Store
295
312
  continue
296
313
 
@@ -347,17 +347,6 @@ async def stake_add(
347
347
  return False
348
348
  remaining_wallet_balance -= amount_to_stake
349
349
 
350
- # TODO this should be asyncio gathered before the for loop
351
- stake_fee = await subtensor.get_stake_fee(
352
- origin_hotkey_ss58=None,
353
- origin_netuid=None,
354
- origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
355
- destination_hotkey_ss58=hotkey[1],
356
- destination_netuid=netuid,
357
- destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
358
- amount=amount_to_stake.rao,
359
- )
360
-
361
350
  # Calculate slippage
362
351
  # TODO: Update for V3, slippage calculation is significantly different in v3
363
352
  # try:
@@ -409,7 +398,13 @@ async def stake_add(
409
398
  safe_staking_=safe_staking,
410
399
  )
411
400
  row_extension = []
412
- received_amount = rate * (amount_to_stake - stake_fee - extrinsic_fee)
401
+ # TODO this should be asyncio gathered before the for loop
402
+ sim_swap = await subtensor.sim_swap(
403
+ origin_netuid=0,
404
+ destination_netuid=netuid,
405
+ amount=(amount_to_stake - extrinsic_fee).rao,
406
+ )
407
+ received_amount = sim_swap.alpha_amount
413
408
  # Add rows for the table
414
409
  base_row = [
415
410
  str(netuid), # netuid
@@ -418,7 +413,7 @@ async def stake_add(
418
413
  str(rate)
419
414
  + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
420
415
  str(received_amount.set_unit(netuid)), # received
421
- str(stake_fee), # fee
416
+ str(sim_swap.tao_fee), # fee
422
417
  str(extrinsic_fee),
423
418
  # str(slippage_pct), # slippage
424
419
  ] + row_extension
@@ -520,14 +520,10 @@ async def move_stake(
520
520
  "alpha_amount": amount_to_move_as_balance.rao,
521
521
  },
522
522
  )
523
- stake_fee, extrinsic_fee = await asyncio.gather(
524
- subtensor.get_stake_fee(
525
- origin_hotkey_ss58=origin_hotkey,
523
+ sim_swap, extrinsic_fee = await asyncio.gather(
524
+ subtensor.sim_swap(
526
525
  origin_netuid=origin_netuid,
527
- origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
528
- destination_hotkey_ss58=destination_hotkey,
529
526
  destination_netuid=destination_netuid,
530
- destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
531
527
  amount=amount_to_move_as_balance.rao,
532
528
  ),
533
529
  subtensor.get_extrinsic_fee(call, wallet.coldkeypub),
@@ -543,7 +539,9 @@ async def move_stake(
543
539
  origin_hotkey=origin_hotkey,
544
540
  destination_hotkey=destination_hotkey,
545
541
  amount_to_move=amount_to_move_as_balance,
546
- stake_fee=stake_fee,
542
+ stake_fee=sim_swap.alpha_fee
543
+ if origin_netuid != 0
544
+ else sim_swap.tao_fee,
547
545
  extrinsic_fee=extrinsic_fee,
548
546
  )
549
547
  except ValueError:
@@ -709,14 +707,10 @@ async def transfer_stake(
709
707
  "alpha_amount": amount_to_transfer.rao,
710
708
  },
711
709
  )
712
- stake_fee, extrinsic_fee = await asyncio.gather(
713
- subtensor.get_stake_fee(
714
- origin_hotkey_ss58=origin_hotkey,
710
+ sim_swap, extrinsic_fee = await asyncio.gather(
711
+ subtensor.sim_swap(
715
712
  origin_netuid=origin_netuid,
716
- origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
717
- destination_hotkey_ss58=origin_hotkey,
718
713
  destination_netuid=dest_netuid,
719
- destination_coldkey_ss58=dest_coldkey_ss58,
720
714
  amount=amount_to_transfer.rao,
721
715
  ),
722
716
  subtensor.get_extrinsic_fee(call, wallet.coldkeypub),
@@ -732,7 +726,9 @@ async def transfer_stake(
732
726
  origin_hotkey=origin_hotkey,
733
727
  destination_hotkey=origin_hotkey,
734
728
  amount_to_move=amount_to_transfer,
735
- stake_fee=stake_fee,
729
+ stake_fee=sim_swap.alpha_fee
730
+ if origin_netuid != 0
731
+ else sim_swap.tao_fee,
736
732
  extrinsic_fee=extrinsic_fee,
737
733
  )
738
734
  except ValueError:
@@ -880,14 +876,10 @@ async def swap_stake(
880
876
  "alpha_amount": amount_to_swap.rao,
881
877
  },
882
878
  )
883
- stake_fee, extrinsic_fee = await asyncio.gather(
884
- subtensor.get_stake_fee(
885
- origin_hotkey_ss58=hotkey_ss58,
879
+ sim_swap, extrinsic_fee = await asyncio.gather(
880
+ subtensor.sim_swap(
886
881
  origin_netuid=origin_netuid,
887
- origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
888
- destination_hotkey_ss58=hotkey_ss58,
889
882
  destination_netuid=destination_netuid,
890
- destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
891
883
  amount=amount_to_swap.rao,
892
884
  ),
893
885
  subtensor.get_extrinsic_fee(call, wallet.coldkeypub),
@@ -903,7 +895,9 @@ async def swap_stake(
903
895
  origin_hotkey=hotkey_ss58,
904
896
  destination_hotkey=hotkey_ss58,
905
897
  amount_to_move=amount_to_swap,
906
- stake_fee=stake_fee,
898
+ stake_fee=sim_swap.alpha_fee
899
+ if origin_netuid != 0
900
+ else sim_swap.tao_fee,
907
901
  extrinsic_fee=extrinsic_fee,
908
902
  )
909
903
  except ValueError:
@@ -200,16 +200,6 @@ async def unstake(
200
200
  )
201
201
  continue # Skip to the next subnet - useful when single amount is specified for all subnets
202
202
 
203
- stake_fee = await subtensor.get_stake_fee(
204
- origin_hotkey_ss58=staking_address_ss58,
205
- origin_netuid=netuid,
206
- origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
207
- destination_hotkey_ss58=None,
208
- destination_netuid=None,
209
- destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
210
- amount=amount_to_unstake_as_balance.rao,
211
- )
212
-
213
203
  try:
214
204
  current_price = subnet_info.price.tao
215
205
  if safe_staking:
@@ -240,10 +230,10 @@ async def unstake(
240
230
  netuid=netuid,
241
231
  amount=amount_to_unstake_as_balance,
242
232
  )
243
- rate = current_price
244
- received_amount = (
245
- (amount_to_unstake_as_balance - stake_fee) * rate
246
- ) - extrinsic_fee
233
+ sim_swap = await subtensor.sim_swap(
234
+ netuid, 0, amount_to_unstake_as_balance.rao
235
+ )
236
+ received_amount = sim_swap.tao_amount - extrinsic_fee
247
237
  except ValueError:
248
238
  continue
249
239
  total_received_amount += received_amount
@@ -266,7 +256,7 @@ async def unstake(
266
256
  str(amount_to_unstake_as_balance), # Amount to Unstake
267
257
  f"{subnet_info.price.tao:.6f}"
268
258
  + f"(τ/{Balance.get_unit(netuid)})", # Rate
269
- str(stake_fee.set_unit(netuid)), # Fee
259
+ str(sim_swap.alpha_fee), # Fee
270
260
  str(extrinsic_fee), # Extrinsic fee
271
261
  str(received_amount), # Received Amount
272
262
  # slippage_pct, # Slippage Percent
@@ -361,6 +351,7 @@ async def unstake(
361
351
  )
362
352
  if json_output:
363
353
  json_console.print(json.dumps(successes))
354
+ return True
364
355
 
365
356
 
366
357
  async def unstake_all(
@@ -493,15 +484,6 @@ async def unstake_all(
493
484
  hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58)
494
485
  subnet_info = all_sn_dynamic_info.get(stake.netuid)
495
486
  stake_amount = stake.stake
496
- stake_fee = await subtensor.get_stake_fee(
497
- origin_hotkey_ss58=stake.hotkey_ss58,
498
- origin_netuid=stake.netuid,
499
- origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
500
- destination_hotkey_ss58=None,
501
- destination_netuid=None,
502
- destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
503
- amount=stake_amount.rao,
504
- )
505
487
 
506
488
  try:
507
489
  current_price = subnet_info.price.tao
@@ -514,8 +496,8 @@ async def unstake_all(
514
496
  subtensor,
515
497
  hotkey_ss58=stake.hotkey_ss58,
516
498
  )
517
- rate = current_price
518
- received_amount = ((stake_amount - stake_fee) * rate) - extrinsic_fee
499
+ sim_swap = await subtensor.sim_swap(stake.netuid, 0, stake_amount.rao)
500
+ received_amount = sim_swap.tao_amount - extrinsic_fee
519
501
 
520
502
  if received_amount < Balance.from_tao(0):
521
503
  print_error("Not enough Alpha to pay the transaction fee.")
@@ -531,7 +513,7 @@ async def unstake_all(
531
513
  str(stake_amount),
532
514
  f"{float(subnet_info.price):.6f}"
533
515
  + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})",
534
- str(stake_fee),
516
+ str(sim_swap.alpha_fee),
535
517
  str(extrinsic_fee),
536
518
  str(received_amount),
537
519
  )
@@ -52,7 +52,17 @@ async def price(
52
52
 
53
53
  step = 300
54
54
  start_block = max(0, current_block - total_blocks)
55
- block_numbers = list(range(start_block, current_block + 1, step))
55
+
56
+ # snap start block down to nearest multiple of 10
57
+ start_block -= start_block % 10
58
+
59
+ block_numbers = []
60
+ for b in range(start_block, current_block + 1, step):
61
+ if b == current_block:
62
+ block_numbers.append(b) # exact current block
63
+ else:
64
+ block_numbers.append(b - (b % 5)) # snap down to multiple of 10
65
+ block_numbers = sorted(set(block_numbers))
56
66
 
57
67
  # Block hashes
58
68
  block_hash_cors = [
@@ -2448,3 +2448,71 @@ async def start_subnet(
2448
2448
  await get_start_schedule(subtensor, netuid)
2449
2449
  print_error(f":cross_mark: Failed to start subnet: {error_msg}")
2450
2450
  return False
2451
+
2452
+
2453
+ async def set_symbol(
2454
+ wallet: "Wallet",
2455
+ subtensor: "SubtensorInterface",
2456
+ netuid: int,
2457
+ symbol: str,
2458
+ prompt: bool = False,
2459
+ json_output: bool = False,
2460
+ ) -> bool:
2461
+ """
2462
+ Set a subtensor's symbol, given the netuid and symbol.
2463
+
2464
+ The symbol must be a symbol that subtensor recognizes as available
2465
+ (defined in https://github.com/opentensor/subtensor/blob/main/pallets/subtensor/src/subnets/symbols.rs#L8)
2466
+ """
2467
+ if not await subtensor.subnet_exists(netuid):
2468
+ err = f"Subnet {netuid} does not exist."
2469
+ if json_output:
2470
+ json_console.print_json(data={"success": False, "message": err})
2471
+ else:
2472
+ err_console.print(err)
2473
+ return False
2474
+
2475
+ if prompt and not json_output:
2476
+ sn_info = await subtensor.subnet(netuid=netuid)
2477
+ if not Confirm.ask(
2478
+ f"Your current subnet symbol for SN{netuid} is {sn_info.symbol}. Do you want to update it to {symbol}?"
2479
+ ):
2480
+ return False
2481
+
2482
+ if not (unlock_status := unlock_key(wallet, print_out=False)).success:
2483
+ err = unlock_status.message
2484
+ if json_output:
2485
+ json_console.print_json(data={"success": False, "message": err})
2486
+ else:
2487
+ console.print(err)
2488
+ return False
2489
+
2490
+ start_call = await subtensor.substrate.compose_call(
2491
+ call_module="SubtensorModule",
2492
+ call_function="update_symbol",
2493
+ call_params={"netuid": netuid, "symbol": symbol.encode("utf-8")},
2494
+ )
2495
+
2496
+ signed_ext = await subtensor.substrate.create_signed_extrinsic(
2497
+ call=start_call,
2498
+ keypair=wallet.coldkey,
2499
+ )
2500
+
2501
+ response = await subtensor.substrate.submit_extrinsic(
2502
+ extrinsic=signed_ext,
2503
+ wait_for_inclusion=True,
2504
+ )
2505
+ if await response.is_success:
2506
+ message = f"Successfully updated SN{netuid}'s symbol to {symbol}."
2507
+ if json_output:
2508
+ json_console.print_json(data={"success": True, "message": message})
2509
+ else:
2510
+ console.print(f":white_heavy_check_mark:[dark_sea_green3] {message}\n")
2511
+ return True
2512
+ else:
2513
+ err = format_error_message(await response.error_message)
2514
+ if json_output:
2515
+ json_console.print_json(data={"success": False, "message": err})
2516
+ else:
2517
+ err_console.print(f":cross_mark: [red]Failed[/red]: {err}")
2518
+ return False
@@ -34,6 +34,7 @@ if TYPE_CHECKING:
34
34
  SubtensorInterface,
35
35
  ProposalVoteData,
36
36
  )
37
+ from scalecodec.types import GenericMetadataVersioned
37
38
 
38
39
 
39
40
  # helpers and extrinsics
@@ -91,8 +92,8 @@ def search_metadata(
91
92
  param_name: str,
92
93
  value: Union[str, bool, float, list[float]],
93
94
  netuid: int,
94
- metadata,
95
- pallet: str = DEFAULT_PALLET,
95
+ metadata: "GenericMetadataVersioned",
96
+ pallet_name: str = DEFAULT_PALLET,
96
97
  ) -> tuple[bool, Optional[dict]]:
97
98
  """
98
99
  Searches the substrate metadata AdminUtils pallet for a given parameter name. Crafts a response dict to be used
@@ -103,7 +104,7 @@ def search_metadata(
103
104
  value: the value to set the hyperparameter
104
105
  netuid: the specified netuid
105
106
  metadata: the subtensor.substrate.metadata
106
- pallet: the name of the module to use for the query. If not set, the default value is DEFAULT_PALLET
107
+ pallet_name: the name of the module to use for the query. If not set, the default value is DEFAULT_PALLET
107
108
 
108
109
  Returns:
109
110
  (success, dict of call params)
@@ -125,7 +126,7 @@ def search_metadata(
125
126
 
126
127
  call_crafter = {"netuid": netuid}
127
128
 
128
- pallet = metadata.get_metadata_pallet(pallet)
129
+ pallet = metadata.get_metadata_pallet(pallet_name)
129
130
  for call in pallet.calls:
130
131
  if call.name == param_name:
131
132
  if "netuid" not in [x.name for x in call.args]: