bittensor-cli 8.1.1__py3-none-any.whl → 8.3.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.
@@ -4,16 +4,18 @@ from typing import Any, Optional
4
4
 
5
5
 
6
6
  class Constants:
7
- networks = ["local", "finney", "test", "archive"]
7
+ networks = ["local", "finney", "test", "archive", "subvortex"]
8
8
  finney_entrypoint = "wss://entrypoint-finney.opentensor.ai:443"
9
9
  finney_test_entrypoint = "wss://test.finney.opentensor.ai:443"
10
10
  archive_entrypoint = "wss://archive.chain.opentensor.ai:443"
11
- local_entrypoint = "ws://127.0.0.1:9444"
11
+ subvortex_entrypoint = "ws://subvortex.info:9944"
12
+ local_entrypoint = "ws://127.0.0.1:9944"
12
13
  network_map = {
13
14
  "finney": finney_entrypoint,
14
15
  "test": finney_test_entrypoint,
15
16
  "archive": archive_entrypoint,
16
17
  "local": local_entrypoint,
18
+ "subvortex": subvortex_entrypoint,
17
19
  }
18
20
  delegates_detail_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json"
19
21
 
@@ -460,6 +460,9 @@ class Runtime:
460
460
  self.runtime_config = runtime_config
461
461
  self.metadata = metadata
462
462
 
463
+ def __str__(self):
464
+ return f"Runtime: {self.chain} | {self.config}"
465
+
463
466
  @property
464
467
  def implements_scaleinfo(self) -> bool:
465
468
  """
@@ -679,12 +682,12 @@ class Websocket:
679
682
 
680
683
  async def shutdown(self):
681
684
  async with self._lock:
682
- self._receiving_task.cancel()
683
685
  try:
686
+ self._receiving_task.cancel()
684
687
  await self._receiving_task
685
- except asyncio.CancelledError:
688
+ await self.ws.close()
689
+ except (AttributeError, asyncio.CancelledError):
686
690
  pass
687
- await self.ws.close()
688
691
  self.ws = None
689
692
  self._initialized = False
690
693
  self._receiving_task = None
@@ -897,9 +900,10 @@ class AsyncSubstrateInterface:
897
900
 
898
901
  async def get_runtime(block_hash, block_id) -> Runtime:
899
902
  # Check if runtime state already set to current block
900
- if (block_hash and block_hash == self.last_block_hash) or (
901
- block_id and block_id == self.block_id
902
- ):
903
+ if (
904
+ (block_hash and block_hash == self.last_block_hash)
905
+ or (block_id and block_id == self.block_id)
906
+ ) and self.metadata is not None:
903
907
  return Runtime(
904
908
  self.chain,
905
909
  self.runtime_config,
@@ -945,9 +949,11 @@ class AsyncSubstrateInterface:
945
949
  raise SubstrateRequestException(
946
950
  f"No runtime information for block '{block_hash}'"
947
951
  )
948
-
949
952
  # Check if runtime state already set to current block
950
- if runtime_info.get("specVersion") == self.runtime_version:
953
+ if (
954
+ runtime_info.get("specVersion") == self.runtime_version
955
+ and self.metadata is not None
956
+ ):
951
957
  return Runtime(
952
958
  self.chain,
953
959
  self.runtime_config,
@@ -962,16 +968,19 @@ class AsyncSubstrateInterface:
962
968
  if self.runtime_version in self.__metadata_cache:
963
969
  # Get metadata from cache
964
970
  # self.debug_message('Retrieved metadata for {} from memory'.format(self.runtime_version))
965
- self.metadata = self.__metadata_cache[self.runtime_version]
971
+ metadata = self.metadata = self.__metadata_cache[
972
+ self.runtime_version
973
+ ]
966
974
  else:
967
- self.metadata = await self.get_block_metadata(
975
+ metadata = self.metadata = await self.get_block_metadata(
968
976
  block_hash=runtime_block_hash, decode=True
969
977
  )
970
978
  # self.debug_message('Retrieved metadata for {} from Substrate node'.format(self.runtime_version))
971
979
 
972
980
  # Update metadata cache
973
981
  self.__metadata_cache[self.runtime_version] = self.metadata
974
-
982
+ else:
983
+ metadata = self.metadata
975
984
  # Update type registry
976
985
  self.reload_type_registry(use_remote_preset=False, auto_discover=True)
977
986
 
@@ -1012,7 +1021,10 @@ class AsyncSubstrateInterface:
1012
1021
  if block_id and block_hash:
1013
1022
  raise ValueError("Cannot provide block_hash and block_id at the same time")
1014
1023
 
1015
- if not (runtime := self.runtime_cache.retrieve(block_id, block_hash)):
1024
+ if (
1025
+ not (runtime := self.runtime_cache.retrieve(block_id, block_hash))
1026
+ or runtime.metadata is None
1027
+ ):
1016
1028
  runtime = await get_runtime(block_hash, block_id)
1017
1029
  self.runtime_cache.add_item(block_id, block_hash, runtime)
1018
1030
  return runtime
@@ -1123,7 +1135,7 @@ class AsyncSubstrateInterface:
1123
1135
  -------
1124
1136
  StorageKey
1125
1137
  """
