bittensor-cli 8.4.4__py3-none-any.whl → 9.0.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.
Files changed (33) hide show
  1. bittensor_cli/__init__.py +1 -1
  2. bittensor_cli/cli.py +1827 -1394
  3. bittensor_cli/src/__init__.py +623 -168
  4. bittensor_cli/src/bittensor/balances.py +41 -8
  5. bittensor_cli/src/bittensor/chain_data.py +557 -428
  6. bittensor_cli/src/bittensor/extrinsics/registration.py +129 -23
  7. bittensor_cli/src/bittensor/extrinsics/root.py +3 -3
  8. bittensor_cli/src/bittensor/extrinsics/transfer.py +6 -11
  9. bittensor_cli/src/bittensor/minigraph.py +46 -8
  10. bittensor_cli/src/bittensor/subtensor_interface.py +567 -250
  11. bittensor_cli/src/bittensor/utils.py +370 -25
  12. bittensor_cli/src/commands/stake/__init__.py +154 -0
  13. bittensor_cli/src/commands/stake/add.py +625 -0
  14. bittensor_cli/src/commands/stake/children_hotkeys.py +103 -75
  15. bittensor_cli/src/commands/stake/list.py +687 -0
  16. bittensor_cli/src/commands/stake/move.py +1000 -0
  17. bittensor_cli/src/commands/stake/remove.py +1146 -0
  18. bittensor_cli/src/commands/subnets/__init__.py +0 -0
  19. bittensor_cli/src/commands/subnets/price.py +867 -0
  20. bittensor_cli/src/commands/subnets/subnets.py +2028 -0
  21. bittensor_cli/src/commands/sudo.py +554 -12
  22. bittensor_cli/src/commands/wallets.py +225 -531
  23. bittensor_cli/src/commands/weights.py +2 -2
  24. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/METADATA +7 -4
  25. bittensor_cli-9.0.0.dist-info/RECORD +34 -0
  26. bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
  27. bittensor_cli/src/commands/root.py +0 -1787
  28. bittensor_cli/src/commands/stake/stake.py +0 -1448
  29. bittensor_cli/src/commands/subnets.py +0 -897
  30. bittensor_cli-8.4.4.dist-info/RECORD +0 -31
  31. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/WHEEL +0 -0
  32. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/entry_points.txt +0 -0
  33. {bittensor_cli-8.4.4.dist-info → bittensor_cli-9.0.0.dist-info}/top_level.txt +0 -0
@@ -1,41 +1,36 @@
1
1
  import asyncio
2
2
  from typing import Optional, Any, Union, TypedDict, Iterable
3
3
 
4
-
5
4
  import aiohttp
6
5
  from bittensor_wallet import Wallet
7
6
  from bittensor_wallet.utils import SS58_FORMAT
8
- import scalecodec
9
7
  from scalecodec import GenericCall
10
- from scalecodec.base import RuntimeConfiguration
11
- from scalecodec.type_registry import load_type_registry_preset
12
- from substrateinterface.exceptions import SubstrateRequestException
8
+ from async_substrate_interface.errors import SubstrateRequestException
13
9
  import typer
14
10
 
15
- from bittensor_cli.src.bittensor.async_substrate_interface import (
16
- AsyncSubstrateInterface,
17
- TimeoutException,
18
- )
11
+
12
+ from async_substrate_interface.async_substrate import AsyncSubstrateInterface
19
13
  from bittensor_cli.src.bittensor.chain_data import (
20
14
  DelegateInfo,
21
- custom_rpc_type_registry,
22
15
  StakeInfo,
23
16
  NeuronInfoLite,
24
17
  NeuronInfo,
25
18
  SubnetHyperparameters,
26
19
  decode_account_id,
20
+ decode_hex_identity,
21
+ DynamicInfo,
22
+ SubnetState,
27
23
  )
28
24
  from bittensor_cli.src import DelegatesDetails
29
- from bittensor_cli.src.bittensor.balances import Balance
25
+ from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float
30
26
  from bittensor_cli.src import Constants, defaults, TYPE_REGISTRY
31
27
  from bittensor_cli.src.bittensor.utils import (
32
- ss58_to_vec_u8,
33
28
  format_error_message,
34
29
  console,
35
30
  err_console,
36
31
  decode_hex_identity_dict,
37
32
  validate_chain_endpoint,
38
- hex_to_bytes,
33
+ u16_normalized_float,
39
34
  )
40
35
 
41
36
 
@@ -59,11 +54,11 @@ class ProposalVoteData:
59
54
  self.end = proposal_dict["end"]
60
55
 
61
56
  @staticmethod
62
- def decode_ss58_tuples(l: tuple):
57
+ def decode_ss58_tuples(data: tuple):
63
58
  """
64
59
  Decodes a tuple of ss58 addresses formatted as bytes tuples
65
60
  """
66
- return [decode_account_id(l[x][0]) for x in range(len(l))]
61
+ return [decode_account_id(data[x][0]) for x in range(len(data))]
67
62
 
68
63
 
69
64
  class SubtensorInterface:
@@ -96,13 +91,14 @@ class SubtensorInterface:
96
91
  f"Network not specified or not valid. Using default chain endpoint: "
97
92
  f"{Constants.network_map[defaults.subtensor.network]}.\n"
98
93
  f"You can set this for commands with the `--network` flag, or by setting this"
99
- f" in the config."
94
+ f" in the config. If you're sure you're using the correct URL, ensure it begins"
95
+ f" with 'ws://' or 'wss://'"
100
96
  )
101
97
  self.chain_endpoint = Constants.network_map[defaults.subtensor.network]
102
98
  self.network = defaults.subtensor.network
103
99
 
