bittensor-cli 8.4.3__py3-none-any.whl → 9.0.0rc2__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.
- bittensor_cli/__init__.py +2 -2
- bittensor_cli/cli.py +1508 -1385
- bittensor_cli/src/__init__.py +627 -197
- bittensor_cli/src/bittensor/balances.py +41 -8
- bittensor_cli/src/bittensor/chain_data.py +557 -428
- bittensor_cli/src/bittensor/extrinsics/registration.py +161 -47
- bittensor_cli/src/bittensor/extrinsics/root.py +14 -8
- bittensor_cli/src/bittensor/extrinsics/transfer.py +14 -21
- bittensor_cli/src/bittensor/minigraph.py +46 -8
- bittensor_cli/src/bittensor/subtensor_interface.py +572 -253
- bittensor_cli/src/bittensor/utils.py +326 -75
- bittensor_cli/src/commands/stake/__init__.py +154 -0
- bittensor_cli/src/commands/stake/children_hotkeys.py +121 -87
- bittensor_cli/src/commands/stake/move.py +1000 -0
- bittensor_cli/src/commands/stake/stake.py +1637 -1264
- bittensor_cli/src/commands/subnets/__init__.py +0 -0
- bittensor_cli/src/commands/subnets/price.py +867 -0
- bittensor_cli/src/commands/subnets/subnets.py +2055 -0
- bittensor_cli/src/commands/sudo.py +529 -26
- bittensor_cli/src/commands/wallets.py +234 -544
- bittensor_cli/src/commands/weights.py +15 -11
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/METADATA +7 -4
- bittensor_cli-9.0.0rc2.dist-info/RECORD +32 -0
- bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
- bittensor_cli/src/commands/root.py +0 -1752
- bittensor_cli/src/commands/subnets.py +0 -897
- bittensor_cli-8.4.3.dist-info/RECORD +0 -31
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.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
|
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
|
-
|
16
|
-
|
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
|
-
|
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(
|
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(
|
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
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
127
|
+
async def query(
|
132
128
|
self,
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
170
|
-
|
171
|
-
if
|
172
|
-
|
173
|
-
|
173
|
+
res = []
|
174
|
+
async for netuid, exists in result:
|
175
|
+
if exists.value:
|
176
|
+
res.append(netuid)
|
177
|
+
return res
|
174
178
|
|
175
|
-
async def
|
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 []
|
220
|
-
|
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
|
-
|
199
|
+
result = await self.query_runtime_api(
|
243
200
|
runtime_api="StakeInfoRuntimeApi",
|
244
201
|
method="get_stake_info_for_coldkey",
|
245
|
-
params=[
|
202
|
+
params=[coldkey_ss58],
|
246
203
|
block_hash=block_hash,
|
247
204
|
reuse_block=reuse_block,
|
248
205
|
)
|
249
206
|
|
250
|
-
if
|
207
|
+
if result is None:
|
251
208
|
return []
|
252
|
-
|
253
|
-
return
|
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,
|
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
|
-
|
260
|
-
|
261
|
-
:param
|
262
|
-
:param
|
263
|
-
:
|
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
|
-
|
230
|
+
alpha_shares = await self.query(
|
266
231
|
module="SubtensorModule",
|
267
|
-
storage_function="
|
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
|
-
|
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
|
268
|
+
params: Optional[Union[list, dict]] = None,
|
278
269
|
block_hash: Optional[str] = None,
|
279
270
|
reuse_block: Optional[bool] = False,
|
280
|
-
) -> Optional[
|
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
|
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
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
-
)
|
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
|
312
293
|
|
313
|
-
|
314
|
-
return None
|
315
|
-
|
316
|
-
return_type = call_definition["type"]
|
317
|
-
|
318
|
-
as_scale_bytes = scalecodec.ScaleBytes(json_result["result"]) # type: ignore
|
319
|
-
|
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
|
-
|
325
|
-
|
326
|
-
|
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
|
-
|
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
|
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
|
-
"
|
378
|
-
[
|
440
|
+
"TotalHotkeyAlpha",
|
441
|
+
params=[ss58, netuid],
|
379
442
|
block_hash=block_hash,
|
380
443
|
)
|
381
444
|
)
|
382
|
-
for
|
445
|
+
for ss58 in ss58_addresses
|
446
|
+
for netuid in netuids
|
383
447
|
]
|
384
|
-
|
385
|
-
results = {
|
386
|
-
|
387
|
-
|
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
|
461
|
+
async def current_take(
|
391
462
|
self,
|
392
|
-
|
463
|
+
hotkey_ss58: int,
|
393
464
|
block_hash: Optional[str] = None,
|
394
465
|
reuse_block: bool = False,
|
395
|
-
) ->
|
466
|
+
) -> Optional[float]:
|
396
467
|
"""
|
397
|
-
|
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
|
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:
|
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
|
-
|
406
|
-
params=[s for s in ss58_addresses],
|
480
|
+
result = await self.query(
|
407
481
|
module="SubtensorModule",
|
408
|
-
storage_function="
|
482
|
+
storage_function="Delegates",
|
483
|
+
params=[hotkey_ss58],
|
409
484
|
block_hash=block_hash,
|
410
485
|
reuse_block_hash=reuse_block,
|
411
486
|
)
|
412
|
-
|
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
|
-
|
440
|
-
|
441
|
-
if
|
442
|
-
|
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.
|
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.
|
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 =
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
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
|
-
|
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
|
748
|
+
if result is None:
|
646
749
|
return []
|
647
750
|
|
648
|
-
return NeuronInfoLite.
|
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
|
-
|
672
|
-
|
673
|
-
method="
|
674
|
-
params=
|
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
|
784
|
+
if not result:
|
678
785
|
return NeuronInfo.get_null_neuron()
|
679
786
|
|
680
|
-
|
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
|
-
|
709
|
-
|
710
|
-
method="
|
711
|
-
params=
|
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
|
821
|
+
if not result:
|
715
822
|
return []
|
716
823
|
|
717
|
-
return DelegateInfo.
|
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
|
-
|
745
|
-
|
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
|
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 = [
|
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 = [
|
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
|
-
|
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
|
-
|
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,
|
1017
|
+
self,
|
1018
|
+
hotkey_ss58: str,
|
1019
|
+
block_hash: Optional[str] = None,
|
868
1020
|
) -> Optional[str]:
|
869
|
-
|
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:
|
@@ -913,9 +1064,11 @@ class SubtensorInterface:
|
|
913
1064
|
if await response.is_success:
|
914
1065
|
return True, ""
|
915
1066
|
else:
|
916
|
-
return False, format_error_message(
|
1067
|
+
return False, format_error_message(
|
1068
|
+
await response.error_message, substrate=self.substrate
|
1069
|
+
)
|
917
1070
|
except SubstrateRequestException as e:
|
918
|
-
return False, format_error_message(e)
|
1071
|
+
return False, format_error_message(e, substrate=self.substrate)
|
919
1072
|
|
920
1073
|
async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
|
921
1074
|
"""
|
@@ -929,7 +1082,7 @@ class SubtensorInterface:
|
|
929
1082
|
message (if applicable)
|
930
1083
|
"""
|
931
1084
|
try:
|
932
|
-
children = await self.
|
1085
|
+
children = await self.query(
|
933
1086
|
module="SubtensorModule",
|
934
1087
|
storage_function="ChildKeys",
|
935
1088
|
params=[hotkey, netuid],
|
@@ -945,7 +1098,7 @@ class SubtensorInterface:
|
|
945
1098
|
else:
|
946
1099
|
return True, [], ""
|
947
1100
|
except SubstrateRequestException as e:
|
948
|
-
return False, [], format_error_message(e)
|
1101
|
+
return False, [], format_error_message(e, self.substrate)
|
949
1102
|
|
950
1103
|
async def get_subnet_hyperparameters(
|
951
1104
|
self, netuid: int, block_hash: Optional[str] = None
|
@@ -962,17 +1115,26 @@ class SubtensorInterface:
|
|
962
1115
|
Understanding the hyperparameters is crucial for comprehending how subnets are configured and
|
963
1116
|
managed, and how they interact with the network's consensus and incentive mechanisms.
|
964
1117
|
"""
|
965
|
-
|
1118
|
+
result = await self.query_runtime_api(
|
966
1119
|
runtime_api="SubnetInfoRuntimeApi",
|
967
1120
|
method="get_subnet_hyperparams",
|
968
1121
|
params=[netuid],
|
969
1122
|
block_hash=block_hash,
|
970
1123
|
)
|
971
1124
|
|
972
|
-
if
|
1125
|
+
if not result:
|
973
1126
|
return []
|
974
1127
|
|
975
|
-
return SubnetHyperparameters.
|
1128
|
+
return SubnetHyperparameters.from_any(result)
|
1129
|
+
|
1130
|
+
async def burn_cost(self, block_hash: Optional[str] = None) -> Optional[Balance]:
|
1131
|
+
result = await self.query_runtime_api(
|
1132
|
+
runtime_api="SubnetRegistrationRuntimeApi",
|
1133
|
+
method="get_network_registration_cost",
|
1134
|
+
params=[],
|
1135
|
+
block_hash=block_hash,
|
1136
|
+
)
|
1137
|
+
return Balance.from_rao(result) if result is not None else None
|
976
1138
|
|
977
1139
|
async def get_vote_data(
|
978
1140
|
self,
|
@@ -993,7 +1155,7 @@ class SubtensorInterface:
|
|
993
1155
|
This function is important for tracking and understanding the decision-making processes within
|
994
1156
|
the Bittensor network, particularly how proposals are received and acted upon by the governing body.
|
995
1157
|
"""
|
996
|
-
vote_data = await self.
|
1158
|
+
vote_data = await self.query(
|
997
1159
|
module="Triumvirate",
|
998
1160
|
storage_function="Voting",
|
999
1161
|
params=[proposal_hash],
|
@@ -1013,10 +1175,9 @@ class SubtensorInterface:
|
|
1013
1175
|
is filled-in by the info from GitHub. At some point, we want to totally move away from fetching this info
|
1014
1176
|
from GitHub, but chain data is still limited in that regard.
|
1015
1177
|
|
1016
|
-
|
1017
|
-
block_hash: the hash of the blockchain block for the query
|
1178
|
+
:param block_hash: the hash of the blockchain block for the query
|
1018
1179
|
|
1019
|
-
|
1180
|
+
:return: {ss58: DelegatesDetails, ...}
|
1020
1181
|
|
1021
1182
|
"""
|
1022
1183
|
timeout = aiohttp.ClientTimeout(10.0)
|
@@ -1030,12 +1191,17 @@ class SubtensorInterface:
|
|
1030
1191
|
session.get(Constants.delegates_detail_url),
|
1031
1192
|
)
|
1032
1193
|
|
1033
|
-
all_delegates_details = {
|
1034
|
-
|
1035
|
-
|
1194
|
+
all_delegates_details = {}
|
1195
|
+
async for ss58_address, identity in identities_info:
|
1196
|
+
all_delegates_details.update(
|
1197
|
+
{
|
1198
|
+
decode_account_id(
|
1199
|
+
ss58_address[0]
|
1200
|
+
): DelegatesDetails.from_chain_data(
|
1201
|
+
decode_hex_identity_dict(identity.value["info"])
|
1202
|
+
)
|
1203
|
+
}
|
1036
1204
|
)
|
1037
|
-
for ss58_address, identity in identities_info
|
1038
|
-
}
|
1039
1205
|
|
1040
1206
|
if response.ok:
|
1041
1207
|
all_delegates: dict[str, Any] = await response.json(content_type=None)
|
@@ -1066,3 +1232,156 @@ class SubtensorInterface:
|
|
1066
1232
|
)
|
1067
1233
|
|
1068
1234
|
return all_delegates_details
|
1235
|
+
|
1236
|
+
async def get_stake_for_coldkey_and_hotkey_on_netuid(
|
1237
|
+
self,
|
1238
|
+
hotkey_ss58: str,
|
1239
|
+
coldkey_ss58: str,
|
1240
|
+
netuid: int,
|
1241
|
+
block_hash: Optional[str] = None,
|
1242
|
+
) -> "Balance":
|
1243
|
+
"""Returns the stake under a coldkey - hotkey - netuid pairing"""
|
1244
|
+
_result = await self.query(
|
1245
|
+
"SubtensorModule",
|
1246
|
+
"Alpha",
|
1247
|
+
[hotkey_ss58, coldkey_ss58, netuid],
|
1248
|
+
block_hash,
|
1249
|
+
)
|
1250
|
+
if _result is None:
|
1251
|
+
return Balance(0).set_unit(netuid)
|
1252
|
+
else:
|
1253
|
+
return Balance.from_rao(_result).set_unit(int(netuid))
|
1254
|
+
|
1255
|
+
async def multi_get_stake_for_coldkey_and_hotkey_on_netuid(
|
1256
|
+
self,
|
1257
|
+
hotkey_ss58s: list[str],
|
1258
|
+
coldkey_ss58: str,
|
1259
|
+
netuids: list[int],
|
1260
|
+
block_hash: Optional[str] = None,
|
1261
|
+
) -> dict[str, dict[int, "Balance"]]:
|
1262
|
+
"""
|
1263
|
+
Queries the stake for multiple hotkey - coldkey - netuid pairings.
|
1264
|
+
|
1265
|
+
:param hotkey_ss58s: list of hotkey ss58 addresses
|
1266
|
+
:param coldkey_ss58: a single coldkey ss58 address
|
1267
|
+
:param netuids: list of netuids
|
1268
|
+
:param block_hash: hash of the blockchain block, if any
|
1269
|
+
|
1270
|
+
:return:
|
1271
|
+
{
|
1272
|
+
hotkey_ss58_1: {
|
1273
|
+
netuid_1: netuid1_stake,
|
1274
|
+
netuid_2: netuid2_stake,
|
1275
|
+
...
|
1276
|
+
},
|
1277
|
+
hotkey_ss58_2: {
|
1278
|
+
netuid_1: netuid1_stake,
|
1279
|
+
netuid_2: netuid2_stake,
|
1280
|
+
...
|
1281
|
+
},
|
1282
|
+
...
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
"""
|
1286
|
+
calls = [
|
1287
|
+
(
|
1288
|
+
await self.substrate.create_storage_key(
|
1289
|
+
"SubtensorModule",
|
1290
|
+
"Alpha",
|
1291
|
+
[hk_ss58, coldkey_ss58, netuid],
|
1292
|
+
block_hash=block_hash,
|
1293
|
+
)
|
1294
|
+
)
|
1295
|
+
for hk_ss58 in hotkey_ss58s
|
1296
|
+
for netuid in netuids
|
1297
|
+
]
|
1298
|
+
batch_call = await self.substrate.query_multi(calls, block_hash=block_hash)
|
1299
|
+
results: dict[str, dict[int, "Balance"]] = {
|
1300
|
+
hk_ss58: {} for hk_ss58 in hotkey_ss58s
|
1301
|
+
}
|
1302
|
+
for idx, (_, val) in enumerate(batch_call):
|
1303
|
+
hotkey_idx = idx // len(netuids)
|
1304
|
+
netuid_idx = idx % len(netuids)
|
1305
|
+
hotkey_ss58 = hotkey_ss58s[hotkey_idx]
|
1306
|
+
netuid = netuids[netuid_idx]
|
1307
|
+
value = (
|
1308
|
+
Balance.from_rao(val).set_unit(netuid)
|
1309
|
+
if val is not None
|
1310
|
+
else Balance(0).set_unit(netuid)
|
1311
|
+
)
|
1312
|
+
results[hotkey_ss58][netuid] = value
|
1313
|
+
return results
|
1314
|
+
|
1315
|
+
async def get_stake_for_coldkeys(
|
1316
|
+
self, coldkey_ss58_list: list[str], block_hash: Optional[str] = None
|
1317
|
+
) -> Optional[dict[str, list[StakeInfo]]]:
|
1318
|
+
"""
|
1319
|
+
Retrieves stake information for a list of coldkeys. This function aggregates stake data for multiple
|
1320
|
+
accounts, providing a collective view of their stakes and delegations.
|
1321
|
+
|
1322
|
+
:param coldkey_ss58_list: A list of SS58 addresses of the accounts' coldkeys.
|
1323
|
+
:param block_hash: The blockchain block number for the query.
|
1324
|
+
|
1325
|
+
:return: A dictionary mapping each coldkey to a list of its StakeInfo objects.
|
1326
|
+
|
1327
|
+
This function is useful for analyzing the stake distribution and delegation patterns of multiple
|
1328
|
+
accounts simultaneously, offering a broader perspective on network participation and investment strategies.
|
1329
|
+
"""
|
1330
|
+
result = await self.query_runtime_api(
|
1331
|
+
runtime_api="StakeInfoRuntimeApi",
|
1332
|
+
method="get_stake_info_for_coldkeys",
|
1333
|
+
params=coldkey_ss58_list,
|
1334
|
+
block_hash=block_hash,
|
1335
|
+
)
|
1336
|
+
if result is None:
|
1337
|
+
return None
|
1338
|
+
|
1339
|
+
stake_info_map = {}
|
1340
|
+
for coldkey_bytes, stake_info_list in result:
|
1341
|
+
coldkey_ss58 = decode_account_id(coldkey_bytes)
|
1342
|
+
stake_info_map[coldkey_ss58] = StakeInfo.list_from_any(stake_info_list)
|
1343
|
+
return stake_info_map
|
1344
|
+
|
1345
|
+
async def all_subnets(self, block_hash: Optional[str] = None) -> list[DynamicInfo]:
|
1346
|
+
result = await self.query_runtime_api(
|
1347
|
+
"SubnetInfoRuntimeApi",
|
1348
|
+
"get_all_dynamic_info",
|
1349
|
+
block_hash=block_hash,
|
1350
|
+
)
|
1351
|
+
return DynamicInfo.list_from_any(result)
|
1352
|
+
|
1353
|
+
async def subnet(
|
1354
|
+
self, netuid: int, block_hash: Optional[str] = None
|
1355
|
+
) -> "DynamicInfo":
|
1356
|
+
result = await self.query_runtime_api(
|
1357
|
+
"SubnetInfoRuntimeApi",
|
1358
|
+
"get_dynamic_info",
|
1359
|
+
params=[netuid],
|
1360
|
+
block_hash=block_hash,
|
1361
|
+
)
|
1362
|
+
return DynamicInfo.from_any(result)
|
1363
|
+
|
1364
|
+
async def get_owned_hotkeys(
|
1365
|
+
self,
|
1366
|
+
coldkey_ss58: str,
|
1367
|
+
block_hash: Optional[str] = None,
|
1368
|
+
reuse_block: bool = False,
|
1369
|
+
) -> list[str]:
|
1370
|
+
"""
|
1371
|
+
Retrieves all hotkeys owned by a specific coldkey address.
|
1372
|
+
|
1373
|
+
:param coldkey_ss58: The SS58 address of the coldkey to query.
|
1374
|
+
:param block_hash: The hash of the blockchain block number for the query.
|
1375
|
+
:param reuse_block: Whether to reuse the last-used blockchain block hash.
|
1376
|
+
|
1377
|
+
:return: A list of hotkey SS58 addresses owned by the coldkey.
|
1378
|
+
"""
|
1379
|
+
owned_hotkeys = await self.query(
|
1380
|
+
module="SubtensorModule",
|
1381
|
+
storage_function="OwnedHotkeys",
|
1382
|
+
params=[coldkey_ss58],
|
1383
|
+
block_hash=block_hash,
|
1384
|
+
reuse_block_hash=reuse_block,
|
1385
|
+
)
|
1386
|
+
|
1387
|
+
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
|