1126
- await self.init_runtime(block_hash=block_hash)
1138
+ runtime = await self.init_runtime(block_hash=block_hash)
1127
1139
 
1128
1140
  return StorageKey.create_from_storage_function(
1129
1141
  pallet,
@@ -1707,9 +1719,7 @@ class AsyncSubstrateInterface:
1707
1719
  )
1708
1720
  result = await self._make_rpc_request(payloads, runtime=runtime)
1709
1721
  if "error" in result[payload_id][0]:
1710
- raise SubstrateRequestException(
1711
- result["rpc_request"][0]["error"]["message"]
1712
- )
1722
+ raise SubstrateRequestException(result[payload_id][0]["error"]["message"])
1713
1723
  if "result" in result[payload_id][0]:
1714
1724
  return result[payload_id][0]
1715
1725
  else:
@@ -2274,7 +2284,7 @@ class AsyncSubstrateInterface:
2274
2284
  MetadataModuleConstants
2275
2285
  """
2276
2286
 
2277
- # await self.init_runtime(block_hash=block_hash)
2287
+ await self.init_runtime(block_hash=block_hash)
2278
2288
 
2279
2289
  for module in self.metadata.pallets:
2280
2290
  if module_name == module.name and module.constants:
@@ -16,6 +16,7 @@ import random
16
16
  import time
17
17
  import typing
18
18
  from typing import Optional
19
+ import subprocess
19
20
 
20
21
  import backoff
21
22
  from bittensor_wallet import Wallet
@@ -35,6 +36,7 @@ from bittensor_cli.src.bittensor.utils import (
35
36
  millify,
36
37
  get_human_readable,
37
38
  print_verbose,
39
+ print_error,
38
40
  )
39
41
 
40
42
  if typing.TYPE_CHECKING:
@@ -512,12 +514,13 @@ async def register_extrinsic(
512
514
  with console.status(
513
515
  f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
514
516
  spinner="aesthetic",
515
- ):
517
+ ) as status:
516
518
  neuron = await get_neuron_for_pubkey_and_subnet()
517
519
  if not neuron.is_null:
518
- # bittensor.logging.debug(
519
- # f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}"
520
- # )
520
+ print_error(
521
+ f"Wallet {wallet} is already registered on subnet {neuron.netuid} with uid {neuron.uid}",
522
+ status,
523
+ )
521
524
  return True
522
525
 
523
526
  if prompt:
@@ -611,28 +614,32 @@ async def register_extrinsic(
611
614
  success, err_msg = True, ""
612
615
  else:
613
616
  await response.process_events()
614
- if not await response.is_success:
617
+ success = await response.is_success
618
+ if not success:
615
619
  success, err_msg = (
616
620
  False,
617
- format_error_message(await response.error_message),
621
+ format_error_message(
622
+ await response.error_message,
623
+ substrate=subtensor.substrate,
624
+ ),
618
625
  )
619
-
620
- if not success:
621
- # Look error here
622
- # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs
623
- if "HotKeyAlreadyRegisteredInSubNet" in err_msg:
624
- console.print(
625
- f":white_heavy_check_mark: [green]Already Registered on "
626
- f"[bold]subnet:{netuid}[/bold][/green]"
626
+ # Look error here
627
+ # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs
628
+
629
+ if "HotKeyAlreadyRegisteredInSubNet" in err_msg:
630
+ console.print(
631
+ f":white_heavy_check_mark: [green]Already Registered on "
632
+ f"[bold]subnet:{netuid}[/bold][/green]"
633
+ )
634
+ return True
635
+ err_console.print(
636
+ f":cross_mark: [red]Failed[/red]: {err_msg}"
627
637
  )
628
- return True
629
-
630
- err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
631
- await asyncio.sleep(0.5)
638
+ await asyncio.sleep(0.5)
632
639
 
633
640
  # Successful registration, final check for neuron and pubkey
634
- else:
635
- console.print(":satellite: Checking Balance...")
641
+ if success:
642
+ console.print(":satellite: Checking Registration status...")
636
643
  is_registered = await is_hotkey_registered(
637
644
  subtensor,
638
645
  netuid=netuid,
@@ -785,7 +792,8 @@ async def run_faucet_extrinsic(
785
792
  await response.process_events()
786
793
  if not await response.is_success:
787
794
  err_console.print(
788
- f":cross_mark: [red]Failed[/red]: {format_error_message(await response.error_message)}"
795
+ f":cross_mark: [red]Failed[/red]: "
796
+ f"{format_error_message(await response.error_message, subtensor.substrate)}"
789
797
  )
790
798
  if attempts == max_allowed_attempts:
791
799
  raise MaxAttemptsException
@@ -910,6 +918,9 @@ async def _block_solver(
910
918
  stop_event.clear()
911
919
 
912
920
  solution_queue = Queue()
921
+ if cuda:
922
+ num_processes = len(dev_id)
923
+
913
924
  finished_queues = [Queue() for _ in range(num_processes)]
914
925
  check_block = Lock()
915
926
 
@@ -919,7 +930,6 @@ async def _block_solver(
919
930
 
920
931
  if cuda:
921
932
  ## Create a worker per CUDA device
922
- num_processes = len(dev_id)
923
933
  solvers = [
924
934
  _CUDASolver(
925
935
  i,
@@ -1227,8 +1237,14 @@ def _terminate_workers_and_wait_for_exit(
1227
1237
  if isinstance(worker, Queue_Type):
1228
1238
  worker.join_thread()
1229
1239
  else:
1230
- worker.join()
1231
- worker.close()
1240
+ try:
1241
+ worker.join(3.0)
1242
+ except subprocess.TimeoutExpired:
1243
+ worker.terminate()
1244
+ try:
1245
+ worker.close()
1246
+ except ValueError:
1247
+ worker.terminate()
1232
1248
 
1233
1249
 
1234
1250
  # TODO verify this works with async
@@ -1626,6 +1642,7 @@ async def swap_hotkey_extrinsic(
1626
1642
  try:
1627
1643
  wallet.unlock_coldkey()
1628
1644
  except KeyFileError:
1645
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
1629
1646
  return False
1630
1647
 
1631
1648
  if prompt:
@@ -20,15 +20,15 @@ import hashlib
20
20
  import time
21
21
  from typing import Union, List, TYPE_CHECKING
22
22
 
23
- from bittensor_wallet import Wallet
23
+ from bittensor_wallet import Wallet, Keypair
24
24
  from bittensor_wallet.errors import KeyFileError
25
25
  import numpy as np
26
26
  from numpy.typing import NDArray
27
27
  from rich.prompt import Confirm
28
28
  from rich.table import Table, Column
29
29
  from scalecodec import ScaleBytes, U16, Vec
30
+ from substrateinterface.exceptions import SubstrateRequestException
30
31
 
31
- from bittensor_wallet import Keypair
32
32
  from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
33
33
  from bittensor_cli.src.bittensor.extrinsics.registration import is_hotkey_registered
34
34
  from bittensor_cli.src.bittensor.utils import (
@@ -36,6 +36,7 @@ from bittensor_cli.src.bittensor.utils import (
36
36
  err_console,
37
37
  u16_normalized_float,
38
38
  print_verbose,
39
+ format_error_message,
39
40
  )
40
41
 
41
42
  if TYPE_CHECKING:
@@ -308,6 +309,7 @@ async def root_register_extrinsic(
308
309
  try:
309
310
  wallet.unlock_coldkey()
310
311
  except KeyFileError:
312
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
311
313
  return False
312
314
 
313
315
  print_verbose(f"Checking if hotkey ({wallet.hotkey_str}) is registered on root")
@@ -428,6 +430,7 @@ async def set_root_weights_extrinsic(
428
430
  try:
429
431
  wallet.unlock_coldkey()
430
432
  except KeyFileError:
433
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
431
434
  return False
432
435
 
433
436
  # First convert types.
@@ -481,7 +484,6 @@ async def set_root_weights_extrinsic(
481
484
  )
482
485
 
483
486
  success, error_message = await _do_set_weights()
484
- console.print(success, error_message)
485
487
 
486
488
  if not wait_for_finalization and not wait_for_inclusion:
487
489
  return True
@@ -490,9 +492,11 @@ async def set_root_weights_extrinsic(
490
492
  console.print(":white_heavy_check_mark: [green]Finalized[/green]")
491
493
  return True
492
494
  else:
493
- err_console.print(f":cross_mark: [red]Failed[/red]: {error_message}")
495
+ fmt_err = format_error_message(error_message, subtensor.substrate)
496
+ err_console.print(f":cross_mark: [red]Failed[/red]: {fmt_err}")
494
497
  return False
495
498
 
496
- except Exception as e:
497
- err_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e))
499
+ except SubstrateRequestException as e:
500
+ fmt_err = format_error_message(e, subtensor.substrate)
501
+ err_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(fmt_err))
498
502
  return False
@@ -3,6 +3,7 @@ import asyncio
3
3
  from bittensor_wallet import Wallet
4
4
  from bittensor_wallet.errors import KeyFileError
5
5
  from rich.prompt import Confirm
6
+ from substrateinterface.exceptions import SubstrateRequestException
6
7
 
7
8
  from bittensor_cli.src import NETWORK_EXPLORER_MAP
8
9
  from bittensor_cli.src.bittensor.balances import Balance
@@ -14,6 +15,7 @@ from bittensor_cli.src.bittensor.utils import (
14
15
  format_error_message,
15
16
  get_explorer_url_for_network,
16
17
  is_valid_bittensor_address_or_public_key,
18
+ print_error,
17
19
  )
18
20
 
19
21
 
@@ -22,6 +24,7 @@ async def transfer_extrinsic(
22
24
  wallet: Wallet,
23
25
  destination: str,
24
26
  amount: Balance,
27
+ transfer_all: bool = False,
25
28
  wait_for_inclusion: bool = True,
26
29
  wait_for_finalization: bool = False,
27
30
  keep_alive: bool = True,
@@ -33,6 +36,7 @@ async def transfer_extrinsic(
33
36
  :param wallet: Bittensor wallet object to make transfer from.
34
37
  :param destination: Destination public key address (ss58_address or ed25519) of recipient.
35
38
  :param amount: Amount to stake as Bittensor balance.
39
+ :param transfer_all: Whether to transfer all funds from this wallet to the destination address.
36
40
  :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
37
41
  or returns `False` if the extrinsic fails to enter the block within the timeout.
38
42
  :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning
@@ -59,11 +63,11 @@ async def transfer_extrinsic(
59
63
  payment_info = await subtensor.substrate.get_payment_info(
60
64
  call=call, keypair=wallet.coldkeypub
61
65
  )
62
- except Exception as e:
66
+ except SubstrateRequestException as e:
63
67
  payment_info = {"partialFee": int(2e7)} # assume 0.02 Tao
64
68
  err_console.print(
65
69
  f":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n"
66
- f" {e}[/bold white]\n"
70
+ f" {format_error_message(e, subtensor.substrate)}[/bold white]\n"
67
71
  f" Defaulting to default transfer fee: {payment_info['partialFee']}"
68
72
  )
69
73
 
@@ -97,7 +101,11 @@ async def transfer_extrinsic(
97
101
  block_hash_ = response.block_hash
98
102
  return True, block_hash_, ""
99
103
  else:
100
- return False, "", format_error_message(await response.error_message)
104
+ return (
105
+ False,
106
+ "",
107
+ format_error_message(await response.error_message, subtensor.substrate),
108
+ )
101
109
 
102
110
  # Validate destination address.
103
111
  if not is_valid_bittensor_address_or_public_key(destination):
@@ -110,6 +118,7 @@ async def transfer_extrinsic(
110
118
  try:
111
119
  wallet.unlock_coldkey()
112
120
  except KeyFileError:
121
+ err_console.print("Error decrypting coldkey (possibly incorrect password)")
113
122
  return False
114
123
 
115
124
  # Check balance.
@@ -121,7 +130,9 @@ async def transfer_extrinsic(
121
130
  print_verbose("Fetching existential and fee", status)
122
131
  block_hash = await subtensor.substrate.get_chain_head()
123
132
  account_balance_, existential_deposit = await asyncio.gather(
124
- subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash),
133
+ subtensor.get_balance(
134
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
135
+ ),
125
136
  subtensor.get_existential_deposit(block_hash=block_hash),
126
137
  )
127
138
  account_balance = account_balance_[wallet.coldkeypub.ss58_address]
@@ -132,6 +143,12 @@ async def transfer_extrinsic(
132
143
  existential_deposit = Balance(0)
133
144
 
134
145
  # Check if we have enough balance.
146
+ if transfer_all is True:
147
+ amount = account_balance - fee - existential_deposit
148
+ if amount < Balance(0):
149
+ print_error("Not enough balance to transfer")
150
+ return False
151
+
135
152
  if account_balance < (amount + fee + existential_deposit):
136
153
  err_console.print(
137
154
  ":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
@@ -180,7 +197,7 @@ async def transfer_extrinsic(
180
197
  )
181
198
  console.print(
182
199
  f"Balance:\n"
183
- f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]"
200
+ f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
184
201
  )
185
202
  return True
186
203
 
@@ -111,18 +111,18 @@ class SubtensorInterface:
111
111
  return f"Network: {self.network}, Chain: {self.chain_endpoint}"
112
112
 
113
113
  async def __aenter__(self):
114
- with console.status(
115
- f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..."
116
- ):
117
- try:
114
+ try:
115
+ with console.status(
116
+ f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..."
117
+ ):
118
118
  async with self.substrate:
119
119
  return self
120
- except TimeoutException:
121
- err_console.print(
122
- "\n[red]Error[/red]: Timeout occurred connecting to substrate. "
123
- f"Verify your chain and network settings: {self}"
124
- )
125
- raise typer.Exit(code=1)
120
+ except TimeoutException:
121
+ err_console.print(
122
+ "\n[red]Error[/red]: Timeout occurred connecting to substrate. "
123
+ f"Verify your chain and network settings: {self}"
124
+ )
125
+ raise typer.Exit(code=1)
126
126
 
127
127
  async def __aexit__(self, exc_type, exc_val, exc_tb):
128
128
  await self.substrate.close()
@@ -283,7 +283,7 @@ class SubtensorInterface:
283
283
  self,
284
284
  runtime_api: str,
285
285
  method: str,
286
- params: Optional[Union[list[list[int]], dict[str, int]]],
286
+ params: Optional[Union[list[list[int]], list[int], dict[str, int]]],
287
287
  block_hash: Optional[str] = None,
288
288
  reuse_block: Optional[bool] = False,
289
289
  ) -> Optional[str]:
@@ -927,9 +927,11 @@ class SubtensorInterface:
927
927
  if await response.is_success:
928
928
  return True, ""
929
929
  else:
930
- return False, format_error_message(await response.error_message)
930
+ return False, format_error_message(
931
+ await response.error_message, substrate=self.substrate
932
+ )
931
933
  except SubstrateRequestException as e:
932
- return False, e
934
+ return False, format_error_message(e, substrate=self.substrate)
933
935
 
934
936
  async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
935
937
  """