104
100
  self.substrate = AsyncSubstrateInterface(
105
- chain_endpoint=self.chain_endpoint,
101
+ url=self.chain_endpoint,
106
102
  ss58_format=SS58_FORMAT,
107
103
  type_registry=TYPE_REGISTRY,
108
104
  chain_name="Bittensor",
@@ -112,41 +108,48 @@ class SubtensorInterface:
112
108
  return f"Network: {self.network}, Chain: {self.chain_endpoint}"
113
109
 
114
110
  async def __aenter__(self):
115
- try:
116
- with console.status(
117
- f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..."
118
- ):
111
+ with console.status(
112
+ f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..."
113
+ ):
114
+ try:
119
115
  async with self.substrate:
120
116
  return self
121
- except TimeoutException:
122
- err_console.print(
123
- "\n[red]Error[/red]: Timeout occurred connecting to substrate. "
124
- f"Verify your chain and network settings: {self}"
125
- )
126
- raise typer.Exit(code=1)
117
+ except TimeoutError: # TODO verify
118
+ err_console.print(
119
+ "\n[red]Error[/red]: Timeout occurred connecting to substrate. "
120
+ f"Verify your chain and network settings: {self}"
121
+ )
122
+ raise typer.Exit(code=1)
127
123
 
128
124
  async def __aexit__(self, exc_type, exc_val, exc_tb):
129
125
  await self.substrate.close()
130
126
 
131
- async def encode_params(
127
+ async def query(
132
128
  self,
133
- call_definition: list["ParamWithTypes"],
134
- params: Union[list[Any], dict[str, Any]],
135
- ) -> str:
136
- """Returns a hex encoded string of the params using their types."""
137
- param_data = scalecodec.ScaleBytes(b"")
138
-
139
- for i, param in enumerate(call_definition["params"]): # type: ignore
140
- scale_obj = await self.substrate.create_scale_object(param["type"])
141
- if isinstance(params, list):
142
- param_data += scale_obj.encode(params[i])
143
- else:
144
- if param["name"] not in params:
145
- raise ValueError(f"Missing param {param['name']} in params dict.")
146
-
147
- param_data += scale_obj.encode(params[param["name"]])
148
-
149
- return param_data.to_hex()
129
+ module: str,
130
+ storage_function: str,
131
+ params: Optional[list] = None,
132
+ block_hash: Optional[str] = None,
133
+ raw_storage_key: Optional[bytes] = None,
134
+ subscription_handler=None,
135
+ reuse_block_hash: bool = False,
136
+ ) -> Any:
137
+ """
138
+ Pass-through to substrate.query which automatically returns the .value if it's a ScaleObj
139
+ """
140
+ result = await self.substrate.query(
141
+ module,
142
+ storage_function,
143
+ params,
144
+ block_hash,
145
+ raw_storage_key,
146
+ subscription_handler,
147
+ reuse_block_hash,
148
+ )
149
+ if hasattr(result, "value"):
150
+ return result.value
151
+ else:
152
+ return result
150
153
 
151
154
  async def get_all_subnet_netuids(
152
155
  self, block_hash: Optional[str] = None
@@ -155,6 +158,7 @@ class SubtensorInterface:
155
158
  Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network.
156
159
 
157
160
  :param block_hash: The hash of the block to retrieve the subnet unique identifiers from.
161
+
158
162
  :return: A list of subnet netuids.
159
163
 
160
164
  This function provides a comprehensive view of the subnets within the Bittensor network,
@@ -166,59 +170,13 @@ class SubtensorInterface:
166
170
  block_hash=block_hash,
167
171
  reuse_block_hash=True,
168
172
  )
169
- return (
170
- []
171
- if result is None or not hasattr(result, "records")
172
- else [netuid async for netuid, exists in result if exists]
173
- )
174
-
175
- async def is_hotkey_delegate(
176
- self,
177
- hotkey_ss58: str,
178
- block_hash: Optional[str] = None,
179
- reuse_block: Optional[bool] = False,
180
- ) -> bool:
181
- """
182
- Determines whether a given hotkey (public key) is a delegate on the Bittensor network. This function
183
- checks if the neuron associated with the hotkey is part of the network's delegation system.
184
-
185
- :param hotkey_ss58: The SS58 address of the neuron's hotkey.
186
- :param block_hash: The hash of the blockchain block number for the query.
187
- :param reuse_block: Whether to reuse the last-used block hash.
188
-
189
- :return: `True` if the hotkey is a delegate, `False` otherwise.
190
-
191
- Being a delegate is a significant status within the Bittensor network, indicating a neuron's
192
- involvement in consensus and governance processes.
193
- """
194
- delegates = await self.get_delegates(
195
- block_hash=block_hash, reuse_block=reuse_block
196
- )
197
- return hotkey_ss58 in [info.hotkey_ss58 for info in delegates]
198
-
199
- async def get_delegates(
200
- self, block_hash: Optional[str] = None, reuse_block: Optional[bool] = False
201
- ) -> list[DelegateInfo]:
202
- """
203
- Fetches all delegates on the chain
204
-
205
- :param block_hash: hash of the blockchain block number for the query.
206
- :param reuse_block: whether to reuse the last-used block hash.
207
-
208
- :return: List of DelegateInfo objects, or an empty list if there are no delegates.
209
- """
210
- hex_bytes_result = await self.query_runtime_api(
211
- runtime_api="DelegateInfoRuntimeApi",
212
- method="get_delegates",
213
- params=[],
214
- block_hash=block_hash,
215
- )
216
- if hex_bytes_result is not None:
217
- return DelegateInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
218
- else:
219
- return []
173
+ res = []
174
+ async for netuid, exists in result:
175
+ if exists.value:
176
+ res.append(netuid)
177
+ return res
220
178
 
221
- async def get_stake_info_for_coldkey(
179
+ async def get_stake_for_coldkey(
222
180
  self,
223
181
  coldkey_ss58: str,
224
182
  block_hash: Optional[str] = None,
@@ -237,47 +195,80 @@ class SubtensorInterface:
237
195
  Stake information is vital for account holders to assess their investment and participation
238
196
  in the network's delegation and consensus processes.
239
197
  """
240
- encoded_coldkey = ss58_to_vec_u8(coldkey_ss58)
241
198
 
242
- hex_bytes_result = await self.query_runtime_api(
199
+ result = await self.query_runtime_api(
243
200
  runtime_api="StakeInfoRuntimeApi",
244
201
  method="get_stake_info_for_coldkey",
245
- params=[encoded_coldkey],
202
+ params=[coldkey_ss58],
246
203
  block_hash=block_hash,
247
204
  reuse_block=reuse_block,
248
205
  )
249
206
 
250
- if hex_bytes_result is None:
207
+ if result is None:
251
208
  return []
252
-
253
- return StakeInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
209
+ stakes = StakeInfo.list_from_any(result)
210
+ return [stake for stake in stakes if stake.stake > 0]
254
211
 
255
212
  async def get_stake_for_coldkey_and_hotkey(
256
- self, hotkey_ss58: str, coldkey_ss58: str, block_hash: Optional[str]
213
+ self,
214
+ hotkey_ss58: str,
215
+ coldkey_ss58: str,
216
+ netuid: Optional[int] = None,
217
+ block_hash: Optional[str] = None,
257
218
  ) -> Balance:
258
219
  """
259
- Retrieves stake information associated with a specific coldkey and hotkey.
260
- :param hotkey_ss58: the hotkey SS58 address to query
261
- :param coldkey_ss58: the coldkey SS58 address to query
262
- :param block_hash: the hash of the blockchain block number for the query.
263
- :return: Stake Balance for the given coldkey and hotkey
220
+ Returns the stake under a coldkey - hotkey pairing.
221
+
222
+ :param hotkey_ss58: The SS58 address of the hotkey.
223
+ :param coldkey_ss58: The SS58 address of the coldkey.
224
+ :param netuid: The subnet ID to filter by. If provided, only returns stake for this specific
225
+ subnet.
226
+ :param block_hash: The block hash at which to query the stake information.
227
+
228
+ :return: Balance: The stake under the coldkey - hotkey pairing.
264
229
  """
265
- _result = await self.substrate.query(
230
+ alpha_shares = await self.query(
266
231
  module="SubtensorModule",
267
- storage_function="Stake",
268
- params=[hotkey_ss58, coldkey_ss58],
232
+ storage_function="Alpha",
233
+ params=[hotkey_ss58, coldkey_ss58, netuid],
269
234
  block_hash=block_hash,
270
235
  )
271
- return Balance.from_rao(_result or 0)
236
+
237
+ hotkey_alpha = await self.query(
238
+ module="SubtensorModule",
239
+ storage_function="TotalHotkeyAlpha",
240
+ params=[hotkey_ss58, netuid],
241
+ block_hash=block_hash,
242
+ )
243
+
244
+ hotkey_shares = await self.query(
245
+ module="SubtensorModule",
246
+ storage_function="TotalHotkeyShares",
247
+ params=[hotkey_ss58, netuid],
248
+ block_hash=block_hash,
249
+ )
250
+
251
+ alpha_shares_as_float = fixed_to_float(alpha_shares or 0)
252
+ hotkey_shares_as_float = fixed_to_float(hotkey_shares or 0)
253
+
254
+ if hotkey_shares_as_float == 0:
255
+ return Balance.from_rao(0).set_unit(netuid=netuid)
256
+
257
+ stake = alpha_shares_as_float / hotkey_shares_as_float * (hotkey_alpha or 0)
258
+
259
+ return Balance.from_rao(int(stake)).set_unit(netuid=netuid)
260
+
261
+ # Alias
262
+ get_stake = get_stake_for_coldkey_and_hotkey
272
263
 
273
264
  async def query_runtime_api(
274
265
  self,
275
266
  runtime_api: str,
276
267
  method: str,
277
- params: Optional[Union[list[list[int]], list[int], dict[str, int]]],
268
+ params: Optional[Union[list, dict]] = None,
278
269
  block_hash: Optional[str] = None,
279
270
  reuse_block: Optional[bool] = False,
280
- ) -> Optional[str]:
271
+ ) -> Optional[Any]:
281
272
  """
282
273
  Queries the runtime API of the Bittensor blockchain, providing a way to interact with the underlying
283
274
  runtime and retrieve data encoded in Scale Bytes format. This function is essential for advanced users
@@ -289,45 +280,44 @@ class SubtensorInterface:
289
280
  :param block_hash: The hash of the blockchain block number at which to perform the query.
290
281
  :param reuse_block: Whether to reuse the last-used block hash.
291
282
 
292
- :return: The Scale Bytes encoded result from the runtime API call, or ``None`` if the call fails.
283
+ :return: The decoded result from the runtime API call, or ``None`` if the call fails.
293
284
 
294
285
  This function enables access to the deeper layers of the Bittensor blockchain, allowing for detailed
295
286
  and specific interactions with the network's runtime environment.
296
287
  """
297
- call_definition = TYPE_REGISTRY["runtime_api"][runtime_api]["methods"][method]
298
-
299
- data = (
300
- "0x"
301
- if params is None
302
- else await self.encode_params(
303
- call_definition=call_definition, params=params
304
- )
305
- )
306
- api_method = f"{runtime_api}_{method}"
307
-
308
- json_result = await self.substrate.rpc_request(
309
- method="state_call",
310
- params=[api_method, data, block_hash] if block_hash else [api_method, data],
311
- )
312
-
313
- if json_result is None:
314
- return None
315
-
316
- return_type = call_definition["type"]
317
-
318
- as_scale_bytes = scalecodec.ScaleBytes(json_result["result"]) # type: ignore
288
+ if reuse_block:
289
+ block_hash = self.substrate.last_block_hash
290
+ result = (
291
+ await self.substrate.runtime_call(runtime_api, method, params, block_hash)
292
+ ).value
319
293
 
320
- rpc_runtime_config = RuntimeConfiguration()
321
- rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy"))
322
- rpc_runtime_config.update_type_registry(custom_rpc_type_registry)
294
+ return result
323
295
 
324
- obj = rpc_runtime_config.create_scale_object(return_type, as_scale_bytes)
325
- if obj.data.to_hex() == "0x0400": # RPC returned None result
326
- return None
296
+ async def get_balance(
297
+ self,
298
+ address: str,
299
+ block_hash: Optional[str] = None,
300
+ reuse_block: bool = False,
301
+ ) -> Balance:
302
+ """
303
+ Retrieves the balance for a single coldkey address
327
304
 
328
- return obj.decode()
305
+ :param address: coldkey address
306
+ :param block_hash: the block hash, optional
307
+ :param reuse_block: Whether to reuse the last-used block hash when retrieving info.
308
+ :return: Balance object representing the address's balance
309
+ """
310
+ result = await self.query(
311
+ module="System",
312
+ storage_function="Account",
313
+ params=[address],
314
+ block_hash=block_hash,
315
+ reuse_block_hash=reuse_block,
316
+ )
317
+ value = result or {"data": {"free": 0}}
318
+ return Balance(value["data"]["free"])
329
319
 
330
- async def get_balance(
320
+ async def get_balances(
331
321
  self,
332
322
  *addresses: str,
333
323
  block_hash: Optional[str] = None,
@@ -340,6 +330,8 @@ class SubtensorInterface:
340
330
  :param reuse_block: Whether to reuse the last-used block hash when retrieving info.
341
331
  :return: dict of {address: Balance objects}
342
332
  """
333
+ if reuse_block:
334
+ block_hash = self.substrate.last_block_hash
343
335
  calls = [
344
336
  (
345
337
  await self.substrate.create_storage_key(
@@ -370,46 +362,132 @@ class SubtensorInterface:
370
362
 
371
363
  :return: {address: Balance objects}
372
364
  """
365
+ sub_stakes = await self.get_stake_for_coldkeys(
366
+ list(ss58_addresses), block_hash=block_hash
367
+ )
368
+ # Token pricing info
369
+ dynamic_info = await self.all_subnets()
370
+
371
+ results = {}
372
+ for ss58, stake_info_list in sub_stakes.items():
373
+ all_staked_tao = 0
374
+ for sub_stake in stake_info_list:
375
+ if sub_stake.stake.rao == 0:
376
+ continue
377
+ netuid = sub_stake.netuid
378
+ pool = dynamic_info[netuid]
379
+
380
+ alpha_value = Balance.from_rao(int(sub_stake.stake.rao)).set_unit(
381
+ netuid
382
+ )
383
+
384
+ tao_locked = pool.tao_in
385
+
386
+ issuance = pool.alpha_out if pool.is_dynamic else tao_locked
387
+ tao_ownership = Balance(0)
388
+
389
+ if alpha_value.tao > 0.00009 and issuance.tao != 0:
390
+ tao_ownership = Balance.from_tao(
391
+ (alpha_value.tao / issuance.tao) * tao_locked.tao
392
+ )
393
+
394
+ all_staked_tao += tao_ownership.rao
395
+
396
+ results[ss58] = Balance.from_rao(all_staked_tao)
397
+ return results
398
+
399
+ async def get_total_stake_for_hotkey(
400
+ self,
401
+ *ss58_addresses,
402
+ netuids: Optional[list[int]] = None,
403
+ block_hash: Optional[str] = None,
404
+ reuse_block: bool = False,
405
+ ) -> dict[str, dict[int, Balance]]:
406
+ """
407
+ Returns the total stake held on a hotkey.
408
+
409
+ :param ss58_addresses: The SS58 address(es) of the hotkey(s)
410
+ :param netuids: The netuids to retrieve the stake from. If not specified, will use all subnets.
411
+ :param block_hash: The hash of the block number to retrieve the stake from.
412
+ :param reuse_block: Whether to reuse the last-used block hash when retrieving info.
413
+
414
+ :return:
415
+ {
416
+ hotkey_ss58_1: {
417
+ netuid_1: netuid1_stake,
418
+ netuid_2: netuid2_stake,
419
+ ...
420
+ },
421
+ hotkey_ss58_2: {
422
+ netuid_1: netuid1_stake,
423
+ netuid_2: netuid2_stake,
424
+ ...
425
+ },
426
+ ...
427
+ }
428
+ """
429
+ if not block_hash:
430
+ if reuse_block:
431
+ block_hash = self.substrate.last_block_hash
432
+ else:
433
+ block_hash = await self.substrate.get_chain_head()
434
+
435
+ netuids = netuids or await self.get_all_subnet_netuids(block_hash=block_hash)
373
436
  calls = [
374
437
  (
375
438
  await self.substrate.create_storage_key(
376
439
  "SubtensorModule",
377
- "TotalColdkeyStake",
378
- [address],
440
+ "TotalHotkeyAlpha",
441
+ params=[ss58, netuid],
379
442
  block_hash=block_hash,
380
443
  )
381
444
  )
382
- for address in ss58_addresses
445
+ for ss58 in ss58_addresses
446
+ for netuid in netuids
383
447
  ]
384
- batch_call = await self.substrate.query_multi(calls, block_hash=block_hash)
385
- results = {}
386
- for item in batch_call:
387
- results.update({item[0].params[0]: Balance.from_rao(item[1] or 0)})
448
+ query = await self.substrate.query_multi(calls, block_hash=block_hash)
449
+ results: dict[str, dict[int, "Balance"]] = {
450
+ hk_ss58: {} for hk_ss58 in ss58_addresses
451
+ }
452
+ for idx, (_, val) in enumerate(query):
453
+ hotkey_ss58 = ss58_addresses[idx // len(netuids)]
454
+ netuid = netuids[idx % len(netuids)]
455
+ value = (Balance.from_rao(val) if val is not None else Balance(0)).set_unit(
456
+ netuid
457
+ )
458
+ results[hotkey_ss58][netuid] = value
388
459
  return results
389
460
 
390
- async def get_total_stake_for_hotkey(
461
+ async def current_take(
391
462
  self,
392
- *ss58_addresses,
463
+ hotkey_ss58: int,
393
464
  block_hash: Optional[str] = None,
394
465
  reuse_block: bool = False,
395
- ) -> dict[str, Balance]:
466
+ ) -> Optional[float]:
396
467
  """
397
- Returns the total stake held on a hotkey.
468
+ Retrieves the delegate 'take' percentage for a neuron identified by its hotkey. The 'take'
469
+ represents the percentage of rewards that the delegate claims from its nominators' stakes.
398
470
 
399
- :param ss58_addresses: The SS58 address(es) of the hotkey(s)
471
+ :param hotkey_ss58: The `SS58` address of the neuron's hotkey.
400
472
  :param block_hash: The hash of the block number to retrieve the stake from.
401
473
  :param reuse_block: Whether to reuse the last-used block hash when retrieving info.
402
474
 
403
- :return: {address: Balance objects}
475
+ :return: The delegate take percentage, None if not available.
476
+
477
+ The delegate take is a critical parameter in the network's incentive structure, influencing
478
+ the distribution of rewards among neurons and their nominators.
404
479
  """
405
- results = await self.substrate.query_multiple(
406
- params=[s for s in ss58_addresses],
480
+ result = await self.query(
407
481
  module="SubtensorModule",
408
- storage_function="TotalHotkeyStake",
482
+ storage_function="Delegates",
483
+ params=[hotkey_ss58],
409
484
  block_hash=block_hash,
410
485
  reuse_block_hash=reuse_block,
411
486
  )
412
- return {k: Balance.from_rao(r or 0) for (k, r) in results.items()}
487
+ if result is None:
488
+ return None
489
+ else:
490
+ return u16_normalized_float(result)
413
491
 
414
492
  async def get_netuids_for_hotkey(
415
493
  self,
@@ -436,11 +514,11 @@ class SubtensorInterface:
436
514
  block_hash=block_hash,
437
515
  reuse_block_hash=reuse_block,
438
516
  )
439
- return (
440
- [record[0] async for record in result if record[1]]
441
- if result and hasattr(result, "records")
442
- else []
443
- )
517
+ res = []
518
+ async for record in result:
519
+ if record[1].value:
520
+ res.append(record[0])
521
+ return res
444
522
 
445
523
  async def subnet_exists(
446
524
  self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False
@@ -457,7 +535,7 @@ class SubtensorInterface:
457
535
  This function is critical for verifying the presence of specific subnets in the network,
458
536
  enabling a deeper understanding of the network's structure and composition.
459
537
  """
460
- result = await self.substrate.query(
538
+ result = await self.query(
461
539
  module="SubtensorModule",
462
540
  storage_function="NetworksAdded",
463
541
  params=[netuid],
@@ -466,6 +544,29 @@ class SubtensorInterface:
466
544
  )
467
545
  return result
468
546
 
547
+ async def get_subnet_state(
548
+ self, netuid: int, block_hash: Optional[str] = None
549
+ ) -> Optional["SubnetState"]:
550
+ """
551
+ Retrieves the state of a specific subnet within the Bittensor network.
552
+
553
+ :param netuid: The network UID of the subnet to query.
554
+ :param block_hash: The hash of the blockchain block number for the query.
555
+
556
+ :return: SubnetState object containing the subnet's state information, or None if the subnet doesn't exist.
557
+ """
558
+ result = await self.query_runtime_api(
559
+ runtime_api="SubnetInfoRuntimeApi",
560
+ method="get_subnet_state",
561
+ params=[netuid],
562
+ block_hash=block_hash,
563
+ )
564
+
565
+ if result is None:
566
+ return None
567
+
568
+ return SubnetState.from_any(result)
569
+
469
570
  async def get_hyperparameter(
470
571
  self,
471
572
  param_name: str,
@@ -487,7 +588,7 @@ class SubtensorInterface:
487
588
  print("subnet does not exist")
488
589
  return None
489
590
 
490
- result = await self.substrate.query(
591
+ result = await self.query(
491
592
  module="SubtensorModule",
492
593
  storage_function=param_name,
493
594
  params=[netuid],
@@ -569,11 +670,15 @@ class SubtensorInterface:
569
670
  The existential deposit is a fundamental economic parameter in the Bittensor network, ensuring
570
671
  efficient use of storage and preventing the proliferation of dust accounts.
571
672
  """
572
- result = await self.substrate.get_constant(
573
- module_name="Balances",
574
- constant_name="ExistentialDeposit",
575
- block_hash=block_hash,
576
- reuse_block_hash=reuse_block,
673
+ result = getattr(
674
+ await self.substrate.get_constant(
675
+ module_name="Balances",
676
+ constant_name="ExistentialDeposit",
677
+ block_hash=block_hash,
678
+ reuse_block_hash=reuse_block,
679
+ ),
680
+ "value",
681
+ None,
577
682
  )
578
683
 
579
684
  if result is None:
@@ -632,20 +737,18 @@ class SubtensorInterface:
632
737
  This function offers a quick overview of the neuron population within a subnet, facilitating
633
738
  efficient analysis of the network's decentralized structure and neuron dynamics.
634
739
  """
635
- hex_bytes_result = await self.query_runtime_api(
740
+ result = await self.query_runtime_api(
636
741
  runtime_api="NeuronInfoRuntimeApi",
637
742
  method="get_neurons_lite",
638
- params=[
639
- netuid
640
- ], # TODO check to see if this can accept more than one at a time
743
+ params=[netuid],
641
744
  block_hash=block_hash,
642
745
  reuse_block=reuse_block,
643
746
  )
644
747
 
645
- if hex_bytes_result is None:
748
+ if result is None:
646
749
  return []
647
750
 
648
- return NeuronInfoLite.list_from_vec_u8(hex_to_bytes(hex_bytes_result))
751
+ return NeuronInfoLite.list_from_any(result)
649
752
 
650
753
  async def neuron_for_uid(
651
754
  self, uid: Optional[int], netuid: int, block_hash: Optional[str] = None
@@ -668,17 +771,20 @@ class SubtensorInterface:
668
771
  if uid is None:
669
772
  return NeuronInfo.get_null_neuron()
670
773
 
671
- params = [netuid, uid, block_hash] if block_hash else [netuid, uid]
672
- json_body = await self.substrate.rpc_request(
673
- method="neuronInfo_getNeuron",
674
- params=params, # custom rpc method
774
+ result = await self.query_runtime_api(
775
+ runtime_api="NeuronInfoRuntimeApi",
776
+ method="get_neuron",
777
+ params=[
778
+ netuid,
779
+ uid,
780
+ ], # TODO check to see if this can accept more than one at a time
781
+ block_hash=block_hash,
675
782
  )
676
783
 
677
- if not (result := json_body.get("result", None)):
784
+ if not result:
678
785
  return NeuronInfo.get_null_neuron()
679
786
 
680
- bytes_result = bytes(result)
681
- return NeuronInfo.from_vec_u8(bytes_result)
787
+ return NeuronInfo.from_any(result)
682
788
 
683
789
  async def get_delegated(
684
790
  self,
@@ -705,16 +811,45 @@ class SubtensorInterface:
705
811
  if block_hash
706
812
  else (self.substrate.last_block_hash if reuse_block else None)
707
813
  )
708
- encoded_coldkey = ss58_to_vec_u8(coldkey_ss58)
709
- json_body = await self.substrate.rpc_request(
710
- method="delegateInfo_getDelegated",
711
- params=([block_hash, encoded_coldkey] if block_hash else [encoded_coldkey]),
814
+ result = await self.query_runtime_api(
815
+ runtime_api="DelegateInfoRuntimeApi",
816
+ method="get_delegated",
817
+ params=[coldkey_ss58],
818
+ block_hash=block_hash,
712
819
  )
713
820
 
714
- if not (result := json_body.get("result")):
821
+ if not result:
715
822
  return []
716
823
 
717
- return DelegateInfo.delegated_list_from_vec_u8(bytes(result))
824
+ return DelegateInfo.list_from_any(result)
825
+
826
+ async def query_all_identities(
827
+ self,
828
+ block_hash: Optional[str] = None,
829
+ reuse_block: bool = False,
830
+ ) -> dict[str, dict]:
831
+ """
832
+ Queries all identities on the Bittensor blockchain.
833
+
834
+ :param block_hash: The hash of the blockchain block number at which to perform the query.
835
+ :param reuse_block: Whether to reuse the last-used blockchain block hash.
836
+
837
+ :return: A dictionary mapping addresses to their decoded identity data.
838
+ """
839
+
840
+ identities = await self.substrate.query_map(
841
+ module="SubtensorModule",
842
+ storage_function="IdentitiesV2",
843
+ block_hash=block_hash,
844
+ reuse_block_hash=reuse_block,
845
+ )
846
+ all_identities = {}
847
+ async for ss58_address, identity in identities:
848
+ all_identities[decode_account_id(ss58_address[0])] = decode_hex_identity(
849
+ identity.value
850
+ )
851
+
852
+ return all_identities
718
853
 
719
854
  async def query_identity(
720
855
  self,
@@ -740,43 +875,61 @@ class SubtensorInterface:
740
875
  The identity information can include various attributes such as the neuron's stake, rank, and other
741
876
  network-specific details, providing insights into the neuron's role and status within the Bittensor network.
742
877
  """
743
-
744
- def decode_hex_identity_dict(info_dictionary):
745
- for k, v in info_dictionary.items():
746
- if isinstance(v, dict):
747
- item = next(iter(v.values()))
748
- else:
749
- item = v
750
- if isinstance(item, tuple) and item:
751
- if len(item) > 1:
752
- try:
753
- info_dictionary[k] = (
754
- bytes(item).hex(sep=" ", bytes_per_sep=2).upper()
755
- )
756
- except UnicodeDecodeError:
757
- print(f"Could not decode: {k}: {item}")
758
- else:
759
- try:
760
- info_dictionary[k] = bytes(item[0]).decode("utf-8")
761
- except UnicodeDecodeError:
762
- print(f"Could not decode: {k}: {item}")
763
- else:
764
- info_dictionary[k] = item
765
-
766
- return info_dictionary
767
-
768
- identity_info = await self.substrate.query(
769
- module="Registry",
770
- storage_function="IdentityOf",
878
+ identity_info = await self.query(
879
+ module="SubtensorModule",
880
+ storage_function="IdentitiesV2",
771
881
  params=[key],
772
882
  block_hash=block_hash,
773
883
  reuse_block_hash=reuse_block,
774
884
  )
885
+ if not identity_info:
886
+ return {}
775
887
  try:
776
- return decode_hex_identity_dict(identity_info["info"])
888
+ return decode_hex_identity(identity_info)
777
889
  except TypeError:
778
890
  return {}
779
891
 
892
+ async def fetch_coldkey_hotkey_identities(
893
+ self,
894
+ block_hash: Optional[str] = None,
895
+ reuse_block: bool = False,
896
+ ) -> dict[str, dict]:
897
+ """
898
+ Builds a dictionary containing coldkeys and hotkeys with their associated identities and relationships.
899
+ :param block_hash: The hash of the blockchain block number for the query.
900
+ :param reuse_block: Whether to reuse the last-used blockchain block hash.
901
+ :return: Dict with 'coldkeys' and 'hotkeys' as keys.
902
+ """
903
+
904
+ coldkey_identities = await self.query_all_identities()
905
+ identities = {"coldkeys": {}, "hotkeys": {}}
906
+ if not coldkey_identities:
907
+ return identities
908
+ query = await self.substrate.query_multiple( # TODO probably more efficient to do this with query_multi
909
+ params=list(coldkey_identities.keys()),
910
+ module="SubtensorModule",
911
+ storage_function="OwnedHotkeys",
912
+ block_hash=block_hash,
913
+ reuse_block_hash=reuse_block,
914
+ )
915
+
916
+ for coldkey_ss58, hotkeys in query.items():
917
+ coldkey_identity = coldkey_identities.get(coldkey_ss58)
918
+ hotkeys = [decode_account_id(hotkey[0]) for hotkey in hotkeys or []]
919
+
920
+ identities["coldkeys"][coldkey_ss58] = {
921
+ "identity": coldkey_identity,
922
+ "hotkeys": hotkeys,
923
+ }
924
+
925
+ for hotkey_ss58 in hotkeys:
926
+ identities["hotkeys"][hotkey_ss58] = {
927
+ "coldkey": coldkey_ss58,
928
+ "identity": coldkey_identity,
929
+ }
930
+
931
+ return identities
932
+
780
933
  async def weights(
781
934
  self, netuid: int, block_hash: Optional[str] = None
782
935
  ) -> list[tuple[int, list[tuple[int, int]]]]:
@@ -785,7 +938,6 @@ class SubtensorInterface:
785
938
  This function maps each neuron's UID to the weights it assigns to other neurons, reflecting the
786
939
  network's trust and value assignment mechanisms.
787
940
 
788
- Args:
789
941
  :param netuid: The network UID of the subnet to query.
790
942
  :param block_hash: The hash of the blockchain block for the query.
791
943
 
@@ -794,14 +946,15 @@ class SubtensorInterface:
794
946
  The weight distribution is a key factor in the network's consensus algorithm and the ranking of neurons,
795
947
  influencing their influence and reward allocation within the subnet.
796
948
  """
797
- # TODO look into seeing if we can speed this up with storage query
798
949
  w_map_encoded = await self.substrate.query_map(
799
950
  module="SubtensorModule",
800
951
  storage_function="Weights",
801
952
  params=[netuid],
802
953
  block_hash=block_hash,
803
954
  )
804
- w_map = [(uid, w or []) async for uid, w in w_map_encoded]
955
+ w_map = []
956
+ async for uid, w in w_map_encoded:
957
+ w_map.append((uid, w.value))
805
958
 
806
959
  return w_map
807
960
 
@@ -829,7 +982,9 @@ class SubtensorInterface:
829
982
  params=[netuid],
830
983
  block_hash=block_hash,
831
984
  )
832
- b_map = [(uid, b) async for uid, b in b_map_encoded]
985
+ b_map = []
986
+ async for uid, b in b_map_encoded:
987
+ b_map.append((uid, b))
833
988
 
834
989
  return b_map
835
990
 
@@ -848,31 +1003,27 @@ class SubtensorInterface:
848
1003
 
849
1004
  :return: `True` if the hotkey is known by the chain and there are accounts, `False` otherwise.
850
1005
  """
851
- _result = await self.substrate.query(
1006
+ result = await self.query(
852
1007
  module="SubtensorModule",
853
1008
  storage_function="Owner",
854
1009
  params=[hotkey_ss58],
855
1010
  block_hash=block_hash,
856
1011
  reuse_block_hash=reuse_block,
857
1012
  )
858
- result = decode_account_id(_result[0])
859
- return_val = (
860
- False
861
- if result is None
862
- else result != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"
863
- )
1013
+ return_val = result != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"
864
1014
  return return_val
865
1015
 
866
1016
  async def get_hotkey_owner(
867
- self, hotkey_ss58: str, block_hash: str
1017
+ self,
1018
+ hotkey_ss58: str,
1019
+ block_hash: Optional[str] = None,
868
1020
  ) -> Optional[str]:
869
- hk_owner_query = await self.substrate.query(
1021
+ val = await self.query(
870
1022
  module="SubtensorModule",
871
1023
  storage_function="Owner",
872
1024
  params=[hotkey_ss58],
873
1025
  block_hash=block_hash,
874
1026
  )
875
- val = decode_account_id(hk_owner_query[0])
876
1027
  if val:
877
1028
  exists = await self.does_hotkey_exist(hotkey_ss58, block_hash=block_hash)
878
1029
  else:
@@ -929,7 +1080,7 @@ class SubtensorInterface:
929
1080
  message (if applicable)
930
1081
  """
931
1082
  try:
932
- children = await self.substrate.query(
1083
+ children = await self.query(
933
1084
  module="SubtensorModule",
934
1085
  storage_function="ChildKeys",
935
1086
  params=[hotkey, netuid],
@@ -962,17 +1113,26 @@ class SubtensorInterface:
962
1113
  Understanding the hyperparameters is crucial for comprehending how subnets are configured and
963
1114
  managed, and how they interact with the network's consensus and incentive mechanisms.
964
1115
  """
965
- hex_bytes_result = await self.query_runtime_api(
1116
+ result = await self.query_runtime_api(
966
1117
  runtime_api="SubnetInfoRuntimeApi",
967
1118
  method="get_subnet_hyperparams",
968
1119
  params=[netuid],
969
1120
  block_hash=block_hash,
970
1121
  )
971
1122
 
972
- if hex_bytes_result is None:
1123
+ if not result:
973
1124
  return []
974
1125
 
975
- return SubnetHyperparameters.from_vec_u8(hex_to_bytes(hex_bytes_result))
1126
+ return SubnetHyperparameters.from_any(result)
1127
+
1128
+ async def burn_cost(self, block_hash: Optional[str] = None) -> Optional[Balance]:
1129
+ result = await self.query_runtime_api(
1130
+ runtime_api="SubnetRegistrationRuntimeApi",
1131
+ method="get_network_registration_cost",
1132
+ params=[],
1133
+ block_hash=block_hash,
1134
+ )
1135
+ return Balance.from_rao(result) if result is not None else None
976
1136
 
977
1137
  async def get_vote_data(
978
1138
  self,
@@ -993,7 +1153,7 @@ class SubtensorInterface:
993
1153
  This function is important for tracking and understanding the decision-making processes within
994
1154
  the Bittensor network, particularly how proposals are received and acted upon by the governing body.
995
1155
  """
996
- vote_data = await self.substrate.query(
1156
+ vote_data = await self.query(
997
1157
  module="Triumvirate",
998
1158
  storage_function="Voting",
999
1159
  params=[proposal_hash],
@@ -1013,10 +1173,9 @@ class SubtensorInterface:
1013
1173
  is filled-in by the info from GitHub. At some point, we want to totally move away from fetching this info
1014
1174
  from GitHub, but chain data is still limited in that regard.
1015
1175
 
1016
- Args:
1017
- block_hash: the hash of the blockchain block for the query
1176
+ :param block_hash: the hash of the blockchain block for the query
1018
1177
 
1019
- Returns: {ss58: DelegatesDetails, ...}
1178
+ :return: {ss58: DelegatesDetails, ...}
1020
1179
 
1021
1180
  """
1022
1181
  timeout = aiohttp.ClientTimeout(10.0)
@@ -1030,12 +1189,17 @@ class SubtensorInterface:
1030
1189
  session.get(Constants.delegates_detail_url),
1031
1190
  )
1032
1191
 
1033
- all_delegates_details = {
1034
- decode_account_id(ss58_address[0]): DelegatesDetails.from_chain_data(
1035
- decode_hex_identity_dict(identity["info"])
1192
+ all_delegates_details = {}
1193
+ async for ss58_address, identity in identities_info:
1194
+ all_delegates_details.update(
1195
+ {
1196
+ decode_account_id(
1197
+ ss58_address[0]
1198
+ ): DelegatesDetails.from_chain_data(
1199
+ decode_hex_identity_dict(identity.value["info"])
1200
+ )
1201
+ }
1036
1202
  )
1037
- for ss58_address, identity in identities_info
1038
- }
1039
1203
 
1040
1204
  if response.ok:
1041
1205
  all_delegates: dict[str, Any] = await response.json(content_type=None)
@@ -1066,3 +1230,156 @@ class SubtensorInterface:
1066
1230
  )
1067
1231
 
1068
1232
  return all_delegates_details
1233
+
1234
+ async def get_stake_for_coldkey_and_hotkey_on_netuid(
1235
+ self,
1236
+ hotkey_ss58: str,
1237
+ coldkey_ss58: str,
1238
+ netuid: int,
1239
+ block_hash: Optional[str] = None,
1240
+ ) -> "Balance":
1241
+ """Returns the stake under a coldkey - hotkey - netuid pairing"""
1242
+ _result = await self.query(
1243
+ "SubtensorModule",
1244
+ "Alpha",
1245
+ [hotkey_ss58, coldkey_ss58, netuid],
1246
+ block_hash,
1247
+ )
1248
+ if _result is None:
1249
+ return Balance(0).set_unit(netuid)
1250
+ else:
1251
+ return Balance.from_rao(_result).set_unit(int(netuid))
1252
+
1253
+ async def multi_get_stake_for_coldkey_and_hotkey_on_netuid(
1254
+ self,
1255
+ hotkey_ss58s: list[str],
1256
+ coldkey_ss58: str,
1257
+ netuids: list[int],
1258
+ block_hash: Optional[str] = None,
1259
+ ) -> dict[str, dict[int, "Balance"]]:
1260
+ """
1261
+ Queries the stake for multiple hotkey - coldkey - netuid pairings.
1262
+
1263
+ :param hotkey_ss58s: list of hotkey ss58 addresses
1264
+ :param coldkey_ss58: a single coldkey ss58 address
1265
+ :param netuids: list of netuids
1266
+ :param block_hash: hash of the blockchain block, if any
1267
+
1268
+ :return:
1269
+ {
1270
+ hotkey_ss58_1: {
1271
+ netuid_1: netuid1_stake,
1272
+ netuid_2: netuid2_stake,
1273
+ ...
1274
+ },
1275
+ hotkey_ss58_2: {
1276
+ netuid_1: netuid1_stake,
1277
+ netuid_2: netuid2_stake,
1278
+ ...
1279
+ },
1280
+ ...
1281
+ }
1282
+
1283
+ """
1284
+ calls = [
1285
+ (
1286
+ await self.substrate.create_storage_key(
1287
+ "SubtensorModule",
1288
+ "Alpha",
1289
+ [hk_ss58, coldkey_ss58, netuid],
1290
+ block_hash=block_hash,
1291
+ )
1292
+ )
1293
+ for hk_ss58 in hotkey_ss58s
1294
+ for netuid in netuids
1295
+ ]
1296
+ batch_call = await self.substrate.query_multi(calls, block_hash=block_hash)
1297
+ results: dict[str, dict[int, "Balance"]] = {
1298
+ hk_ss58: {} for hk_ss58 in hotkey_ss58s
1299
+ }
1300
+ for idx, (_, val) in enumerate(batch_call):
1301
+ hotkey_idx = idx // len(netuids)
1302
+ netuid_idx = idx % len(netuids)
1303
+ hotkey_ss58 = hotkey_ss58s[hotkey_idx]
1304
+ netuid = netuids[netuid_idx]
1305
+ value = (
1306
+ Balance.from_rao(val).set_unit(netuid)
1307
+ if val is not None
1308
+ else Balance(0).set_unit(netuid)
1309
+ )
1310
+ results[hotkey_ss58][netuid] = value
1311
+ return results
1312
+
1313
+ async def get_stake_for_coldkeys(
1314
+ self, coldkey_ss58_list: list[str], block_hash: Optional[str] = None
1315
+ ) -> Optional[dict[str, list[StakeInfo]]]:
1316
+ """
1317
+ Retrieves stake information for a list of coldkeys. This function aggregates stake data for multiple
1318
+ accounts, providing a collective view of their stakes and delegations.
1319
+
1320
+ :param coldkey_ss58_list: A list of SS58 addresses of the accounts' coldkeys.
1321
+ :param block_hash: The blockchain block number for the query.
1322
+
1323
+ :return: A dictionary mapping each coldkey to a list of its StakeInfo objects.
1324
+
1325
+ This function is useful for analyzing the stake distribution and delegation patterns of multiple
1326
+ accounts simultaneously, offering a broader perspective on network participation and investment strategies.
1327
+ """
1328
+ result = await self.query_runtime_api(
1329
+ runtime_api="StakeInfoRuntimeApi",
1330
+ method="get_stake_info_for_coldkeys",
1331
+ params=[coldkey_ss58_list],
1332
+ block_hash=block_hash,
1333
+ )
1334
+ if result is None:
1335
+ return None
1336
+
1337
+ stake_info_map = {}
1338
+ for coldkey_bytes, stake_info_list in result:
1339
+ coldkey_ss58 = decode_account_id(coldkey_bytes)
1340
+ stake_info_map[coldkey_ss58] = StakeInfo.list_from_any(stake_info_list)
1341
+ return stake_info_map
1342
+
1343
+ async def all_subnets(self, block_hash: Optional[str] = None) -> list[DynamicInfo]:
1344
+ result = await self.query_runtime_api(
1345
+ "SubnetInfoRuntimeApi",
1346
+ "get_all_dynamic_info",
1347
+ block_hash=block_hash,
1348
+ )
1349
+ return DynamicInfo.list_from_any(result)
1350
+
1351
+ async def subnet(
1352
+ self, netuid: int, block_hash: Optional[str] = None
1353
+ ) -> "DynamicInfo":
1354
+ result = await self.query_runtime_api(
1355
+ "SubnetInfoRuntimeApi",
1356
+ "get_dynamic_info",
1357
+ params=[netuid],
1358
+ block_hash=block_hash,
1359
+ )
1360
+ return DynamicInfo.from_any(result)
1361
+
1362
+ async def get_owned_hotkeys(
1363
+ self,
1364
+ coldkey_ss58: str,
1365
+ block_hash: Optional[str] = None,
1366
+ reuse_block: bool = False,
1367
+ ) -> list[str]:
1368
+ """
1369
+ Retrieves all hotkeys owned by a specific coldkey address.
1370
+
1371
+ :param coldkey_ss58: The SS58 address of the coldkey to query.
1372
+ :param block_hash: The hash of the blockchain block number for the query.
1373
+ :param reuse_block: Whether to reuse the last-used blockchain block hash.
1374
+
1375
+ :return: A list of hotkey SS58 addresses owned by the coldkey.
1376
+ """
1377
+ owned_hotkeys = await self.query(
1378
+ module="SubtensorModule",
1379
+ storage_function="OwnedHotkeys",
1380
+ params=[coldkey_ss58],
1381
+ block_hash=block_hash,
1382
+ reuse_block_hash=reuse_block,
1383
+ )
1384
+
1385
+ return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]