bittensor-cli 8.4.4__py3-none-any.whl → 9.0.0__py3-none-any.whl

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