@@ -959,7 +961,7 @@ class SubtensorInterface:
959
961
  else:
960
962
  return True, [], ""
961
963
  except SubstrateRequestException as e:
962
- return False, [], str(e)
964
+ return False, [], format_error_message(e, self.substrate)
963
965
 
964
966
  async def get_subnet_hyperparameters(
965
967
  self, netuid: int, block_hash: Optional[str] = None
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  import math
2
3
  import os
3
4
  import sqlite3
@@ -26,6 +27,9 @@ from bittensor_cli.src.bittensor.balances import Balance
26
27
 
27
28
  if TYPE_CHECKING:
28
29
  from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
30
+ from bittensor_cli.src.bittensor.async_substrate_interface import (
31
+ AsyncSubstrateInterface,
32
+ )
29
33
 
30
34
  console = Console()
31
35
  err_console = Console(stderr=True)
@@ -439,7 +443,7 @@ def get_explorer_url_for_network(
439
443
  explorer_opentensor_url = "{root_url}/query/{block_hash}".format(
440
444
  root_url=explorer_root_urls.get("opentensor"), block_hash=block_hash
441
445
  )
442
- explorer_taostats_url = "{root_url}/extrinsic/{block_hash}".format(
446
+ explorer_taostats_url = "{root_url}/hash/{block_hash}".format(
443
447
  root_url=explorer_root_urls.get("taostats"), block_hash=block_hash
444
448
  )
445
449
  explorer_urls["opentensor"] = explorer_opentensor_url
@@ -448,24 +452,87 @@ def get_explorer_url_for_network(
448
452
  return explorer_urls
449
453
 
450
454
 
451
- def format_error_message(error_message: dict) -> str:
455
+ def format_error_message(
456
+ error_message: Union[dict, Exception], substrate: "AsyncSubstrateInterface"
457
+ ) -> str:
452
458
  """
453
- Formats an error message from the Subtensor error information to using in extrinsics.
459
+ Formats an error message from the Subtensor error information for use in extrinsics.
454
460
 
455
- :param error_message: A dictionary containing the error information from Subtensor.
461
+ Args:
462
+ error_message: A dictionary containing the error information from Subtensor, or a SubstrateRequestException
463
+ containing dictionary literal args.
464
+ substrate: The initialised SubstrateInterface object to use.
456
465
 
457
- :return: A formatted error message string.
466
+ Returns:
467
+ str: A formatted error message string.
458
468
  """
459
- err_type = "UnknownType"
460
469
  err_name = "UnknownError"
470
+ err_type = "UnknownType"
461
471
  err_description = "Unknown Description"
462
472
 
473
+ if isinstance(error_message, Exception):
474
+ # generally gotten through SubstrateRequestException args
475
+ new_error_message = None
476
+ for arg in error_message.args:
477
+ try:
478
+ d = ast.literal_eval(arg)
479
+ if isinstance(d, dict):
480
+ if "error" in d:
481
+ new_error_message = d["error"]
482
+ break
483
+ elif all(x in d for x in ["code", "message", "data"]):
484
+ new_error_message = d
485
+ break
486
+ except ValueError:
487
+ pass
488
+ if new_error_message is None:
489
+ return_val = " ".join(error_message.args)
490
+ return f"Subtensor returned: {return_val}"
491
+ else:
492
+ error_message = new_error_message
493
+
463
494
  if isinstance(error_message, dict):
464
- err_type = error_message.get("type", err_type)
465
- err_name = error_message.get("name", err_name)
466
- err_docs = error_message.get("docs", [])
467
- err_description = err_docs[0] if len(err_docs) > 0 else err_description
468
- return f"Subtensor returned `{err_name} ({err_type})` error. This means: `{err_description}`"
495
+ # subtensor error structure
496
+ if (
497
+ error_message.get("code")
498
+ and error_message.get("message")
499
+ and error_message.get("data")
500
+ ):
501
+ err_name = "SubstrateRequestException"
502
+ err_type = error_message.get("message", "")
503
+ err_data = error_message.get("data", "")
504
+
505
+ # subtensor custom error marker
506
+ if err_data.startswith("Custom error:") and substrate:
507
+ if substrate.metadata:
508
+ try:
509
+ pallet = substrate.metadata.get_metadata_pallet(
510
+ "SubtensorModule"
511
+ )
512
+ error_index = int(err_data.split("Custom error:")[-1])
513
+
514
+ error_dict = pallet.errors[error_index].value
515
+ err_type = error_dict.get("message", err_type)
516
+ err_docs = error_dict.get("docs", [])
517
+ err_description = err_docs[0] if err_docs else err_description
518
+ except (AttributeError, IndexError):
519
+ err_console.print(
520
+ "Substrate pallets data unavailable. This is usually caused by an uninitialized substrate."
521
+ )
522
+ else:
523
+ err_description = err_data
524
+
525
+ elif (
526
+ error_message.get("type")
527
+ and error_message.get("name")
528
+ and error_message.get("docs")
529
+ ):
530
+ err_type = error_message.get("type", err_type)
531
+ err_name = error_message.get("name", err_name)
532
+ err_docs = error_message.get("docs", [err_description])
533
+ err_description = err_docs[0] if err_docs else err_description
534
+
535
+ return f"Subtensor returned `{err_name}({err_type})` error. This means: '{err_description}'."
469
536
 
470
537
 
471
538
  def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, int]:
@@ -877,8 +944,6 @@ def validate_chain_endpoint(endpoint_url) -> tuple[bool, str]:
877
944
  )
878
945
  if not parsed.netloc:
879
946
  return False, "Invalid URL passed as the endpoint"
880
- if not parsed.port:
881
- return False, "No port specified in the URL"
882
947
  return True, ""
883
948
 
884
949