bittensor-cli 8.4.2__py3-none-any.whl → 9.0.0rc1__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 +1503 -1372
- bittensor_cli/src/__init__.py +625 -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 +123 -91
- 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 +2043 -0
- bittensor_cli/src/commands/sudo.py +529 -26
- bittensor_cli/src/commands/wallets.py +231 -535
- bittensor_cli/src/commands/weights.py +15 -11
- {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/METADATA +7 -4
- bittensor_cli-9.0.0rc1.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.2.dist-info/RECORD +0 -31
- {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,1752 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import json
|
3
|
-
from typing import Optional, TYPE_CHECKING
|
4
|
-
|
5
|
-
from bittensor_wallet import Wallet
|
6
|
-
import numpy as np
|
7
|
-
from numpy.typing import NDArray
|
8
|
-
from rich import box
|
9
|
-
from rich.prompt import Confirm
|
10
|
-
from rich.table import Column, Table
|
11
|
-
from rich.text import Text
|
12
|
-
from scalecodec import GenericCall, ScaleType
|
13
|
-
from substrateinterface.exceptions import SubstrateRequestException
|
14
|
-
import typer
|
15
|
-
|
16
|
-
from bittensor_cli.src import DelegatesDetails
|
17
|
-
from bittensor_cli.src.bittensor.balances import Balance
|
18
|
-
from bittensor_cli.src.bittensor.chain_data import (
|
19
|
-
DelegateInfo,
|
20
|
-
NeuronInfoLite,
|
21
|
-
decode_account_id,
|
22
|
-
)
|
23
|
-
from bittensor_cli.src.bittensor.extrinsics.root import (
|
24
|
-
root_register_extrinsic,
|
25
|
-
set_root_weights_extrinsic,
|
26
|
-
)
|
27
|
-
from bittensor_cli.src.commands.wallets import (
|
28
|
-
get_coldkey_wallets_for_path,
|
29
|
-
set_id,
|
30
|
-
set_id_prompts,
|
31
|
-
)
|
32
|
-
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
|
33
|
-
from bittensor_cli.src.bittensor.utils import (
|
34
|
-
console,
|
35
|
-
convert_weight_uids_and_vals_to_tensor,
|
36
|
-
create_table,
|
37
|
-
err_console,
|
38
|
-
print_verbose,
|
39
|
-
get_metadata_table,
|
40
|
-
render_table,
|
41
|
-
ss58_to_vec_u8,
|
42
|
-
update_metadata_table,
|
43
|
-
group_subnets,
|
44
|
-
unlock_key,
|
45
|
-
)
|
46
|
-
|
47
|
-
if TYPE_CHECKING:
|
48
|
-
from bittensor_cli.src.bittensor.subtensor_interface import ProposalVoteData
|
49
|
-
|
50
|
-
# helpers
|
51
|
-
|
52
|
-
|
53
|
-
def display_votes(
|
54
|
-
vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails]
|
55
|
-
) -> str:
|
56
|
-
vote_list = list()
|
57
|
-
|
58
|
-
for address in vote_data.ayes:
|
59
|
-
vote_list.append(
|
60
|
-
"{}: {}".format(
|
61
|
-
delegate_info[address].display if address in delegate_info else address,
|
62
|
-
"[bold green]Aye[/bold green]",
|
63
|
-
)
|
64
|
-
)
|
65
|
-
|
66
|
-
for address in vote_data.nays:
|
67
|
-
vote_list.append(
|
68
|
-
"{}: {}".format(
|
69
|
-
delegate_info[address].display if address in delegate_info else address,
|
70
|
-
"[bold red]Nay[/bold red]",
|
71
|
-
)
|
72
|
-
)
|
73
|
-
|
74
|
-
return "\n".join(vote_list)
|
75
|
-
|
76
|
-
|
77
|
-
def format_call_data(call_data: dict) -> str:
|
78
|
-
# Extract the module and call details
|
79
|
-
module, call_details = next(iter(call_data.items()))
|
80
|
-
|
81
|
-
# Extract the call function name and arguments
|
82
|
-
call_info = call_details[0]
|
83
|
-
call_function, call_args = next(iter(call_info.items()))
|
84
|
-
|
85
|
-
# Extract the argument, handling tuple values
|
86
|
-
formatted_args = ", ".join(
|
87
|
-
str(arg[0]) if isinstance(arg, tuple) else str(arg)
|
88
|
-
for arg in call_args.values()
|
89
|
-
)
|
90
|
-
|
91
|
-
# Format the final output string
|
92
|
-
return f"{call_function}({formatted_args})"
|
93
|
-
|
94
|
-
|
95
|
-
async def _get_senate_members(
|
96
|
-
subtensor: SubtensorInterface, block_hash: Optional[str] = None
|
97
|
-
) -> list[str]:
|
98
|
-
"""
|
99
|
-
Gets all members of the senate on the given subtensor's network
|
100
|
-
|
101
|
-
:param subtensor: SubtensorInterface object to use for the query
|
102
|
-
|
103
|
-
:return: list of the senate members' ss58 addresses
|
104
|
-
"""
|
105
|
-
senate_members = await subtensor.substrate.query(
|
106
|
-
module="SenateMembers",
|
107
|
-
storage_function="Members",
|
108
|
-
params=None,
|
109
|
-
block_hash=block_hash,
|
110
|
-
)
|
111
|
-
try:
|
112
|
-
return [
|
113
|
-
decode_account_id(i[x][0]) for i in senate_members for x in range(len(i))
|
114
|
-
]
|
115
|
-
except (IndexError, TypeError):
|
116
|
-
err_console.print("Unable to retrieve senate members.")
|
117
|
-
return []
|
118
|
-
|
119
|
-
|
120
|
-
async def _get_proposals(
|
121
|
-
subtensor: SubtensorInterface, block_hash: str
|
122
|
-
) -> dict[str, tuple[dict, "ProposalVoteData"]]:
|
123
|
-
async def get_proposal_call_data(p_hash: str) -> Optional[GenericCall]:
|
124
|
-
proposal_data = await subtensor.substrate.query(
|
125
|
-
module="Triumvirate",
|
126
|
-
storage_function="ProposalOf",
|
127
|
-
block_hash=block_hash,
|
128
|
-
params=[p_hash],
|
129
|
-
)
|
130
|
-
return proposal_data
|
131
|
-
|
132
|
-
ph = await subtensor.substrate.query(
|
133
|
-
module="Triumvirate",
|
134
|
-
storage_function="Proposals",
|
135
|
-
params=None,
|
136
|
-
block_hash=block_hash,
|
137
|
-
)
|
138
|
-
|
139
|
-
try:
|
140
|
-
proposal_hashes: list[str] = [
|
141
|
-
f"0x{bytes(ph[0][x][0]).hex()}" for x in range(len(ph[0]))
|
142
|
-
]
|
143
|
-
except (IndexError, TypeError):
|
144
|
-
err_console.print("Unable to retrieve proposal vote data")
|
145
|
-
return {}
|
146
|
-
|
147
|
-
call_data_, vote_data_ = await asyncio.gather(
|
148
|
-
asyncio.gather(*[get_proposal_call_data(h) for h in proposal_hashes]),
|
149
|
-
asyncio.gather(*[subtensor.get_vote_data(h) for h in proposal_hashes]),
|
150
|
-
)
|
151
|
-
return {
|
152
|
-
proposal_hash: (cd, vd)
|
153
|
-
for cd, vd, proposal_hash in zip(call_data_, vote_data_, proposal_hashes)
|
154
|
-
}
|
155
|
-
|
156
|
-
|
157
|
-
def _validate_proposal_hash(proposal_hash: str) -> bool:
|
158
|
-
if proposal_hash[0:2] != "0x" or len(proposal_hash) != 66:
|
159
|
-
return False
|
160
|
-
else:
|
161
|
-
return True
|
162
|
-
|
163
|
-
|
164
|
-
async def _is_senate_member(subtensor: SubtensorInterface, hotkey_ss58: str) -> bool:
|
165
|
-
"""
|
166
|
-
Checks if a given neuron (identified by its hotkey SS58 address) is a member of the Bittensor senate.
|
167
|
-
The senate is a key governance body within the Bittensor network, responsible for overseeing and
|
168
|
-
approving various network operations and proposals.
|
169
|
-
|
170
|
-
:param subtensor: SubtensorInterface object to use for the query
|
171
|
-
:param hotkey_ss58: The `SS58` address of the neuron's hotkey.
|
172
|
-
|
173
|
-
:return: `True` if the neuron is a senate member at the given block, `False` otherwise.
|
174
|
-
|
175
|
-
This function is crucial for understanding the governance dynamics of the Bittensor network and for
|
176
|
-
identifying the neurons that hold decision-making power within the network.
|
177
|
-
"""
|
178
|
-
|
179
|
-
senate_members = await _get_senate_members(subtensor)
|
180
|
-
|
181
|
-
if not hasattr(senate_members, "count"):
|
182
|
-
return False
|
183
|
-
|
184
|
-
return senate_members.count(hotkey_ss58) > 0
|
185
|
-
|
186
|
-
|
187
|
-
async def vote_senate_extrinsic(
|
188
|
-
subtensor: SubtensorInterface,
|
189
|
-
wallet: Wallet,
|
190
|
-
proposal_hash: str,
|
191
|
-
proposal_idx: int,
|
192
|
-
vote: bool,
|
193
|
-
wait_for_inclusion: bool = False,
|
194
|
-
wait_for_finalization: bool = True,
|
195
|
-
prompt: bool = False,
|
196
|
-
) -> bool:
|
197
|
-
"""Votes ayes or nays on proposals.
|
198
|
-
|
199
|
-
:param subtensor: The SubtensorInterface object to use for the query
|
200
|
-
:param wallet: Bittensor wallet object, with coldkey and hotkey unlocked.
|
201
|
-
:param proposal_hash: The hash of the proposal for which voting data is requested.
|
202
|
-
:param proposal_idx: The index of the proposal to vote.
|
203
|
-
:param vote: Whether to vote aye or nay.
|
204
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
205
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
206
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
207
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
208
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
209
|
-
|
210
|
-
:return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
211
|
-
finalization/inclusion, the response is `True`.
|
212
|
-
"""
|
213
|
-
|
214
|
-
if prompt:
|
215
|
-
# Prompt user for confirmation.
|
216
|
-
if not Confirm.ask(f"Cast a vote of {vote}?"):
|
217
|
-
return False
|
218
|
-
|
219
|
-
with console.status(":satellite: Casting vote..", spinner="aesthetic"):
|
220
|
-
call = await subtensor.substrate.compose_call(
|
221
|
-
call_module="SubtensorModule",
|
222
|
-
call_function="vote",
|
223
|
-
call_params={
|
224
|
-
"hotkey": wallet.hotkey.ss58_address,
|
225
|
-
"proposal": proposal_hash,
|
226
|
-
"index": proposal_idx,
|
227
|
-
"approve": vote,
|
228
|
-
},
|
229
|
-
)
|
230
|
-
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
231
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
232
|
-
)
|
233
|
-
if not success:
|
234
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
235
|
-
await asyncio.sleep(0.5)
|
236
|
-
return False
|
237
|
-
# Successful vote, final check for data
|
238
|
-
else:
|
239
|
-
if vote_data := await subtensor.get_vote_data(proposal_hash):
|
240
|
-
if (
|
241
|
-
vote_data.ayes.count(wallet.hotkey.ss58_address) > 0
|
242
|
-
or vote_data.nays.count(wallet.hotkey.ss58_address) > 0
|
243
|
-
):
|
244
|
-
console.print(":white_heavy_check_mark: [green]Vote cast.[/green]")
|
245
|
-
return True
|
246
|
-
else:
|
247
|
-
# hotkey not found in ayes/nays
|
248
|
-
err_console.print(
|
249
|
-
":cross_mark: [red]Unknown error. Couldn't find vote.[/red]"
|
250
|
-
)
|
251
|
-
return False
|
252
|
-
else:
|
253
|
-
return False
|
254
|
-
|
255
|
-
|
256
|
-
async def burned_register_extrinsic(
|
257
|
-
subtensor: SubtensorInterface,
|
258
|
-
wallet: Wallet,
|
259
|
-
netuid: int,
|
260
|
-
recycle_amount: Balance,
|
261
|
-
old_balance: Balance,
|
262
|
-
wait_for_inclusion: bool = True,
|
263
|
-
wait_for_finalization: bool = True,
|
264
|
-
prompt: bool = False,
|
265
|
-
) -> bool:
|
266
|
-
"""Registers the wallet to chain by recycling TAO.
|
267
|
-
|
268
|
-
:param subtensor: The SubtensorInterface object to use for the call, initialized
|
269
|
-
:param wallet: Bittensor wallet object.
|
270
|
-
:param netuid: The `netuid` of the subnet to register on.
|
271
|
-
:param recycle_amount: The amount of TAO required for this burn.
|
272
|
-
:param old_balance: The wallet balance prior to the registration burn.
|
273
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
274
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
275
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
276
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
277
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
278
|
-
|
279
|
-
:return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
280
|
-
finalization/inclusion, the response is `True`.
|
281
|
-
"""
|
282
|
-
|
283
|
-
if not unlock_key(wallet).success:
|
284
|
-
return False
|
285
|
-
|
286
|
-
with console.status(
|
287
|
-
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
|
288
|
-
spinner="aesthetic",
|
289
|
-
) as status:
|
290
|
-
my_uid = await subtensor.substrate.query(
|
291
|
-
"SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
|
292
|
-
)
|
293
|
-
|
294
|
-
print_verbose("Checking if already registered", status)
|
295
|
-
neuron = await subtensor.neuron_for_uid(
|
296
|
-
uid=my_uid,
|
297
|
-
netuid=netuid,
|
298
|
-
block_hash=subtensor.substrate.last_block_hash,
|
299
|
-
)
|
300
|
-
|
301
|
-
if not neuron.is_null:
|
302
|
-
console.print(
|
303
|
-
":white_heavy_check_mark: [green]Already Registered[/green]:\n"
|
304
|
-
f"uid: [bold white]{neuron.uid}[/bold white]\n"
|
305
|
-
f"netuid: [bold white]{neuron.netuid}[/bold white]\n"
|
306
|
-
f"hotkey: [bold white]{neuron.hotkey}[/bold white]\n"
|
307
|
-
f"coldkey: [bold white]{neuron.coldkey}[/bold white]"
|
308
|
-
)
|
309
|
-
return True
|
310
|
-
|
311
|
-
with console.status(
|
312
|
-
":satellite: Recycling TAO for Registration...", spinner="aesthetic"
|
313
|
-
):
|
314
|
-
call = await subtensor.substrate.compose_call(
|
315
|
-
call_module="SubtensorModule",
|
316
|
-
call_function="burned_register",
|
317
|
-
call_params={
|
318
|
-
"netuid": netuid,
|
319
|
-
"hotkey": wallet.hotkey.ss58_address,
|
320
|
-
},
|
321
|
-
)
|
322
|
-
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
323
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
324
|
-
)
|
325
|
-
|
326
|
-
if not success:
|
327
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
328
|
-
await asyncio.sleep(0.5)
|
329
|
-
return False
|
330
|
-
# Successful registration, final check for neuron and pubkey
|
331
|
-
else:
|
332
|
-
with console.status(":satellite: Checking Balance...", spinner="aesthetic"):
|
333
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
334
|
-
new_balance, netuids_for_hotkey, my_uid = await asyncio.gather(
|
335
|
-
subtensor.get_balance(
|
336
|
-
wallet.coldkeypub.ss58_address,
|
337
|
-
block_hash=block_hash,
|
338
|
-
reuse_block=False,
|
339
|
-
),
|
340
|
-
subtensor.get_netuids_for_hotkey(
|
341
|
-
wallet.hotkey.ss58_address, block_hash=block_hash
|
342
|
-
),
|
343
|
-
subtensor.substrate.query(
|
344
|
-
"SubtensorModule", "Uids", [netuid, wallet.hotkey.ss58_address]
|
345
|
-
),
|
346
|
-
)
|
347
|
-
|
348
|
-
console.print(
|
349
|
-
"Balance:\n"
|
350
|
-
f" [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]"
|
351
|
-
)
|
352
|
-
|
353
|
-
if len(netuids_for_hotkey) > 0:
|
354
|
-
console.print(
|
355
|
-
f":white_heavy_check_mark: [green]Registered on netuid {netuid} with UID {my_uid}[/green]"
|
356
|
-
)
|
357
|
-
return True
|
358
|
-
else:
|
359
|
-
# neuron not found, try again
|
360
|
-
err_console.print(
|
361
|
-
":cross_mark: [red]Unknown error. Neuron not found.[/red]"
|
362
|
-
)
|
363
|
-
return False
|
364
|
-
|
365
|
-
|
366
|
-
async def set_take_extrinsic(
|
367
|
-
subtensor: SubtensorInterface,
|
368
|
-
wallet: Wallet,
|
369
|
-
delegate_ss58: str,
|
370
|
-
take: float = 0.0,
|
371
|
-
wait_for_inclusion: bool = True,
|
372
|
-
wait_for_finalization: bool = False,
|
373
|
-
) -> bool:
|
374
|
-
"""
|
375
|
-
Set delegate hotkey take
|
376
|
-
|
377
|
-
:param subtensor: SubtensorInterface (initialized)
|
378
|
-
:param wallet: The wallet containing the hotkey to be nominated.
|
379
|
-
:param delegate_ss58: Hotkey
|
380
|
-
:param take: Delegate take on subnet ID
|
381
|
-
:param wait_for_finalization: If `True`, waits until the transaction is finalized on the
|
382
|
-
blockchain.
|
383
|
-
:param wait_for_inclusion: If `True`, waits until the transaction is included in a block.
|
384
|
-
|
385
|
-
:return: `True` if the process is successful, `False` otherwise.
|
386
|
-
|
387
|
-
This function is a key part of the decentralized governance mechanism of Bittensor, allowing for the
|
388
|
-
dynamic selection and participation of validators in the network's consensus process.
|
389
|
-
"""
|
390
|
-
|
391
|
-
async def _get_delegate_by_hotkey(ss58: str) -> Optional[DelegateInfo]:
|
392
|
-
"""Retrieves the delegate info for a given hotkey's ss58 address"""
|
393
|
-
encoded_hotkey = ss58_to_vec_u8(ss58)
|
394
|
-
json_body = await subtensor.substrate.rpc_request(
|
395
|
-
method="delegateInfo_getDelegate", # custom rpc method
|
396
|
-
params=([encoded_hotkey, subtensor.substrate.last_block_hash]),
|
397
|
-
)
|
398
|
-
if not (result := json_body.get("result", None)):
|
399
|
-
return None
|
400
|
-
else:
|
401
|
-
return DelegateInfo.from_vec_u8(bytes(result))
|
402
|
-
|
403
|
-
# Calculate u16 representation of the take
|
404
|
-
take_u16 = int(take * 0xFFFF)
|
405
|
-
|
406
|
-
print_verbose("Checking current take")
|
407
|
-
# Check if the new take is greater or lower than existing take or if existing is set
|
408
|
-
delegate = await _get_delegate_by_hotkey(delegate_ss58)
|
409
|
-
current_take = None
|
410
|
-
if delegate is not None:
|
411
|
-
current_take = int(
|
412
|
-
float(delegate.take) * 65535.0
|
413
|
-
) # TODO verify this, why not u16_float_to_int?
|
414
|
-
|
415
|
-
if take_u16 == current_take:
|
416
|
-
console.print("Nothing to do, take hasn't changed")
|
417
|
-
return True
|
418
|
-
if current_take is None or current_take < take_u16:
|
419
|
-
console.print(
|
420
|
-
f"Current take is {float(delegate.take):.4f}. Increasing to {take:.4f}."
|
421
|
-
)
|
422
|
-
with console.status(
|
423
|
-
f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..."
|
424
|
-
):
|
425
|
-
call = await subtensor.substrate.compose_call(
|
426
|
-
call_module="SubtensorModule",
|
427
|
-
call_function="increase_take",
|
428
|
-
call_params={
|
429
|
-
"hotkey": delegate_ss58,
|
430
|
-
"take": take_u16,
|
431
|
-
},
|
432
|
-
)
|
433
|
-
success, err = await subtensor.sign_and_send_extrinsic(call, wallet)
|
434
|
-
|
435
|
-
else:
|
436
|
-
console.print(
|
437
|
-
f"Current take is {float(delegate.take):.4f}. Decreasing to {take:.4f}."
|
438
|
-
)
|
439
|
-
with console.status(
|
440
|
-
f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..."
|
441
|
-
):
|
442
|
-
call = await subtensor.substrate.compose_call(
|
443
|
-
call_module="SubtensorModule",
|
444
|
-
call_function="decrease_take",
|
445
|
-
call_params={
|
446
|
-
"hotkey": delegate_ss58,
|
447
|
-
"take": take_u16,
|
448
|
-
},
|
449
|
-
)
|
450
|
-
success, err = await subtensor.sign_and_send_extrinsic(call, wallet)
|
451
|
-
|
452
|
-
if not success:
|
453
|
-
err_console.print(err)
|
454
|
-
else:
|
455
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
456
|
-
return success
|
457
|
-
|
458
|
-
|
459
|
-
async def delegate_extrinsic(
|
460
|
-
subtensor: SubtensorInterface,
|
461
|
-
wallet: Wallet,
|
462
|
-
delegate_ss58: str,
|
463
|
-
amount: Optional[float],
|
464
|
-
wait_for_inclusion: bool = True,
|
465
|
-
wait_for_finalization: bool = False,
|
466
|
-
prompt: bool = False,
|
467
|
-
delegate: bool = True,
|
468
|
-
) -> bool:
|
469
|
-
"""Delegates the specified amount of stake to the passed delegate.
|
470
|
-
|
471
|
-
:param subtensor: The SubtensorInterface used to perform the delegation, initialized.
|
472
|
-
:param wallet: Bittensor wallet object.
|
473
|
-
:param delegate_ss58: The `ss58` address of the delegate.
|
474
|
-
:param amount: Amount to stake as bittensor balance, None to stake all available TAO.
|
475
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
476
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
477
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
478
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
479
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
480
|
-
:param delegate: whether to delegate (`True`) or undelegate (`False`)
|
481
|
-
|
482
|
-
:return: `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion,
|
483
|
-
the response is `True`.
|
484
|
-
"""
|
485
|
-
|
486
|
-
async def _do_delegation(staking_balance_: Balance) -> tuple[bool, str]:
|
487
|
-
"""Performs the delegation extrinsic call to the chain."""
|
488
|
-
if delegate:
|
489
|
-
call = await subtensor.substrate.compose_call(
|
490
|
-
call_module="SubtensorModule",
|
491
|
-
call_function="add_stake",
|
492
|
-
call_params={
|
493
|
-
"hotkey": delegate_ss58,
|
494
|
-
"amount_staked": staking_balance_.rao,
|
495
|
-
},
|
496
|
-
)
|
497
|
-
else:
|
498
|
-
call = await subtensor.substrate.compose_call(
|
499
|
-
call_module="SubtensorModule",
|
500
|
-
call_function="remove_stake",
|
501
|
-
call_params={
|
502
|
-
"hotkey": delegate_ss58,
|
503
|
-
"amount_unstaked": staking_balance_.rao,
|
504
|
-
},
|
505
|
-
)
|
506
|
-
return await subtensor.sign_and_send_extrinsic(
|
507
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
508
|
-
)
|
509
|
-
|
510
|
-
async def get_hotkey_owner(ss58: str, block_hash_: str):
|
511
|
-
"""Returns the coldkey owner of the passed hotkey."""
|
512
|
-
if not await subtensor.does_hotkey_exist(ss58, block_hash=block_hash_):
|
513
|
-
return None
|
514
|
-
_result = await subtensor.substrate.query(
|
515
|
-
module="SubtensorModule",
|
516
|
-
storage_function="Owner",
|
517
|
-
params=[ss58],
|
518
|
-
block_hash=block_hash_,
|
519
|
-
)
|
520
|
-
return decode_account_id(_result[0])
|
521
|
-
|
522
|
-
async def get_stake_for_coldkey_and_hotkey(
|
523
|
-
hotkey_ss58: str, coldkey_ss58: str, block_hash_: str
|
524
|
-
):
|
525
|
-
"""Returns the stake under a coldkey - hotkey pairing."""
|
526
|
-
_result = await subtensor.substrate.query(
|
527
|
-
module="SubtensorModule",
|
528
|
-
storage_function="Stake",
|
529
|
-
params=[hotkey_ss58, coldkey_ss58],
|
530
|
-
block_hash=block_hash_,
|
531
|
-
)
|
532
|
-
return Balance.from_rao(_result or 0)
|
533
|
-
|
534
|
-
delegate_string = "delegate" if delegate else "undelegate"
|
535
|
-
|
536
|
-
# Decrypt key
|
537
|
-
if not unlock_key(wallet).success:
|
538
|
-
return False
|
539
|
-
|
540
|
-
print_verbose("Checking if hotkey is a delegate")
|
541
|
-
if not await subtensor.is_hotkey_delegate(delegate_ss58):
|
542
|
-
err_console.print(f"Hotkey: {delegate_ss58} is not a delegate.")
|
543
|
-
return False
|
544
|
-
|
545
|
-
# Get state.
|
546
|
-
with console.status(
|
547
|
-
f":satellite: Syncing with [bold white]{subtensor}[/bold white] ...",
|
548
|
-
spinner="aesthetic",
|
549
|
-
) as status:
|
550
|
-
print_verbose("Fetching balance, stake, and ownership", status)
|
551
|
-
initial_block_hash = await subtensor.substrate.get_chain_head()
|
552
|
-
(
|
553
|
-
my_prev_coldkey_balance_,
|
554
|
-
delegate_owner,
|
555
|
-
my_prev_delegated_stake,
|
556
|
-
) = await asyncio.gather(
|
557
|
-
subtensor.get_balance(
|
558
|
-
wallet.coldkey.ss58_address, block_hash=initial_block_hash
|
559
|
-
),
|
560
|
-
get_hotkey_owner(delegate_ss58, block_hash_=initial_block_hash),
|
561
|
-
get_stake_for_coldkey_and_hotkey(
|
562
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
563
|
-
hotkey_ss58=delegate_ss58,
|
564
|
-
block_hash_=initial_block_hash,
|
565
|
-
),
|
566
|
-
)
|
567
|
-
|
568
|
-
my_prev_coldkey_balance = my_prev_coldkey_balance_[wallet.coldkey.ss58_address]
|
569
|
-
|
570
|
-
# Convert to bittensor.Balance
|
571
|
-
if amount is None:
|
572
|
-
# Stake it all.
|
573
|
-
if delegate_string == "delegate":
|
574
|
-
staking_balance = Balance.from_tao(my_prev_coldkey_balance.tao)
|
575
|
-
else:
|
576
|
-
# Unstake all
|
577
|
-
staking_balance = Balance.from_tao(my_prev_delegated_stake.tao)
|
578
|
-
else:
|
579
|
-
staking_balance = Balance.from_tao(amount)
|
580
|
-
|
581
|
-
# Check enough balance to stake.
|
582
|
-
if delegate_string == "delegate" and staking_balance > my_prev_coldkey_balance:
|
583
|
-
err_console.print(
|
584
|
-
":cross_mark: [red]Not enough balance to stake[/red]:\n"
|
585
|
-
f" [bold blue]current balance[/bold blue]:{my_prev_coldkey_balance}\n"
|
586
|
-
f" [bold red]amount staking[/bold red]: {staking_balance}\n"
|
587
|
-
f" [bold white]coldkey: {wallet.name}[/bold white]"
|
588
|
-
)
|
589
|
-
return False
|
590
|
-
|
591
|
-
if delegate_string == "undelegate" and (
|
592
|
-
my_prev_delegated_stake is None or staking_balance > my_prev_delegated_stake
|
593
|
-
):
|
594
|
-
err_console.print(
|
595
|
-
"\n:cross_mark: [red]Not enough balance to unstake[/red]:\n"
|
596
|
-
f" [bold blue]current stake[/bold blue]: {my_prev_delegated_stake}\n"
|
597
|
-
f" [bold red]amount unstaking[/bold red]: {staking_balance}\n"
|
598
|
-
f" [bold white]coldkey: {wallet.name}[bold white]\n\n"
|
599
|
-
)
|
600
|
-
return False
|
601
|
-
|
602
|
-
if delegate:
|
603
|
-
# Grab the existential deposit.
|
604
|
-
existential_deposit = await subtensor.get_existential_deposit()
|
605
|
-
|
606
|
-
# Remove existential balance to keep key alive.
|
607
|
-
if staking_balance > my_prev_coldkey_balance - existential_deposit:
|
608
|
-
staking_balance = my_prev_coldkey_balance - existential_deposit
|
609
|
-
else:
|
610
|
-
staking_balance = staking_balance
|
611
|
-
|
612
|
-
# Ask before moving on.
|
613
|
-
if prompt:
|
614
|
-
if not Confirm.ask(
|
615
|
-
f"\n[bold blue]Current stake[/bold blue]: [blue]{my_prev_delegated_stake}[/blue]\n"
|
616
|
-
f"[bold white]Do you want to {delegate_string}:[/bold white]\n"
|
617
|
-
f" [bold red]amount[/bold red]: [red]{staking_balance}\n[/red]"
|
618
|
-
f" [bold yellow]{'to' if delegate_string == 'delegate' else 'from'} hotkey[/bold yellow]: [yellow]{delegate_ss58}\n[/yellow]"
|
619
|
-
f" [bold green]hotkey owner[/bold green]: [green]{delegate_owner}[/green]"
|
620
|
-
):
|
621
|
-
return False
|
622
|
-
|
623
|
-
with console.status(
|
624
|
-
f":satellite: Staking to: [bold white]{subtensor}[/bold white] ...",
|
625
|
-
spinner="aesthetic",
|
626
|
-
) as status:
|
627
|
-
print_verbose("Transmitting delegate operation call")
|
628
|
-
staking_response, err_msg = await _do_delegation(staking_balance)
|
629
|
-
|
630
|
-
if staking_response is True: # If we successfully staked.
|
631
|
-
# We only wait here if we expect finalization.
|
632
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
633
|
-
return True
|
634
|
-
|
635
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]\n")
|
636
|
-
with console.status(
|
637
|
-
f":satellite: Checking Balance on: [white]{subtensor}[/white] ...",
|
638
|
-
spinner="aesthetic",
|
639
|
-
) as status:
|
640
|
-
print_verbose("Fetching balance and stakes", status)
|
641
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
642
|
-
new_balance, new_delegate_stake = await asyncio.gather(
|
643
|
-
subtensor.get_balance(
|
644
|
-
wallet.coldkey.ss58_address, block_hash=block_hash
|
645
|
-
),
|
646
|
-
get_stake_for_coldkey_and_hotkey(
|
647
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
648
|
-
hotkey_ss58=delegate_ss58,
|
649
|
-
block_hash_=block_hash,
|
650
|
-
),
|
651
|
-
)
|
652
|
-
|
653
|
-
console.print(
|
654
|
-
"Balance:\n"
|
655
|
-
f" [blue]{my_prev_coldkey_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]\n"
|
656
|
-
"Stake:\n"
|
657
|
-
f" [blue]{my_prev_delegated_stake}[/blue] :arrow_right: [green]{new_delegate_stake}[/green]"
|
658
|
-
)
|
659
|
-
return True
|
660
|
-
else:
|
661
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
662
|
-
return False
|
663
|
-
|
664
|
-
|
665
|
-
async def nominate_extrinsic(
|
666
|
-
subtensor: SubtensorInterface,
|
667
|
-
wallet: Wallet,
|
668
|
-
wait_for_finalization: bool = False,
|
669
|
-
wait_for_inclusion: bool = True,
|
670
|
-
) -> bool:
|
671
|
-
"""Becomes a delegate for the hotkey.
|
672
|
-
|
673
|
-
:param wallet: The unlocked wallet to become a delegate for.
|
674
|
-
:param subtensor: The SubtensorInterface to use for the transaction
|
675
|
-
:param wait_for_finalization: Wait for finalization or not
|
676
|
-
:param wait_for_inclusion: Wait for inclusion or not
|
677
|
-
|
678
|
-
:return: success
|
679
|
-
"""
|
680
|
-
with console.status(
|
681
|
-
":satellite: Sending nominate call on [white]{}[/white] ...".format(
|
682
|
-
subtensor.network
|
683
|
-
)
|
684
|
-
):
|
685
|
-
call = await subtensor.substrate.compose_call(
|
686
|
-
call_module="SubtensorModule",
|
687
|
-
call_function="become_delegate",
|
688
|
-
call_params={"hotkey": wallet.hotkey.ss58_address},
|
689
|
-
)
|
690
|
-
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
691
|
-
call,
|
692
|
-
wallet,
|
693
|
-
wait_for_inclusion=wait_for_inclusion,
|
694
|
-
wait_for_finalization=wait_for_finalization,
|
695
|
-
)
|
696
|
-
|
697
|
-
if success is True:
|
698
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
699
|
-
|
700
|
-
else:
|
701
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: error:{err_msg}")
|
702
|
-
return success
|
703
|
-
|
704
|
-
|
705
|
-
# Commands
|
706
|
-
|
707
|
-
|
708
|
-
async def root_list(subtensor: SubtensorInterface):
|
709
|
-
"""List the root network"""
|
710
|
-
|
711
|
-
async def _get_list() -> tuple:
|
712
|
-
senate_query = await subtensor.substrate.query(
|
713
|
-
module="SenateMembers",
|
714
|
-
storage_function="Members",
|
715
|
-
params=None,
|
716
|
-
)
|
717
|
-
sm = [decode_account_id(i[x][0]) for i in senate_query for x in range(len(i))]
|
718
|
-
|
719
|
-
rn: list[NeuronInfoLite] = await subtensor.neurons_lite(netuid=0)
|
720
|
-
if not rn:
|
721
|
-
return [], [], {}, {}
|
722
|
-
|
723
|
-
di: dict[str, DelegatesDetails] = await subtensor.get_delegate_identities()
|
724
|
-
ts: dict[str, ScaleType] = await subtensor.substrate.query_multiple(
|
725
|
-
[n.hotkey for n in rn],
|
726
|
-
module="SubtensorModule",
|
727
|
-
storage_function="TotalHotkeyStake",
|
728
|
-
reuse_block_hash=True,
|
729
|
-
)
|
730
|
-
return sm, rn, di, ts
|
731
|
-
|
732
|
-
with console.status(
|
733
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
734
|
-
spinner="aesthetic",
|
735
|
-
):
|
736
|
-
senate_members, root_neurons, delegate_info, total_stakes = await _get_list()
|
737
|
-
total_tao = sum(
|
738
|
-
float(Balance.from_rao(total_stakes[neuron.hotkey]))
|
739
|
-
for neuron in root_neurons
|
740
|
-
)
|
741
|
-
|
742
|
-
table = Table(
|
743
|
-
Column(
|
744
|
-
"[bold white]UID",
|
745
|
-
style="dark_orange",
|
746
|
-
no_wrap=True,
|
747
|
-
footer=f"[bold]{len(root_neurons)}[/bold]",
|
748
|
-
),
|
749
|
-
Column(
|
750
|
-
"[bold white]NAME",
|
751
|
-
style="bright_cyan",
|
752
|
-
no_wrap=True,
|
753
|
-
),
|
754
|
-
Column(
|
755
|
-
"[bold white]ADDRESS",
|
756
|
-
style="bright_magenta",
|
757
|
-
no_wrap=True,
|
758
|
-
),
|
759
|
-
Column(
|
760
|
-
"[bold white]STAKE(\u03c4)",
|
761
|
-
justify="right",
|
762
|
-
style="light_goldenrod2",
|
763
|
-
no_wrap=True,
|
764
|
-
footer=f"{total_tao:.2f} (\u03c4) ",
|
765
|
-
),
|
766
|
-
Column(
|
767
|
-
"[bold white]SENATOR",
|
768
|
-
style="dark_sea_green",
|
769
|
-
no_wrap=True,
|
770
|
-
),
|
771
|
-
title=f"[underline dark_orange]Root Network[/underline dark_orange]\n[dark_orange]Network {subtensor.network}",
|
772
|
-
show_footer=True,
|
773
|
-
show_edge=False,
|
774
|
-
expand=False,
|
775
|
-
border_style="bright_black",
|
776
|
-
leading=True,
|
777
|
-
)
|
778
|
-
|
779
|
-
if not root_neurons:
|
780
|
-
err_console.print(
|
781
|
-
f"[red]Error: No neurons detected on the network:[/red] [white]{subtensor}"
|
782
|
-
)
|
783
|
-
raise typer.Exit()
|
784
|
-
|
785
|
-
sorted_root_neurons = sorted(
|
786
|
-
root_neurons,
|
787
|
-
key=lambda neuron: float(Balance.from_rao(total_stakes[neuron.hotkey])),
|
788
|
-
reverse=True,
|
789
|
-
)
|
790
|
-
|
791
|
-
for neuron_data in sorted_root_neurons:
|
792
|
-
table.add_row(
|
793
|
-
str(neuron_data.uid),
|
794
|
-
(
|
795
|
-
delegate_info[neuron_data.hotkey].display
|
796
|
-
if neuron_data.hotkey in delegate_info
|
797
|
-
else "~"
|
798
|
-
),
|
799
|
-
neuron_data.hotkey,
|
800
|
-
"{:.5f}".format(float(Balance.from_rao(total_stakes[neuron_data.hotkey]))),
|
801
|
-
"Yes" if neuron_data.hotkey in senate_members else "No",
|
802
|
-
)
|
803
|
-
|
804
|
-
return console.print(table)
|
805
|
-
|
806
|
-
|
807
|
-
async def set_weights(
|
808
|
-
wallet: Wallet,
|
809
|
-
subtensor: SubtensorInterface,
|
810
|
-
netuids: list[int],
|
811
|
-
weights: list[float],
|
812
|
-
prompt: bool,
|
813
|
-
):
|
814
|
-
"""Set weights for root network."""
|
815
|
-
netuids_ = np.array(netuids, dtype=np.int64)
|
816
|
-
weights_ = np.array(weights, dtype=np.float32)
|
817
|
-
console.print(f"Setting weights in [dark_orange]network: {subtensor.network}")
|
818
|
-
|
819
|
-
# Run the set weights operation.
|
820
|
-
|
821
|
-
await set_root_weights_extrinsic(
|
822
|
-
subtensor=subtensor,
|
823
|
-
wallet=wallet,
|
824
|
-
netuids=netuids_,
|
825
|
-
weights=weights_,
|
826
|
-
version_key=0,
|
827
|
-
prompt=prompt,
|
828
|
-
wait_for_finalization=True,
|
829
|
-
wait_for_inclusion=True,
|
830
|
-
)
|
831
|
-
|
832
|
-
|
833
|
-
async def get_weights(
|
834
|
-
subtensor: SubtensorInterface,
|
835
|
-
limit_min_col: Optional[int],
|
836
|
-
limit_max_col: Optional[int],
|
837
|
-
reuse_last: bool,
|
838
|
-
html_output: bool,
|
839
|
-
no_cache: bool,
|
840
|
-
):
|
841
|
-
"""Get weights for root network."""
|
842
|
-
if not reuse_last:
|
843
|
-
with console.status(
|
844
|
-
":satellite: Fetching weights from chain...", spinner="aesthetic"
|
845
|
-
):
|
846
|
-
weights = await subtensor.weights(0)
|
847
|
-
|
848
|
-
uid_to_weights: dict[int, dict] = {}
|
849
|
-
netuids = set()
|
850
|
-
for matrix in weights:
|
851
|
-
[uid, weights_data] = matrix
|
852
|
-
|
853
|
-
if not len(weights_data):
|
854
|
-
uid_to_weights[uid] = {}
|
855
|
-
normalized_weights = []
|
856
|
-
else:
|
857
|
-
normalized_weights = np.array(weights_data)[:, 1] / max(
|
858
|
-
np.sum(weights_data, axis=0)[1], 1
|
859
|
-
)
|
860
|
-
|
861
|
-
for weight_data, normalized_weight in zip(weights_data, normalized_weights):
|
862
|
-
[netuid, _] = weight_data
|
863
|
-
netuids.add(netuid)
|
864
|
-
if uid not in uid_to_weights:
|
865
|
-
uid_to_weights[uid] = {}
|
866
|
-
|
867
|
-
uid_to_weights[uid][netuid] = normalized_weight
|
868
|
-
rows: list[list[str]] = []
|
869
|
-
sorted_netuids: list = list(netuids)
|
870
|
-
sorted_netuids.sort()
|
871
|
-
for uid in uid_to_weights:
|
872
|
-
row = [str(uid)]
|
873
|
-
|
874
|
-
uid_weights = uid_to_weights[uid]
|
875
|
-
for netuid in sorted_netuids:
|
876
|
-
if netuid in uid_weights:
|
877
|
-
row.append("{:0.2f}%".format(uid_weights[netuid] * 100))
|
878
|
-
else:
|
879
|
-
row.append("~")
|
880
|
-
rows.append(row)
|
881
|
-
|
882
|
-
if not no_cache:
|
883
|
-
db_cols = [("UID", "INTEGER")]
|
884
|
-
for netuid in sorted_netuids:
|
885
|
-
db_cols.append((f"_{netuid}", "TEXT"))
|
886
|
-
create_table("rootgetweights", db_cols, rows)
|
887
|
-
update_metadata_table(
|
888
|
-
"rootgetweights",
|
889
|
-
{"rows": json.dumps(rows), "netuids": json.dumps(sorted_netuids)},
|
890
|
-
)
|
891
|
-
else:
|
892
|
-
metadata = get_metadata_table("rootgetweights")
|
893
|
-
rows = json.loads(metadata["rows"])
|
894
|
-
sorted_netuids = json.loads(metadata["netuids"])
|
895
|
-
|
896
|
-
_min_lim = limit_min_col if limit_min_col is not None else 0
|
897
|
-
_max_lim = limit_max_col + 1 if limit_max_col is not None else len(sorted_netuids)
|
898
|
-
_max_lim = min(_max_lim, len(sorted_netuids))
|
899
|
-
|
900
|
-
if _min_lim is not None and _min_lim > len(sorted_netuids):
|
901
|
-
err_console.print("Minimum limit greater than number of netuids")
|
902
|
-
return
|
903
|
-
|
904
|
-
if not html_output:
|
905
|
-
table = Table(
|
906
|
-
show_footer=True,
|
907
|
-
box=None,
|
908
|
-
pad_edge=False,
|
909
|
-
width=None,
|
910
|
-
title="[white]Root Network Weights",
|
911
|
-
)
|
912
|
-
table.add_column(
|
913
|
-
"[white]UID",
|
914
|
-
header_style="overline white",
|
915
|
-
footer_style="overline white",
|
916
|
-
style="rgb(50,163,219)",
|
917
|
-
no_wrap=True,
|
918
|
-
)
|
919
|
-
for netuid in sorted_netuids[_min_lim:_max_lim]:
|
920
|
-
table.add_column(
|
921
|
-
f"[white]{netuid}",
|
922
|
-
header_style="overline white",
|
923
|
-
footer_style="overline white",
|
924
|
-
justify="right",
|
925
|
-
style="green",
|
926
|
-
no_wrap=True,
|
927
|
-
)
|
928
|
-
|
929
|
-
if not rows:
|
930
|
-
err_console.print("No weights exist on the root network.")
|
931
|
-
return
|
932
|
-
|
933
|
-
# Adding rows
|
934
|
-
for row in rows:
|
935
|
-
new_row = [row[0]] + row[_min_lim + 1 : _max_lim + 1]
|
936
|
-
table.add_row(*new_row)
|
937
|
-
|
938
|
-
return console.print(table)
|
939
|
-
|
940
|
-
else:
|
941
|
-
html_cols = [{"title": "UID", "field": "UID"}]
|
942
|
-
for netuid in sorted_netuids[_min_lim:_max_lim]:
|
943
|
-
html_cols.append({"title": str(netuid), "field": f"_{netuid}"})
|
944
|
-
render_table(
|
945
|
-
"rootgetweights",
|
946
|
-
"Root Network Weights",
|
947
|
-
html_cols,
|
948
|
-
)
|
949
|
-
|
950
|
-
|
951
|
-
async def _get_my_weights(
|
952
|
-
subtensor: SubtensorInterface, ss58_address: str, my_uid: str
|
953
|
-
) -> NDArray[np.float32]:
|
954
|
-
"""Retrieves the weight array for a given hotkey SS58 address."""
|
955
|
-
|
956
|
-
my_weights_, total_subnets_ = await asyncio.gather(
|
957
|
-
subtensor.substrate.query(
|
958
|
-
"SubtensorModule", "Weights", [0, my_uid], reuse_block_hash=True
|
959
|
-
),
|
960
|
-
subtensor.substrate.query(
|
961
|
-
"SubtensorModule", "TotalNetworks", reuse_block_hash=True
|
962
|
-
),
|
963
|
-
)
|
964
|
-
# If setting weights for the first time, pass 0 root weights
|
965
|
-
my_weights: list[tuple[int, int]] = (
|
966
|
-
my_weights_ if my_weights_ is not None else [(0, 0)]
|
967
|
-
)
|
968
|
-
total_subnets: int = total_subnets_
|
969
|
-
|
970
|
-
print_verbose("Fetching current weights")
|
971
|
-
for _, w in enumerate(my_weights):
|
972
|
-
if w:
|
973
|
-
print_verbose(f"{w}")
|
974
|
-
|
975
|
-
uids, values = zip(*my_weights)
|
976
|
-
weight_array = convert_weight_uids_and_vals_to_tensor(total_subnets, uids, values)
|
977
|
-
return weight_array
|
978
|
-
|
979
|
-
|
980
|
-
async def set_boost(
|
981
|
-
wallet: Wallet,
|
982
|
-
subtensor: SubtensorInterface,
|
983
|
-
netuid: int,
|
984
|
-
amount: float,
|
985
|
-
prompt: bool,
|
986
|
-
):
|
987
|
-
"""Boosts weight of a given netuid for root network."""
|
988
|
-
console.print(f"Boosting weights in [dark_orange]network: {subtensor.network}")
|
989
|
-
print_verbose(f"Fetching uid of hotkey on root: {wallet.hotkey_str}")
|
990
|
-
my_uid = await subtensor.substrate.query(
|
991
|
-
"SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address]
|
992
|
-
)
|
993
|
-
|
994
|
-
if my_uid is None:
|
995
|
-
err_console.print("Your hotkey is not registered to the root network")
|
996
|
-
return False
|
997
|
-
|
998
|
-
print_verbose("Fetching current weights")
|
999
|
-
my_weights = await _get_my_weights(subtensor, wallet.hotkey.ss58_address, my_uid)
|
1000
|
-
prev_weights = my_weights.copy()
|
1001
|
-
my_weights[netuid] += amount
|
1002
|
-
all_netuids = np.arange(len(my_weights))
|
1003
|
-
|
1004
|
-
console.print(
|
1005
|
-
f"Boosting weight for netuid {netuid}\n\tfrom {prev_weights[netuid]} to {my_weights[netuid]}\n"
|
1006
|
-
)
|
1007
|
-
console.print(
|
1008
|
-
f"Previous weights -> Raw weights: \n\t{prev_weights} -> \n\t{my_weights}"
|
1009
|
-
)
|
1010
|
-
|
1011
|
-
print_verbose(f"All netuids: {all_netuids}")
|
1012
|
-
await set_root_weights_extrinsic(
|
1013
|
-
subtensor=subtensor,
|
1014
|
-
wallet=wallet,
|
1015
|
-
netuids=all_netuids,
|
1016
|
-
weights=my_weights,
|
1017
|
-
version_key=0,
|
1018
|
-
wait_for_inclusion=True,
|
1019
|
-
wait_for_finalization=True,
|
1020
|
-
prompt=prompt,
|
1021
|
-
)
|
1022
|
-
|
1023
|
-
|
1024
|
-
async def set_slash(
|
1025
|
-
wallet: Wallet,
|
1026
|
-
subtensor: SubtensorInterface,
|
1027
|
-
netuid: int,
|
1028
|
-
amount: float,
|
1029
|
-
prompt: bool,
|
1030
|
-
):
|
1031
|
-
"""Slashes weight"""
|
1032
|
-
console.print(f"Slashing weights in [dark_orange]network: {subtensor.network}")
|
1033
|
-
print_verbose(f"Fetching uid of hotkey on root: {wallet.hotkey_str}")
|
1034
|
-
my_uid = await subtensor.substrate.query(
|
1035
|
-
"SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address]
|
1036
|
-
)
|
1037
|
-
if my_uid is None:
|
1038
|
-
err_console.print("Your hotkey is not registered to the root network")
|
1039
|
-
return False
|
1040
|
-
|
1041
|
-
print_verbose("Fetching current weights")
|
1042
|
-
my_weights = await _get_my_weights(subtensor, wallet.hotkey.ss58_address, my_uid)
|
1043
|
-
prev_weights = my_weights.copy()
|
1044
|
-
my_weights[netuid] -= amount
|
1045
|
-
my_weights[my_weights < 0] = 0 # Ensure weights don't go negative
|
1046
|
-
all_netuids = np.arange(len(my_weights))
|
1047
|
-
|
1048
|
-
console.print(
|
1049
|
-
f"Slashing weight for netuid {netuid}\n\tfrom {prev_weights[netuid]} to {my_weights[netuid]}\n"
|
1050
|
-
)
|
1051
|
-
console.print(
|
1052
|
-
f"Previous weights -> Raw weights: \n\t{prev_weights} -> \n\t{my_weights}"
|
1053
|
-
)
|
1054
|
-
|
1055
|
-
await set_root_weights_extrinsic(
|
1056
|
-
subtensor=subtensor,
|
1057
|
-
wallet=wallet,
|
1058
|
-
netuids=all_netuids,
|
1059
|
-
weights=my_weights,
|
1060
|
-
version_key=0,
|
1061
|
-
wait_for_inclusion=True,
|
1062
|
-
wait_for_finalization=True,
|
1063
|
-
prompt=prompt,
|
1064
|
-
)
|
1065
|
-
|
1066
|
-
|
1067
|
-
async def senate_vote(
|
1068
|
-
wallet: Wallet,
|
1069
|
-
subtensor: SubtensorInterface,
|
1070
|
-
proposal_hash: str,
|
1071
|
-
vote: bool,
|
1072
|
-
prompt: bool,
|
1073
|
-
) -> bool:
|
1074
|
-
"""Vote in Bittensor's governance protocol proposals"""
|
1075
|
-
|
1076
|
-
if not proposal_hash:
|
1077
|
-
err_console.print(
|
1078
|
-
"Aborting: Proposal hash not specified. View all proposals with the `proposals` command."
|
1079
|
-
)
|
1080
|
-
return False
|
1081
|
-
elif not _validate_proposal_hash(proposal_hash):
|
1082
|
-
err_console.print(
|
1083
|
-
"Aborting. Proposal hash is invalid. Proposal hashes should start with '0x' and be 32 bytes long"
|
1084
|
-
)
|
1085
|
-
return False
|
1086
|
-
|
1087
|
-
print_verbose(f"Fetching senate status of {wallet.hotkey_str}")
|
1088
|
-
if not await _is_senate_member(subtensor, hotkey_ss58=wallet.hotkey.ss58_address):
|
1089
|
-
err_console.print(
|
1090
|
-
f"Aborting: Hotkey {wallet.hotkey.ss58_address} isn't a senate member."
|
1091
|
-
)
|
1092
|
-
return False
|
1093
|
-
|
1094
|
-
# Unlock the wallet.
|
1095
|
-
if not unlock_key(wallet).success and unlock_key(wallet, "hot").success:
|
1096
|
-
return False
|
1097
|
-
|
1098
|
-
console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}")
|
1099
|
-
vote_data = await subtensor.get_vote_data(proposal_hash, reuse_block=True)
|
1100
|
-
if not vote_data:
|
1101
|
-
err_console.print(":cross_mark: [red]Failed[/red]: Proposal not found.")
|
1102
|
-
return False
|
1103
|
-
|
1104
|
-
success = await vote_senate_extrinsic(
|
1105
|
-
subtensor=subtensor,
|
1106
|
-
wallet=wallet,
|
1107
|
-
proposal_hash=proposal_hash,
|
1108
|
-
proposal_idx=vote_data.index,
|
1109
|
-
vote=vote,
|
1110
|
-
wait_for_inclusion=True,
|
1111
|
-
wait_for_finalization=False,
|
1112
|
-
prompt=prompt,
|
1113
|
-
)
|
1114
|
-
|
1115
|
-
return success
|
1116
|
-
|
1117
|
-
|
1118
|
-
async def get_senate(subtensor: SubtensorInterface):
|
1119
|
-
"""View Bittensor's governance protocol proposals"""
|
1120
|
-
with console.status(
|
1121
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
1122
|
-
spinner="aesthetic",
|
1123
|
-
) as status:
|
1124
|
-
print_verbose("Fetching senate members", status)
|
1125
|
-
senate_members = await _get_senate_members(subtensor)
|
1126
|
-
|
1127
|
-
print_verbose("Fetching member details from Github")
|
1128
|
-
delegate_info: dict[
|
1129
|
-
str, DelegatesDetails
|
1130
|
-
] = await subtensor.get_delegate_identities()
|
1131
|
-
|
1132
|
-
table = Table(
|
1133
|
-
Column(
|
1134
|
-
"[bold white]NAME",
|
1135
|
-
style="bright_cyan",
|
1136
|
-
no_wrap=True,
|
1137
|
-
),
|
1138
|
-
Column(
|
1139
|
-
"[bold white]ADDRESS",
|
1140
|
-
style="bright_magenta",
|
1141
|
-
no_wrap=True,
|
1142
|
-
),
|
1143
|
-
title=f"[underline dark_orange]Senate[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n",
|
1144
|
-
show_footer=True,
|
1145
|
-
show_edge=False,
|
1146
|
-
expand=False,
|
1147
|
-
border_style="bright_black",
|
1148
|
-
leading=True,
|
1149
|
-
)
|
1150
|
-
|
1151
|
-
for ss58_address in senate_members:
|
1152
|
-
table.add_row(
|
1153
|
-
(
|
1154
|
-
delegate_info[ss58_address].display
|
1155
|
-
if ss58_address in delegate_info
|
1156
|
-
else "~"
|
1157
|
-
),
|
1158
|
-
ss58_address,
|
1159
|
-
)
|
1160
|
-
|
1161
|
-
return console.print(table)
|
1162
|
-
|
1163
|
-
|
1164
|
-
async def register(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool):
|
1165
|
-
"""Register neuron by recycling some TAO."""
|
1166
|
-
|
1167
|
-
console.print(
|
1168
|
-
f"Registering on [dark_orange]netuid 0[/dark_orange] on network: [dark_orange]{subtensor.network}"
|
1169
|
-
)
|
1170
|
-
|
1171
|
-
# Check current recycle amount
|
1172
|
-
print_verbose("Fetching recycle amount & balance")
|
1173
|
-
recycle_call, balance_ = await asyncio.gather(
|
1174
|
-
subtensor.get_hyperparameter(param_name="Burn", netuid=0, reuse_block=True),
|
1175
|
-
subtensor.get_balance(wallet.coldkeypub.ss58_address, reuse_block=True),
|
1176
|
-
)
|
1177
|
-
current_recycle = Balance.from_rao(int(recycle_call))
|
1178
|
-
try:
|
1179
|
-
balance: Balance = balance_[wallet.coldkeypub.ss58_address]
|
1180
|
-
except TypeError as e:
|
1181
|
-
err_console.print(f"Unable to retrieve current recycle. {e}")
|
1182
|
-
return False
|
1183
|
-
except KeyError:
|
1184
|
-
err_console.print("Unable to retrieve current balance.")
|
1185
|
-
return False
|
1186
|
-
|
1187
|
-
# Check balance is sufficient
|
1188
|
-
if balance < current_recycle:
|
1189
|
-
err_console.print(
|
1190
|
-
f"[red]Insufficient balance {balance} to register neuron. "
|
1191
|
-
f"Current recycle is {current_recycle} TAO[/red]"
|
1192
|
-
)
|
1193
|
-
return False
|
1194
|
-
|
1195
|
-
if prompt:
|
1196
|
-
if not Confirm.ask(
|
1197
|
-
f"Your balance is: [bold green]{balance}[/bold green]\n"
|
1198
|
-
f"The cost to register by recycle is [bold red]{current_recycle}[/bold red]\n"
|
1199
|
-
f"Do you want to continue?",
|
1200
|
-
default=False,
|
1201
|
-
):
|
1202
|
-
return False
|
1203
|
-
|
1204
|
-
await root_register_extrinsic(
|
1205
|
-
subtensor,
|
1206
|
-
wallet,
|
1207
|
-
wait_for_inclusion=True,
|
1208
|
-
wait_for_finalization=True,
|
1209
|
-
prompt=prompt,
|
1210
|
-
)
|
1211
|
-
|
1212
|
-
|
1213
|
-
async def proposals(subtensor: SubtensorInterface):
|
1214
|
-
console.print(
|
1215
|
-
":satellite: Syncing with chain: [white]{}[/white] ...".format(
|
1216
|
-
subtensor.network
|
1217
|
-
)
|
1218
|
-
)
|
1219
|
-
print_verbose("Fetching senate members & proposals")
|
1220
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
1221
|
-
senate_members, all_proposals = await asyncio.gather(
|
1222
|
-
_get_senate_members(subtensor, block_hash),
|
1223
|
-
_get_proposals(subtensor, block_hash),
|
1224
|
-
)
|
1225
|
-
|
1226
|
-
print_verbose("Fetching member information from Chain")
|
1227
|
-
registered_delegate_info: dict[
|
1228
|
-
str, DelegatesDetails
|
1229
|
-
] = await subtensor.get_delegate_identities()
|
1230
|
-
|
1231
|
-
table = Table(
|
1232
|
-
Column(
|
1233
|
-
"[white]HASH",
|
1234
|
-
style="light_goldenrod2",
|
1235
|
-
no_wrap=True,
|
1236
|
-
),
|
1237
|
-
Column("[white]THRESHOLD", style="rgb(42,161,152)"),
|
1238
|
-
Column("[white]AYES", style="green"),
|
1239
|
-
Column("[white]NAYS", style="red"),
|
1240
|
-
Column(
|
1241
|
-
"[white]VOTES",
|
1242
|
-
style="rgb(50,163,219)",
|
1243
|
-
),
|
1244
|
-
Column("[white]END", style="bright_cyan"),
|
1245
|
-
Column("[white]CALLDATA", style="dark_sea_green"),
|
1246
|
-
title=f"\n[dark_orange]Proposals\t\t\nActive Proposals: {len(all_proposals)}\t\tSenate Size: {len(senate_members)}\nNetwork: {subtensor.network}",
|
1247
|
-
show_footer=True,
|
1248
|
-
box=box.SIMPLE_HEAVY,
|
1249
|
-
pad_edge=False,
|
1250
|
-
width=None,
|
1251
|
-
border_style="bright_black",
|
1252
|
-
)
|
1253
|
-
for hash_, (call_data, vote_data) in all_proposals.items():
|
1254
|
-
table.add_row(
|
1255
|
-
hash_,
|
1256
|
-
str(vote_data.threshold),
|
1257
|
-
str(len(vote_data.ayes)),
|
1258
|
-
str(len(vote_data.nays)),
|
1259
|
-
display_votes(vote_data, registered_delegate_info),
|
1260
|
-
str(vote_data.end),
|
1261
|
-
format_call_data(call_data),
|
1262
|
-
)
|
1263
|
-
return console.print(table)
|
1264
|
-
|
1265
|
-
|
1266
|
-
async def set_take(wallet: Wallet, subtensor: SubtensorInterface, take: float) -> bool:
|
1267
|
-
"""Set delegate take."""
|
1268
|
-
|
1269
|
-
async def _do_set_take() -> bool:
|
1270
|
-
"""
|
1271
|
-
Just more easily allows an early return and to close the substrate interface after the logic
|
1272
|
-
"""
|
1273
|
-
print_verbose("Checking if hotkey is a delegate")
|
1274
|
-
# Check if the hotkey is not a delegate.
|
1275
|
-
if not await subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address):
|
1276
|
-
err_console.print(
|
1277
|
-
f"Aborting: Hotkey {wallet.hotkey.ss58_address} is NOT a delegate."
|
1278
|
-
)
|
1279
|
-
return False
|
1280
|
-
|
1281
|
-
if take > 0.18 or take < 0:
|
1282
|
-
err_console.print("ERROR: Take value should not exceed 18% or be below 0%")
|
1283
|
-
return False
|
1284
|
-
|
1285
|
-
result: bool = await set_take_extrinsic(
|
1286
|
-
subtensor=subtensor,
|
1287
|
-
wallet=wallet,
|
1288
|
-
delegate_ss58=wallet.hotkey.ss58_address,
|
1289
|
-
take=take,
|
1290
|
-
)
|
1291
|
-
|
1292
|
-
if not result:
|
1293
|
-
err_console.print("Could not set the take")
|
1294
|
-
return False
|
1295
|
-
else:
|
1296
|
-
# Check if we are a delegate.
|
1297
|
-
is_delegate: bool = await subtensor.is_hotkey_delegate(
|
1298
|
-
wallet.hotkey.ss58_address
|
1299
|
-
)
|
1300
|
-
if not is_delegate:
|
1301
|
-
err_console.print(
|
1302
|
-
"Could not set the take [white]{}[/white]".format(subtensor.network)
|
1303
|
-
)
|
1304
|
-
return False
|
1305
|
-
else:
|
1306
|
-
console.print(
|
1307
|
-
"Successfully set the take on [white]{}[/white]".format(
|
1308
|
-
subtensor.network
|
1309
|
-
)
|
1310
|
-
)
|
1311
|
-
return True
|
1312
|
-
|
1313
|
-
console.print(f"Setting take on [dark_orange]network: {subtensor.network}")
|
1314
|
-
# Unlock the wallet.
|
1315
|
-
if not unlock_key(wallet).success and unlock_key(wallet, "hot").success:
|
1316
|
-
return False
|
1317
|
-
|
1318
|
-
result_ = await _do_set_take()
|
1319
|
-
|
1320
|
-
return result_
|
1321
|
-
|
1322
|
-
|
1323
|
-
async def delegate_stake(
|
1324
|
-
wallet: Wallet,
|
1325
|
-
subtensor: SubtensorInterface,
|
1326
|
-
amount: Optional[float],
|
1327
|
-
delegate_ss58key: str,
|
1328
|
-
prompt: bool,
|
1329
|
-
):
|
1330
|
-
"""Delegates stake to a chain delegate."""
|
1331
|
-
console.print(f"Delegating stake on [dark_orange]network: {subtensor.network}")
|
1332
|
-
await delegate_extrinsic(
|
1333
|
-
subtensor,
|
1334
|
-
wallet,
|
1335
|
-
delegate_ss58key,
|
1336
|
-
amount,
|
1337
|
-
wait_for_inclusion=True,
|
1338
|
-
prompt=prompt,
|
1339
|
-
delegate=True,
|
1340
|
-
)
|
1341
|
-
|
1342
|
-
|
1343
|
-
async def delegate_unstake(
|
1344
|
-
wallet: Wallet,
|
1345
|
-
subtensor: SubtensorInterface,
|
1346
|
-
amount: Optional[float],
|
1347
|
-
delegate_ss58key: str,
|
1348
|
-
prompt: bool,
|
1349
|
-
):
|
1350
|
-
"""Undelegates stake from a chain delegate."""
|
1351
|
-
console.print(f"Undelegating stake on [dark_orange]network: {subtensor.network}")
|
1352
|
-
await delegate_extrinsic(
|
1353
|
-
subtensor,
|
1354
|
-
wallet,
|
1355
|
-
delegate_ss58key,
|
1356
|
-
amount,
|
1357
|
-
wait_for_inclusion=True,
|
1358
|
-
prompt=prompt,
|
1359
|
-
delegate=False,
|
1360
|
-
)
|
1361
|
-
|
1362
|
-
|
1363
|
-
async def my_delegates(
|
1364
|
-
wallet: Wallet, subtensor: SubtensorInterface, all_wallets: bool
|
1365
|
-
):
|
1366
|
-
"""Delegates stake to a chain delegate."""
|
1367
|
-
|
1368
|
-
async def wallet_to_delegates(
|
1369
|
-
w: Wallet, bh: str
|
1370
|
-
) -> tuple[Optional[Wallet], Optional[list[tuple[DelegateInfo, Balance]]]]:
|
1371
|
-
"""Helper function to retrieve the validity of the wallet (if it has a coldkeypub on the device)
|
1372
|
-
and its delegate info."""
|
1373
|
-
if not w.coldkeypub_file.exists_on_device():
|
1374
|
-
return None, None
|
1375
|
-
else:
|
1376
|
-
delegates_ = await subtensor.get_delegated(
|
1377
|
-
w.coldkeypub.ss58_address, block_hash=bh
|
1378
|
-
)
|
1379
|
-
return w, delegates_
|
1380
|
-
|
1381
|
-
wallets = get_coldkey_wallets_for_path(wallet.path) if all_wallets else [wallet]
|
1382
|
-
|
1383
|
-
table = Table(
|
1384
|
-
Column("[white]Wallet", style="bright_cyan"),
|
1385
|
-
Column(
|
1386
|
-
"[white]OWNER",
|
1387
|
-
style="bold bright_cyan",
|
1388
|
-
overflow="fold",
|
1389
|
-
justify="left",
|
1390
|
-
ratio=1,
|
1391
|
-
),
|
1392
|
-
Column(
|
1393
|
-
"[white]SS58",
|
1394
|
-
style="bright_magenta",
|
1395
|
-
justify="left",
|
1396
|
-
overflow="fold",
|
1397
|
-
ratio=3,
|
1398
|
-
),
|
1399
|
-
Column("[white]Delegation", style="dark_orange", no_wrap=True, ratio=1),
|
1400
|
-
Column("[white]\u03c4/24h", style="bold green", ratio=1),
|
1401
|
-
Column(
|
1402
|
-
"[white]NOMS",
|
1403
|
-
justify="center",
|
1404
|
-
style="rgb(42,161,152)",
|
1405
|
-
no_wrap=True,
|
1406
|
-
ratio=1,
|
1407
|
-
),
|
1408
|
-
Column(
|
1409
|
-
"[white]OWNER STAKE(\u03c4)",
|
1410
|
-
justify="right",
|
1411
|
-
style="light_goldenrod2",
|
1412
|
-
no_wrap=True,
|
1413
|
-
ratio=1,
|
1414
|
-
),
|
1415
|
-
Column(
|
1416
|
-
"[white]TOTAL STAKE(\u03c4)",
|
1417
|
-
justify="right",
|
1418
|
-
style="light_goldenrod2",
|
1419
|
-
no_wrap=True,
|
1420
|
-
ratio=1,
|
1421
|
-
),
|
1422
|
-
Column("[white]SUBNETS", justify="right", style="white", ratio=1),
|
1423
|
-
Column("[white]VPERMIT", justify="right"),
|
1424
|
-
Column(
|
1425
|
-
"[white]24h/k\u03c4", style="rgb(42,161,152)", justify="center", ratio=1
|
1426
|
-
),
|
1427
|
-
Column("[white]Desc", style="rgb(50,163,219)", ratio=3),
|
1428
|
-
title=f"[underline dark_orange]My Delegates[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n",
|
1429
|
-
show_footer=True,
|
1430
|
-
show_edge=False,
|
1431
|
-
expand=False,
|
1432
|
-
box=box.SIMPLE_HEAVY,
|
1433
|
-
border_style="bright_black",
|
1434
|
-
leading=True,
|
1435
|
-
)
|
1436
|
-
|
1437
|
-
total_delegated = 0
|
1438
|
-
|
1439
|
-
# TODO: this doesnt work when passed to wallets_with_delegates
|
1440
|
-
# block_hash = await subtensor.substrate.get_chain_head()
|
1441
|
-
|
1442
|
-
registered_delegate_info: dict[str, DelegatesDetails]
|
1443
|
-
wallets_with_delegates: tuple[
|
1444
|
-
tuple[Optional[Wallet], Optional[list[tuple[DelegateInfo, Balance]]]]
|
1445
|
-
]
|
1446
|
-
|
1447
|
-
print_verbose("Fetching delegate information")
|
1448
|
-
wallets_with_delegates, registered_delegate_info = await asyncio.gather(
|
1449
|
-
asyncio.gather(*[wallet_to_delegates(wallet_, None) for wallet_ in wallets]),
|
1450
|
-
subtensor.get_delegate_identities(),
|
1451
|
-
)
|
1452
|
-
if not registered_delegate_info:
|
1453
|
-
console.print(
|
1454
|
-
":warning:[yellow]Could not get delegate info from chain.[/yellow]"
|
1455
|
-
)
|
1456
|
-
|
1457
|
-
print_verbose("Processing delegate information")
|
1458
|
-
for wall, delegates in wallets_with_delegates:
|
1459
|
-
if not wall or not delegates:
|
1460
|
-
continue
|
1461
|
-
|
1462
|
-
my_delegates_ = {} # hotkey, amount
|
1463
|
-
for delegate in delegates:
|
1464
|
-
for coldkey_addr, staked in delegate[0].nominators:
|
1465
|
-
if coldkey_addr == wall.coldkeypub.ss58_address and staked.tao > 0:
|
1466
|
-
my_delegates_[delegate[0].hotkey_ss58] = staked
|
1467
|
-
|
1468
|
-
delegates.sort(key=lambda d: d[0].total_stake, reverse=True)
|
1469
|
-
total_delegated += sum(my_delegates_.values())
|
1470
|
-
|
1471
|
-
for i, delegate in enumerate(delegates):
|
1472
|
-
owner_stake = next(
|
1473
|
-
(
|
1474
|
-
stake
|
1475
|
-
for owner, stake in delegate[0].nominators
|
1476
|
-
if owner == delegate[0].owner_ss58
|
1477
|
-
),
|
1478
|
-
Balance.from_rao(0), # default to 0 if no owner stake.
|
1479
|
-
)
|
1480
|
-
if delegate[0].hotkey_ss58 in registered_delegate_info:
|
1481
|
-
delegate_name = registered_delegate_info[
|
1482
|
-
delegate[0].hotkey_ss58
|
1483
|
-
].display
|
1484
|
-
delegate_url = registered_delegate_info[delegate[0].hotkey_ss58].web
|
1485
|
-
delegate_description = registered_delegate_info[
|
1486
|
-
delegate[0].hotkey_ss58
|
1487
|
-
].additional
|
1488
|
-
else:
|
1489
|
-
delegate_name = "~"
|
1490
|
-
delegate_url = ""
|
1491
|
-
delegate_description = ""
|
1492
|
-
|
1493
|
-
if delegate[0].hotkey_ss58 in my_delegates_:
|
1494
|
-
twenty_four_hour = delegate[0].total_daily_return.tao * (
|
1495
|
-
my_delegates_[delegate[0].hotkey_ss58] / delegate[0].total_stake.tao
|
1496
|
-
)
|
1497
|
-
table.add_row(
|
1498
|
-
wall.name,
|
1499
|
-
Text(delegate_name, style=f"link {delegate_url}"),
|
1500
|
-
f"{delegate[0].hotkey_ss58}",
|
1501
|
-
f"{my_delegates_[delegate[0].hotkey_ss58]!s:13.13}",
|
1502
|
-
f"{twenty_four_hour!s:6.6}",
|
1503
|
-
str(len(delegate[0].nominators)),
|
1504
|
-
f"{owner_stake!s:13.13}",
|
1505
|
-
f"{delegate[0].total_stake!s:13.13}",
|
1506
|
-
group_subnets(delegate[0].registrations),
|
1507
|
-
group_subnets(delegate[0].validator_permits),
|
1508
|
-
f"{delegate[0].total_daily_return.tao * (1000 / (0.001 + delegate[0].total_stake.tao))!s:6.6}",
|
1509
|
-
str(delegate_description),
|
1510
|
-
)
|
1511
|
-
if console.width < 150:
|
1512
|
-
console.print(
|
1513
|
-
"[yellow]Warning: Your terminal width might be too small to view all the information clearly"
|
1514
|
-
)
|
1515
|
-
console.print(table)
|
1516
|
-
console.print(f"Total delegated TAO: {total_delegated}")
|
1517
|
-
|
1518
|
-
|
1519
|
-
async def list_delegates(subtensor: SubtensorInterface):
|
1520
|
-
"""List all delegates on the network."""
|
1521
|
-
|
1522
|
-
with console.status(
|
1523
|
-
":satellite: Loading delegates...", spinner="aesthetic"
|
1524
|
-
) as status:
|
1525
|
-
print_verbose("Fetching delegate details from chain", status)
|
1526
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
1527
|
-
registered_delegate_info, block_number, delegates = await asyncio.gather(
|
1528
|
-
subtensor.get_delegate_identities(block_hash=block_hash),
|
1529
|
-
subtensor.substrate.get_block_number(block_hash),
|
1530
|
-
subtensor.get_delegates(block_hash=block_hash),
|
1531
|
-
)
|
1532
|
-
|
1533
|
-
print_verbose("Fetching previous delegates info from chain", status)
|
1534
|
-
|
1535
|
-
async def get_prev_delegates(fallback_offsets=(1200, 200)):
|
1536
|
-
for offset in fallback_offsets:
|
1537
|
-
try:
|
1538
|
-
prev_block_hash = await subtensor.substrate.get_block_hash(
|
1539
|
-
max(0, block_number - offset)
|
1540
|
-
)
|
1541
|
-
return await subtensor.get_delegates(block_hash=prev_block_hash)
|
1542
|
-
except SubstrateRequestException:
|
1543
|
-
continue
|
1544
|
-
return None
|
1545
|
-
|
1546
|
-
prev_delegates = await get_prev_delegates()
|
1547
|
-
|
1548
|
-
if prev_delegates is None:
|
1549
|
-
err_console.print(
|
1550
|
-
":warning: [yellow]Could not fetch delegates history. [/yellow]"
|
1551
|
-
)
|
1552
|
-
|
1553
|
-
delegates.sort(key=lambda d: d.total_stake, reverse=True)
|
1554
|
-
prev_delegates_dict = {}
|
1555
|
-
if prev_delegates is not None:
|
1556
|
-
for prev_delegate in prev_delegates:
|
1557
|
-
prev_delegates_dict[prev_delegate.hotkey_ss58] = prev_delegate
|
1558
|
-
|
1559
|
-
if not registered_delegate_info:
|
1560
|
-
console.print(
|
1561
|
-
":warning:[yellow]Could not get delegate info from chain.[/yellow]"
|
1562
|
-
)
|
1563
|
-
table = Table(
|
1564
|
-
Column(
|
1565
|
-
"[white]INDEX\n\n",
|
1566
|
-
str(len(delegates)),
|
1567
|
-
style="bold white",
|
1568
|
-
),
|
1569
|
-
Column(
|
1570
|
-
"[white]DELEGATE\n\n",
|
1571
|
-
style="bold bright_cyan",
|
1572
|
-
justify="left",
|
1573
|
-
overflow="fold",
|
1574
|
-
ratio=1,
|
1575
|
-
),
|
1576
|
-
Column(
|
1577
|
-
"[white]SS58\n\n",
|
1578
|
-
style="bright_magenta",
|
1579
|
-
no_wrap=False,
|
1580
|
-
overflow="fold",
|
1581
|
-
ratio=2,
|
1582
|
-
),
|
1583
|
-
Column(
|
1584
|
-
"[white]NOMINATORS\n\n",
|
1585
|
-
justify="center",
|
1586
|
-
style="gold1",
|
1587
|
-
no_wrap=True,
|
1588
|
-
ratio=1,
|
1589
|
-
),
|
1590
|
-
Column(
|
1591
|
-
"[white]OWN STAKE\n(\u03c4)\n",
|
1592
|
-
justify="right",
|
1593
|
-
style="orange1",
|
1594
|
-
no_wrap=True,
|
1595
|
-
ratio=1,
|
1596
|
-
),
|
1597
|
-
Column(
|
1598
|
-
"[white]TOTAL STAKE\n(\u03c4)\n",
|
1599
|
-
justify="right",
|
1600
|
-
style="light_goldenrod2",
|
1601
|
-
no_wrap=True,
|
1602
|
-
ratio=1,
|
1603
|
-
),
|
1604
|
-
Column("[white]CHANGE\n/(4h)\n", style="grey0", justify="center", ratio=1),
|
1605
|
-
Column("[white]TAKE\n\n", style="white", no_wrap=True, ratio=1),
|
1606
|
-
Column(
|
1607
|
-
"[white]NOMINATOR\n/(24h)/k\u03c4\n",
|
1608
|
-
style="dark_olive_green3",
|
1609
|
-
justify="center",
|
1610
|
-
ratio=1,
|
1611
|
-
),
|
1612
|
-
Column(
|
1613
|
-
"[white]DELEGATE\n/(24h)\n",
|
1614
|
-
style="dark_olive_green3",
|
1615
|
-
justify="center",
|
1616
|
-
ratio=1,
|
1617
|
-
),
|
1618
|
-
Column(
|
1619
|
-
"[white]VPERMIT\n\n",
|
1620
|
-
justify="center",
|
1621
|
-
no_wrap=False,
|
1622
|
-
max_width=20,
|
1623
|
-
style="dark_sea_green",
|
1624
|
-
ratio=2,
|
1625
|
-
),
|
1626
|
-
Column("[white]Desc\n\n", style="rgb(50,163,219)", max_width=30, ratio=2),
|
1627
|
-
title=f"[underline dark_orange]Root Delegates[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n",
|
1628
|
-
show_footer=True,
|
1629
|
-
pad_edge=False,
|
1630
|
-
box=None,
|
1631
|
-
)
|
1632
|
-
|
1633
|
-
for i, delegate in enumerate(delegates):
|
1634
|
-
owner_stake = next(
|
1635
|
-
(
|
1636
|
-
stake
|
1637
|
-
for owner, stake in delegate.nominators
|
1638
|
-
if owner == delegate.owner_ss58
|
1639
|
-
),
|
1640
|
-
Balance.from_rao(0), # default to 0 if no owner stake.
|
1641
|
-
)
|
1642
|
-
if delegate.hotkey_ss58 in registered_delegate_info:
|
1643
|
-
delegate_name = registered_delegate_info[delegate.hotkey_ss58].display
|
1644
|
-
delegate_url = registered_delegate_info[delegate.hotkey_ss58].web
|
1645
|
-
delegate_description = registered_delegate_info[
|
1646
|
-
delegate.hotkey_ss58
|
1647
|
-
].additional
|
1648
|
-
else:
|
1649
|
-
delegate_name = "~"
|
1650
|
-
delegate_url = ""
|
1651
|
-
delegate_description = ""
|
1652
|
-
|
1653
|
-
if delegate.hotkey_ss58 in prev_delegates_dict:
|
1654
|
-
prev_stake = prev_delegates_dict[delegate.hotkey_ss58].total_stake
|
1655
|
-
if prev_stake == 0:
|
1656
|
-
if delegate.total_stake > 0:
|
1657
|
-
rate_change_in_stake_str = "[green]100%[/green]"
|
1658
|
-
else:
|
1659
|
-
rate_change_in_stake_str = "[grey0]0%[/grey0]"
|
1660
|
-
else:
|
1661
|
-
rate_change_in_stake = (
|
1662
|
-
100
|
1663
|
-
* (float(delegate.total_stake) - float(prev_stake))
|
1664
|
-
/ float(prev_stake)
|
1665
|
-
)
|
1666
|
-
if rate_change_in_stake > 0:
|
1667
|
-
rate_change_in_stake_str = "[green]{:.2f}%[/green]".format(
|
1668
|
-
rate_change_in_stake
|
1669
|
-
)
|
1670
|
-
elif rate_change_in_stake < 0:
|
1671
|
-
rate_change_in_stake_str = "[red]{:.2f}%[/red]".format(
|
1672
|
-
rate_change_in_stake
|
1673
|
-
)
|
1674
|
-
else:
|
1675
|
-
rate_change_in_stake_str = "[grey0]0%[/grey0]"
|
1676
|
-
else:
|
1677
|
-
rate_change_in_stake_str = "[grey0]NA[/grey0]"
|
1678
|
-
table.add_row(
|
1679
|
-
# INDEX
|
1680
|
-
str(i),
|
1681
|
-
# DELEGATE
|
1682
|
-
Text(delegate_name, style=f"link {delegate_url}"),
|
1683
|
-
# SS58
|
1684
|
-
f"{delegate.hotkey_ss58}",
|
1685
|
-
# NOMINATORS
|
1686
|
-
str(len([nom for nom in delegate.nominators if nom[1].rao > 0])),
|
1687
|
-
# DELEGATE STAKE
|
1688
|
-
f"{owner_stake!s:13.13}",
|
1689
|
-
# TOTAL STAKE
|
1690
|
-
f"{delegate.total_stake!s:13.13}",
|
1691
|
-
# CHANGE/(4h)
|
1692
|
-
rate_change_in_stake_str,
|
1693
|
-
# TAKE
|
1694
|
-
f"{delegate.take * 100:.1f}%",
|
1695
|
-
# NOMINATOR/(24h)/k
|
1696
|
-
f"{Balance.from_tao(delegate.total_daily_return.tao * (1000 / (0.001 + delegate.total_stake.tao)))!s:6.6}",
|
1697
|
-
# DELEGATE/(24h)
|
1698
|
-
f"{Balance.from_tao(delegate.total_daily_return.tao * 0.18) !s:6.6}",
|
1699
|
-
# VPERMIT
|
1700
|
-
str(group_subnets(delegate.registrations)),
|
1701
|
-
# Desc
|
1702
|
-
str(delegate_description),
|
1703
|
-
end_section=True,
|
1704
|
-
)
|
1705
|
-
console.print(table)
|
1706
|
-
|
1707
|
-
|
1708
|
-
async def nominate(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool):
|
1709
|
-
"""Nominate wallet."""
|
1710
|
-
|
1711
|
-
console.print(f"Nominating on [dark_orange]network: {subtensor.network}")
|
1712
|
-
# Unlock the wallet.
|
1713
|
-
if not unlock_key(wallet).success and unlock_key(wallet, "hot").success:
|
1714
|
-
return False
|
1715
|
-
|
1716
|
-
print_verbose(f"Checking hotkey ({wallet.hotkey_str}) is a delegate")
|
1717
|
-
# Check if the hotkey is already a delegate.
|
1718
|
-
if await subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address):
|
1719
|
-
err_console.print(
|
1720
|
-
f"Aborting: Hotkey {wallet.hotkey.ss58_address} is already a delegate."
|
1721
|
-
)
|
1722
|
-
return
|
1723
|
-
|
1724
|
-
print_verbose("Nominating hotkey as a delegate")
|
1725
|
-
result: bool = await nominate_extrinsic(subtensor, wallet)
|
1726
|
-
if not result:
|
1727
|
-
err_console.print(
|
1728
|
-
f"Could not became a delegate on [white]{subtensor.network}[/white]"
|
1729
|
-
)
|
1730
|
-
return
|
1731
|
-
else:
|
1732
|
-
# Check if we are a delegate.
|
1733
|
-
print_verbose("Confirming delegate status")
|
1734
|
-
is_delegate: bool = await subtensor.is_hotkey_delegate(
|
1735
|
-
wallet.hotkey.ss58_address
|
1736
|
-
)
|
1737
|
-
if not is_delegate:
|
1738
|
-
err_console.print(
|
1739
|
-
f"Could not became a delegate on [white]{subtensor.network}[/white]"
|
1740
|
-
)
|
1741
|
-
return
|
1742
|
-
console.print(
|
1743
|
-
f"Successfully became a delegate on [white]{subtensor.network}[/white]"
|
1744
|
-
)
|
1745
|
-
|
1746
|
-
# Prompt use to set identity on chain.
|
1747
|
-
if prompt:
|
1748
|
-
do_set_identity = Confirm.ask("Would you like to set your identity? [y/n]")
|
1749
|
-
|
1750
|
-
if do_set_identity:
|
1751
|
-
id_prompts = set_id_prompts(validator=True)
|
1752
|
-
await set_id(wallet, subtensor, *id_prompts, prompt=prompt)
|