bittensor-cli 8.4.3__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 -1392
  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 +399 -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.3.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 -1752
  28. bittensor_cli/src/commands/stake/stake.py +0 -1448
  29. bittensor_cli/src/commands/subnets.py +0 -897
  30. bittensor_cli-8.4.3.dist-info/RECORD +0 -31
  31. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/WHEEL +0 -0
  32. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/entry_points.txt +0 -0
  33. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,14 @@
1
1
  import asyncio
2
- from typing import TYPE_CHECKING, Union
2
+ from typing import TYPE_CHECKING, Union, Optional
3
3
 
4
+ import typer
4
5
  from bittensor_wallet import Wallet
5
6
  from rich import box
6
7
  from rich.table import Column, Table
8
+ from rich.prompt import Confirm
9
+ from scalecodec import GenericCall
7
10
 
8
- from bittensor_cli.src import HYPERPARAMS
11
+ from bittensor_cli.src import HYPERPARAMS, DelegatesDetails, COLOR_PALETTE
9
12
  from bittensor_cli.src.bittensor.chain_data import decode_account_id
10
13
  from bittensor_cli.src.bittensor.utils import (
11
14
  console,
@@ -14,10 +17,14 @@ from bittensor_cli.src.bittensor.utils import (
14
17
  print_verbose,
15
18
  normalize_hyperparameters,
16
19
  unlock_key,
20
+ blocks_to_duration,
17
21
  )
18
22
 
19
23
  if TYPE_CHECKING:
20
- from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
24
+ from bittensor_cli.src.bittensor.subtensor_interface import (
25
+ SubtensorInterface,
26
+ ProposalVoteData,
27
+ )
21
28
 
22
29
 
23
30
  # helpers and extrinsics
@@ -89,12 +96,11 @@ async def set_hyperparameter_extrinsic(
89
96
  finalization/inclusion, the response is `True`.
90
97
  """
91
98
  print_verbose("Confirming subnet owner")
92
- subnet_owner_ = await subtensor.substrate.query(
99
+ subnet_owner = await subtensor.query(
93
100
  module="SubtensorModule",
94
101
  storage_function="SubnetOwner",
95
102
  params=[netuid],
96
103
  )
97
- subnet_owner = decode_account_id(subnet_owner_[0])
98
104
  if subnet_owner != wallet.coldkeypub.ss58_address:
99
105
  err_console.print(
100
106
  ":cross_mark: [red]This wallet doesn't own the specified subnet.[/red]"
@@ -110,7 +116,7 @@ async def set_hyperparameter_extrinsic(
110
116
  return False
111
117
 
112
118
  with console.status(
113
- f":satellite: Setting hyperparameter {parameter} to {value} on subnet: {netuid} ...",
119
+ f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{value}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...",
114
120
  spinner="earth",
115
121
  ):
116
122
  substrate = subtensor.substrate
@@ -165,11 +171,297 @@ async def set_hyperparameter_extrinsic(
165
171
  # Successful registration, final check for membership
166
172
  else:
167
173
  console.print(
168
- f":white_heavy_check_mark: [green]Hyperparameter {parameter} changed to {value}[/green]"
174
+ f":white_heavy_check_mark: [dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]"
169
175
  )
170
176
  return True
171
177
 
172
178
 
179
+ async def _get_senate_members(
180
+ subtensor: "SubtensorInterface", block_hash: Optional[str] = None
181
+ ) -> list[str]:
182
+ """
183
+ Gets all members of the senate on the given subtensor's network
184
+
185
+ :param subtensor: SubtensorInterface object to use for the query
186
+
187
+ :return: list of the senate members' ss58 addresses
188
+ """
189
+ senate_members = await subtensor.query(
190
+ module="SenateMembers",
191
+ storage_function="Members",
192
+ params=None,
193
+ block_hash=block_hash,
194
+ )
195
+ try:
196
+ return [
197
+ decode_account_id(i[x][0]) for i in senate_members for x in range(len(i))
198
+ ]
199
+ except (IndexError, TypeError):
200
+ err_console.print("Unable to retrieve senate members.")
201
+ return []
202
+
203
+
204
+ async def _get_proposals(
205
+ subtensor: "SubtensorInterface", block_hash: str
206
+ ) -> dict[str, tuple[dict, "ProposalVoteData"]]:
207
+ async def get_proposal_call_data(p_hash: str) -> Optional[GenericCall]:
208
+ proposal_data = await subtensor.query(
209
+ module="Triumvirate",
210
+ storage_function="ProposalOf",
211
+ block_hash=block_hash,
212
+ params=[p_hash],
213
+ )
214
+ return proposal_data
215
+
216
+ ph = await subtensor.query(
217
+ module="Triumvirate",
218
+ storage_function="Proposals",
219
+ params=None,
220
+ block_hash=block_hash,
221
+ )
222
+
223
+ try:
224
+ proposal_hashes: list[str] = [
225
+ f"0x{bytes(ph[0][x][0]).hex()}" for x in range(len(ph[0]))
226
+ ]
227
+ except (IndexError, TypeError):
228
+ err_console.print("Unable to retrieve proposal vote data")
229
+ return {}
230
+
231
+ call_data_, vote_data_ = await asyncio.gather(
232
+ asyncio.gather(*[get_proposal_call_data(h) for h in proposal_hashes]),
233
+ asyncio.gather(*[subtensor.get_vote_data(h) for h in proposal_hashes]),
234
+ )
235
+ return {
236
+ proposal_hash: (cd, vd)
237
+ for cd, vd, proposal_hash in zip(call_data_, vote_data_, proposal_hashes)
238
+ }
239
+
240
+
241
+ def display_votes(
242
+ vote_data: "ProposalVoteData", delegate_info: dict[str, DelegatesDetails]
243
+ ) -> str:
244
+ vote_list = list()
245
+
246
+ for address in vote_data.ayes:
247
+ vote_list.append(
248
+ "{}: {}".format(
249
+ delegate_info[address].display if address in delegate_info else address,
250
+ "[bold green]Aye[/bold green]",
251
+ )
252
+ )
253
+
254
+ for address in vote_data.nays:
255
+ vote_list.append(
256
+ "{}: {}".format(
257
+ delegate_info[address].display if address in delegate_info else address,
258
+ "[bold red]Nay[/bold red]",
259
+ )
260
+ )
261
+
262
+ return "\n".join(vote_list)
263
+
264
+
265
+ def format_call_data(call_data: dict) -> str:
266
+ # Extract the module and call details
267
+ module, call_details = next(iter(call_data.items()))
268
+
269
+ # Extract the call function name and arguments
270
+ call_info = call_details[0]
271
+ call_function, call_args = next(iter(call_info.items()))
272
+
273
+ # Format arguments, handle nested/large payloads
274
+ formatted_args = []
275
+ for arg_name, arg_value in call_args.items():
276
+ if isinstance(arg_value, (tuple, list, dict)):
277
+ # For large nested, show abbreviated version
278
+ content_str = str(arg_value)
279
+ if len(content_str) > 20:
280
+ formatted_args.append(f"{arg_name}: ... [{len(content_str)}] ...")
281
+ else:
282
+ formatted_args.append(f"{arg_name}: {arg_value}")
283
+ else:
284
+ formatted_args.append(f"{arg_name}: {arg_value}")
285
+
286
+ # Format the final output string
287
+ args_str = ", ".join(formatted_args)
288
+ return f"{module}.{call_function}({args_str})"
289
+
290
+
291
+ def _validate_proposal_hash(proposal_hash: str) -> bool:
292
+ if proposal_hash[0:2] != "0x" or len(proposal_hash) != 66:
293
+ return False
294
+ else:
295
+ return True
296
+
297
+
298
+ async def _is_senate_member(subtensor: "SubtensorInterface", hotkey_ss58: str) -> bool:
299
+ """
300
+ Checks if a given neuron (identified by its hotkey SS58 address) is a member of the Bittensor senate.
301
+ The senate is a key governance body within the Bittensor network, responsible for overseeing and
302
+ approving various network operations and proposals.
303
+
304
+ :param subtensor: SubtensorInterface object to use for the query
305
+ :param hotkey_ss58: The `SS58` address of the neuron's hotkey.
306
+
307
+ :return: `True` if the neuron is a senate member at the given block, `False` otherwise.
308
+
309
+ This function is crucial for understanding the governance dynamics of the Bittensor network and for
310
+ identifying the neurons that hold decision-making power within the network.
311
+ """
312
+
313
+ senate_members = await _get_senate_members(subtensor)
314
+
315
+ if not hasattr(senate_members, "count"):
316
+ return False
317
+
318
+ return senate_members.count(hotkey_ss58) > 0
319
+
320
+
321
+ async def vote_senate_extrinsic(
322
+ subtensor: "SubtensorInterface",
323
+ wallet: Wallet,
324
+ proposal_hash: str,
325
+ proposal_idx: int,
326
+ vote: bool,
327
+ wait_for_inclusion: bool = False,
328
+ wait_for_finalization: bool = True,
329
+ prompt: bool = False,
330
+ ) -> bool:
331
+ """Votes ayes or nays on proposals.
332
+
333
+ :param subtensor: The SubtensorInterface object to use for the query
334
+ :param wallet: Bittensor wallet object, with coldkey and hotkey unlocked.
335
+ :param proposal_hash: The hash of the proposal for which voting data is requested.
336
+ :param proposal_idx: The index of the proposal to vote.
337
+ :param vote: Whether to vote aye or nay.
338
+ :param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
339
+ `False` if the extrinsic fails to enter the block within the timeout.
340
+ :param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
341
+ or returns `False` if the extrinsic fails to be finalized within the timeout.
342
+ :param prompt: If `True`, the call waits for confirmation from the user before proceeding.
343
+
344
+ :return: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
345
+ finalization/inclusion, the response is `True`.
346
+ """
347
+
348
+ if prompt:
349
+ # Prompt user for confirmation.
350
+ if not Confirm.ask(f"Cast a vote of {vote}?"):
351
+ return False
352
+
353
+ with console.status(":satellite: Casting vote..", spinner="aesthetic"):
354
+ call = await subtensor.substrate.compose_call(
355
+ call_module="SubtensorModule",
356
+ call_function="vote",
357
+ call_params={
358
+ "hotkey": wallet.hotkey.ss58_address,
359
+ "proposal": proposal_hash,
360
+ "index": proposal_idx,
361
+ "approve": vote,
362
+ },
363
+ )
364
+ success, err_msg = await subtensor.sign_and_send_extrinsic(
365
+ call, wallet, wait_for_inclusion, wait_for_finalization
366
+ )
367
+ if not success:
368
+ err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
369
+ await asyncio.sleep(0.5)
370
+ return False
371
+ # Successful vote, final check for data
372
+ else:
373
+ if vote_data := await subtensor.get_vote_data(proposal_hash):
374
+ if (
375
+ vote_data.ayes.count(wallet.hotkey.ss58_address) > 0
376
+ or vote_data.nays.count(wallet.hotkey.ss58_address) > 0
377
+ ):
378
+ console.print(":white_heavy_check_mark: [green]Vote cast.[/green]")
379
+ return True
380
+ else:
381
+ # hotkey not found in ayes/nays
382
+ err_console.print(
383
+ ":cross_mark: [red]Unknown error. Couldn't find vote.[/red]"
384
+ )
385
+ return False
386
+ else:
387
+ return False
388
+
389
+
390
+ async def set_take_extrinsic(
391
+ subtensor: "SubtensorInterface",
392
+ wallet: Wallet,
393
+ delegate_ss58: str,
394
+ take: float = 0.0,
395
+ ) -> bool:
396
+ """
397
+ Set delegate hotkey take
398
+
399
+ :param subtensor: SubtensorInterface (initialized)
400
+ :param wallet: The wallet containing the hotkey to be nominated.
401
+ :param delegate_ss58: Hotkey
402
+ :param take: Delegate take on subnet ID
403
+
404
+ :return: `True` if the process is successful, `False` otherwise.
405
+
406
+ This function is a key part of the decentralized governance mechanism of Bittensor, allowing for the
407
+ dynamic selection and participation of validators in the network's consensus process.
408
+ """
409
+
410
+ # Calculate u16 representation of the take
411
+ take_u16 = int(take * 0xFFFF)
412
+
413
+ print_verbose("Checking current take")
414
+ # Check if the new take is greater or lower than existing take or if existing is set
415
+ current_take = await get_current_take(subtensor, wallet)
416
+ current_take_u16 = int(float(current_take) * 0xFFFF)
417
+
418
+ if take_u16 == current_take_u16:
419
+ console.print("Nothing to do, take hasn't changed")
420
+ return True
421
+
422
+ if current_take_u16 < take_u16:
423
+ console.print(
424
+ f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%[/{COLOR_PALETTE['POOLS']['RATE']}]. Increasing to [{COLOR_PALETTE['POOLS']['RATE']}]{take * 100:.2f}%."
425
+ )
426
+ with console.status(
427
+ f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..."
428
+ ):
429
+ call = await subtensor.substrate.compose_call(
430
+ call_module="SubtensorModule",
431
+ call_function="increase_take",
432
+ call_params={
433
+ "hotkey": delegate_ss58,
434
+ "take": take_u16,
435
+ },
436
+ )
437
+ success, err = await subtensor.sign_and_send_extrinsic(call, wallet)
438
+
439
+ else:
440
+ console.print(
441
+ f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%[/{COLOR_PALETTE['POOLS']['RATE']}]. Decreasing to [{COLOR_PALETTE['POOLS']['RATE']}]{take * 100:.2f}%."
442
+ )
443
+ with console.status(
444
+ f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..."
445
+ ):
446
+ call = await subtensor.substrate.compose_call(
447
+ call_module="SubtensorModule",
448
+ call_function="decrease_take",
449
+ call_params={
450
+ "hotkey": delegate_ss58,
451
+ "take": take_u16,
452
+ },
453
+ )
454
+ success, err = await subtensor.sign_and_send_extrinsic(call, wallet)
455
+
456
+ if not success:
457
+ err_console.print(err)
458
+ else:
459
+ console.print(
460
+ ":white_heavy_check_mark: [dark_sea_green_3]Finalized[/dark_sea_green_3]"
461
+ )
462
+ return success
463
+
464
+
173
465
  # commands
174
466
 
175
467
 
@@ -217,13 +509,20 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int):
217
509
  print_error(f"Subnet with netuid {netuid} does not exist.")
218
510
  return False
219
511
  subnet = await subtensor.get_subnet_hyperparameters(netuid)
512
+ subnet_info = await subtensor.subnet(netuid)
513
+ if subnet_info is None:
514
+ print_error(f"Subnet with netuid {netuid} does not exist.")
515
+ raise typer.Exit()
220
516
 
221
517
  table = Table(
222
- Column("[white]HYPERPARAMETER", style="bright_magenta"),
223
- Column("[white]VALUE", style="light_goldenrod2"),
224
- Column("[white]NORMALIZED", style="light_goldenrod3"),
225
- title=f"[underline dark_orange]\nSubnet Hyperparameters[/underline dark_orange]\n NETUID: [dark_orange]"
226
- f"{netuid}[/dark_orange] - Network: [dark_orange]{subtensor.network}[/dark_orange]\n",
518
+ Column("[white]HYPERPARAMETER", style=COLOR_PALETTE["SUDO"]["HYPERPARAMETER"]),
519
+ Column("[white]VALUE", style=COLOR_PALETTE["SUDO"]["VALUE"]),
520
+ Column("[white]NORMALIZED", style=COLOR_PALETTE["SUDO"]["NORMALIZED"]),
521
+ title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]\nSubnet Hyperparameters\n NETUID: "
522
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}"
523
+ f"{f' ({subnet_info.subnet_name})' if subnet_info.subnet_name is not None else ''}"
524
+ f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
525
+ f" - Network: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{subtensor.network}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n",
227
526
  show_footer=True,
228
527
  width=None,
229
528
  pad_edge=False,
@@ -238,3 +537,246 @@ async def get_hyperparameters(subtensor: "SubtensorInterface", netuid: int):
238
537
 
239
538
  console.print(table)
240
539
  return True
540
+
541
+
542
+ async def get_senate(subtensor: "SubtensorInterface"):
543
+ """View Bittensor's senate memebers"""
544
+ with console.status(
545
+ f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
546
+ spinner="aesthetic",
547
+ ) as status:
548
+ print_verbose("Fetching senate members", status)
549
+ senate_members = await _get_senate_members(subtensor)
550
+
551
+ print_verbose("Fetching member details from Github and on-chain identities")
552
+ delegate_info: dict[
553
+ str, DelegatesDetails
554
+ ] = await subtensor.get_delegate_identities()
555
+
556
+ table = Table(
557
+ Column(
558
+ "[bold white]NAME",
559
+ style="bright_cyan",
560
+ no_wrap=True,
561
+ ),
562
+ Column(
563
+ "[bold white]ADDRESS",
564
+ style="bright_magenta",
565
+ no_wrap=True,
566
+ ),
567
+ title=f"[underline dark_orange]Senate[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n",
568
+ show_footer=True,
569
+ show_edge=False,
570
+ expand=False,
571
+ border_style="bright_black",
572
+ leading=True,
573
+ )
574
+
575
+ for ss58_address in senate_members:
576
+ table.add_row(
577
+ (
578
+ delegate_info[ss58_address].display
579
+ if ss58_address in delegate_info
580
+ else "~"
581
+ ),
582
+ ss58_address,
583
+ )
584
+
585
+ return console.print(table)
586
+
587
+
588
+ async def proposals(subtensor: "SubtensorInterface", verbose: bool):
589
+ console.print(
590
+ ":satellite: Syncing with chain: [white]{}[/white] ...".format(
591
+ subtensor.network
592
+ )
593
+ )
594
+ block_hash = await subtensor.substrate.get_chain_head()
595
+ senate_members, all_proposals, current_block = await asyncio.gather(
596
+ _get_senate_members(subtensor, block_hash),
597
+ _get_proposals(subtensor, block_hash),
598
+ subtensor.substrate.get_block_number(block_hash),
599
+ )
600
+
601
+ registered_delegate_info: dict[
602
+ str, DelegatesDetails
603
+ ] = await subtensor.get_delegate_identities()
604
+
605
+ title = (
606
+ f"[bold #4196D6]Bittensor Governance Proposals[/bold #4196D6]\n"
607
+ f"[steel_blue3]Current Block:[/steel_blue3] {current_block}\t"
608
+ f"[steel_blue3]Network:[/steel_blue3] {subtensor.network}\n\n"
609
+ f"[steel_blue3]Active Proposals:[/steel_blue3] {len(all_proposals)}\t"
610
+ f"[steel_blue3]Senate Size:[/steel_blue3] {len(senate_members)}\n"
611
+ )
612
+ table = Table(
613
+ Column(
614
+ "[white]HASH",
615
+ style="light_goldenrod2",
616
+ no_wrap=True,
617
+ ),
618
+ Column("[white]THRESHOLD", style="rgb(42,161,152)"),
619
+ Column("[white]AYES", style="green"),
620
+ Column("[white]NAYS", style="red"),
621
+ Column(
622
+ "[white]VOTES",
623
+ style="rgb(50,163,219)",
624
+ ),
625
+ Column("[white]END", style="bright_cyan"),
626
+ Column("[white]CALLDATA", style="dark_sea_green", width=30),
627
+ title=title,
628
+ show_footer=True,
629
+ box=box.SIMPLE_HEAVY,
630
+ pad_edge=False,
631
+ width=None,
632
+ border_style="bright_black",
633
+ )
634
+ for hash_, (call_data, vote_data) in all_proposals.items():
635
+ blocks_remaining = vote_data.end - current_block
636
+ if blocks_remaining > 0:
637
+ duration_str = blocks_to_duration(blocks_remaining)
638
+ vote_end_cell = f"{vote_data.end} [dim](in {duration_str})[/dim]"
639
+ else:
640
+ vote_end_cell = f"{vote_data.end} [red](expired)[/red]"
641
+
642
+ ayes_threshold = (
643
+ (len(vote_data.ayes) / vote_data.threshold * 100)
644
+ if vote_data.threshold > 0
645
+ else 0
646
+ )
647
+ nays_threshold = (
648
+ (len(vote_data.nays) / vote_data.threshold * 100)
649
+ if vote_data.threshold > 0
650
+ else 0
651
+ )
652
+ table.add_row(
653
+ hash_ if verbose else f"{hash_[:4]}...{hash_[-4:]}",
654
+ str(vote_data.threshold),
655
+ f"{len(vote_data.ayes)} ({ayes_threshold:.2f}%)",
656
+ f"{len(vote_data.nays)} ({nays_threshold:.2f}%)",
657
+ display_votes(vote_data, registered_delegate_info),
658
+ vote_end_cell,
659
+ format_call_data(call_data),
660
+ )
661
+ console.print(table)
662
+ console.print(
663
+ "\n[dim]* Both Ayes and Nays percentages are calculated relative to the proposal's threshold.[/dim]"
664
+ )
665
+
666
+
667
+ async def senate_vote(
668
+ wallet: Wallet,
669
+ subtensor: "SubtensorInterface",
670
+ proposal_hash: str,
671
+ vote: bool,
672
+ prompt: bool,
673
+ ) -> bool:
674
+ """Vote in Bittensor's governance protocol proposals"""
675
+
676
+ if not proposal_hash:
677
+ err_console.print(
678
+ "Aborting: Proposal hash not specified. View all proposals with the `proposals` command."
679
+ )
680
+ return False
681
+ elif not _validate_proposal_hash(proposal_hash):
682
+ err_console.print(
683
+ "Aborting. Proposal hash is invalid. Proposal hashes should start with '0x' and be 32 bytes long"
684
+ )
685
+ return False
686
+
687
+ print_verbose(f"Fetching senate status of {wallet.hotkey_str}")
688
+ if not await _is_senate_member(subtensor, hotkey_ss58=wallet.hotkey.ss58_address):
689
+ err_console.print(
690
+ f"Aborting: Hotkey {wallet.hotkey.ss58_address} isn't a senate member."
691
+ )
692
+ return False
693
+
694
+ # Unlock the wallet.
695
+ if (
696
+ not unlock_key(wallet, "hot").success
697
+ and unlock_key(wallet, "cold").success
698
+ ):
699
+ return False
700
+
701
+ console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}")
702
+ vote_data = await subtensor.get_vote_data(proposal_hash, reuse_block=True)
703
+ if not vote_data:
704
+ err_console.print(":cross_mark: [red]Failed[/red]: Proposal not found.")
705
+ return False
706
+
707
+ success = await vote_senate_extrinsic(
708
+ subtensor=subtensor,
709
+ wallet=wallet,
710
+ proposal_hash=proposal_hash,
711
+ proposal_idx=vote_data.index,
712
+ vote=vote,
713
+ wait_for_inclusion=True,
714
+ wait_for_finalization=False,
715
+ prompt=prompt,
716
+ )
717
+
718
+ return success
719
+
720
+
721
+ async def get_current_take(subtensor: "SubtensorInterface", wallet: Wallet):
722
+ current_take = await subtensor.current_take(wallet.hotkey.ss58_address)
723
+ return current_take
724
+
725
+
726
+ async def display_current_take(subtensor: "SubtensorInterface", wallet: Wallet) -> None:
727
+ current_take = await get_current_take(subtensor, wallet)
728
+ console.print(
729
+ f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%"
730
+ )
731
+
732
+
733
+ async def set_take(
734
+ wallet: Wallet, subtensor: "SubtensorInterface", take: float
735
+ ) -> bool:
736
+ """Set delegate take."""
737
+
738
+ async def _do_set_take() -> bool:
739
+ if take > 0.18 or take < 0:
740
+ err_console.print("ERROR: Take value should not exceed 18% or be below 0%")
741
+ return False
742
+
743
+ block_hash = await subtensor.substrate.get_chain_head()
744
+ netuids_registered = await subtensor.get_netuids_for_hotkey(
745
+ wallet.hotkey.ss58_address, block_hash=block_hash
746
+ )
747
+ if not len(netuids_registered) > 0:
748
+ err_console.print(
749
+ f"Hotkey [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] is not registered to any subnet. Please register using [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]`btcli subnets register`[{COLOR_PALETTE['GENERAL']['SUBHEADING']}] and try again."
750
+ )
751
+ return False
752
+
753
+ result: bool = await set_take_extrinsic(
754
+ subtensor=subtensor,
755
+ wallet=wallet,
756
+ delegate_ss58=wallet.hotkey.ss58_address,
757
+ take=take,
758
+ )
759
+
760
+ if not result:
761
+ err_console.print("Could not set the take")
762
+ return False
763
+ else:
764
+ new_take = await get_current_take(subtensor, wallet)
765
+ console.print(
766
+ f"New take is [{COLOR_PALETTE['POOLS']['RATE']}]{new_take * 100.:.2f}%"
767
+ )
768
+ return True
769
+
770
+ console.print(
771
+ f"Setting take on [{COLOR_PALETTE['GENERAL']['LINKS']}]network: {subtensor.network}"
772
+ )
773
+
774
+ if (
775
+ not unlock_key(wallet, "hot").success
776
+ and unlock_key(wallet, "cold").success
777
+ ):
778
+ return False
779
+
780
+ result_ = await _do_set_take()
781
+
782
+ return result_