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.
Files changed (30) hide show
  1. bittensor_cli/__init__.py +2 -2
  2. bittensor_cli/cli.py +1503 -1372
  3. bittensor_cli/src/__init__.py +625 -197
  4. bittensor_cli/src/bittensor/balances.py +41 -8
  5. bittensor_cli/src/bittensor/chain_data.py +557 -428
  6. bittensor_cli/src/bittensor/extrinsics/registration.py +161 -47
  7. bittensor_cli/src/bittensor/extrinsics/root.py +14 -8
  8. bittensor_cli/src/bittensor/extrinsics/transfer.py +14 -21
  9. bittensor_cli/src/bittensor/minigraph.py +46 -8
  10. bittensor_cli/src/bittensor/subtensor_interface.py +572 -253
  11. bittensor_cli/src/bittensor/utils.py +326 -75
  12. bittensor_cli/src/commands/stake/__init__.py +154 -0
  13. bittensor_cli/src/commands/stake/children_hotkeys.py +123 -91
  14. bittensor_cli/src/commands/stake/move.py +1000 -0
  15. bittensor_cli/src/commands/stake/stake.py +1637 -1264
  16. bittensor_cli/src/commands/subnets/__init__.py +0 -0
  17. bittensor_cli/src/commands/subnets/price.py +867 -0
  18. bittensor_cli/src/commands/subnets/subnets.py +2043 -0
  19. bittensor_cli/src/commands/sudo.py +529 -26
  20. bittensor_cli/src/commands/wallets.py +231 -535
  21. bittensor_cli/src/commands/weights.py +15 -11
  22. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/METADATA +7 -4
  23. bittensor_cli-9.0.0rc1.dist-info/RECORD +32 -0
  24. bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
  25. bittensor_cli/src/commands/root.py +0 -1752
  26. bittensor_cli/src/commands/subnets.py +0 -897
  27. bittensor_cli-8.4.2.dist-info/RECORD +0 -31
  28. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/WHEEL +0 -0
  29. {bittensor_cli-8.4.2.dist-info → bittensor_cli-9.0.0rc1.dist-info}/entry_points.txt +0 -0
  30. {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)