bittensor-cli 8.4.3__py3-none-any.whl → 9.0.0rc2__py3-none-any.whl

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