bittensor-cli 9.4.3__tar.gz → 9.5.0__tar.gz

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 (41) hide show
  1. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/PKG-INFO +5 -6
  2. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/README.md +0 -1
  3. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/cli.py +3 -10
  4. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/extrinsics/transfer.py +2 -2
  5. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/utils.py +8 -38
  6. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/stake/add.py +1 -1
  7. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/stake/remove.py +1 -1
  8. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/subnets/price.py +48 -289
  9. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/sudo.py +10 -6
  10. bittensor_cli-9.5.0/bittensor_cli/src/commands/view.py +354 -0
  11. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/wallets.py +1 -7
  12. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli.egg-info/PKG-INFO +5 -6
  13. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli.egg-info/requires.txt +5 -4
  14. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/pyproject.toml +6 -5
  15. bittensor_cli-9.4.3/bittensor_cli/src/commands/view.py +0 -2945
  16. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/__init__.py +0 -0
  17. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/doc_generation_helper.py +0 -0
  18. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/__init__.py +0 -0
  19. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/__init__.py +0 -0
  20. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/balances.py +0 -0
  21. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/chain_data.py +0 -0
  22. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/extrinsics/__init__.py +0 -0
  23. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/extrinsics/registration.py +0 -0
  24. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/extrinsics/root.py +0 -0
  25. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/minigraph.py +0 -0
  26. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/networking.py +0 -0
  27. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/bittensor/subtensor_interface.py +0 -0
  28. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/__init__.py +0 -0
  29. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/stake/__init__.py +0 -0
  30. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/stake/children_hotkeys.py +0 -0
  31. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/stake/list.py +0 -0
  32. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/stake/move.py +0 -0
  33. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/subnets/__init__.py +0 -0
  34. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/subnets/subnets.py +0 -0
  35. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/src/commands/weights.py +0 -0
  36. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli/version.py +0 -0
  37. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli.egg-info/SOURCES.txt +0 -0
  38. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli.egg-info/dependency_links.txt +0 -0
  39. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli.egg-info/entry_points.txt +0 -0
  40. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/bittensor_cli.egg-info/top_level.txt +0 -0
  41. {bittensor_cli-9.4.3 → bittensor_cli-9.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bittensor-cli
3
- Version: 9.4.3
3
+ Version: 9.5.0
4
4
  Summary: Bittensor CLI
5
5
  Author: bittensor.com
6
6
  Project-URL: homepage, https://github.com/opentensor/btcli
@@ -13,28 +13,27 @@ Requires-Dist: aiohttp~=3.10.2
13
13
  Requires-Dist: backoff~=2.2.1
14
14
  Requires-Dist: click<8.2.0
15
15
  Requires-Dist: GitPython>=3.0.0
16
- Requires-Dist: fuzzywuzzy~=0.18.0
17
16
  Requires-Dist: netaddr~=1.3.0
18
17
  Requires-Dist: numpy<3.0.0,>=2.0.1
19
18
  Requires-Dist: Jinja2
20
19
  Requires-Dist: pycryptodome<4.0.0,>=3.0.0
21
20
  Requires-Dist: PyYAML~=6.0.1
22
- Requires-Dist: pytest
23
- Requires-Dist: python-Levenshtein
24
21
  Requires-Dist: rich<15.0,>=13.7
25
22
  Requires-Dist: scalecodec==1.2.11
26
23
  Requires-Dist: typer<0.16,>=0.12
27
24
  Requires-Dist: bittensor-wallet>=3.0.7
28
25
  Requires-Dist: plotille>=5.0.0
29
- Requires-Dist: pywry>=0.6.2
30
26
  Requires-Dist: plotly>=6.0.0
31
27
  Provides-Extra: cuda
32
28
  Requires-Dist: torch<3.0,>=1.13.1; extra == "cuda"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest; extra == "dev"
31
+ Requires-Dist: pytest-asyncio; extra == "dev"
32
+ Requires-Dist: ruff==0.11.5; extra == "dev"
33
33
 
34
34
  <div align="center">
35
35
 
36
36
  # Bittensor CLI <!-- omit in toc -->
37
- ### Rao Development Version
38
37
  [![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/bittensor)
39
38
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
39
  [![PyPI version](https://badge.fury.io/py/bittensor_cli.svg)](https://badge.fury.io/py/bittensor_cli)
@@ -1,7 +1,6 @@
1
1
  <div align="center">
2
2
 
3
3
  # Bittensor CLI <!-- omit in toc -->
4
- ### Rao Development Version
5
4
  [![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/bittensor)
6
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
6
  [![PyPI version](https://badge.fury.io/py/bittensor_cli.svg)](https://badge.fury.io/py/bittensor_cli)
@@ -67,8 +67,6 @@ from bittensor_cli.src.bittensor.utils import (
67
67
  prompt_for_identity,
68
68
  validate_uri,
69
69
  prompt_for_subnet_identity,
70
- print_linux_dependency_message,
71
- is_linux,
72
70
  validate_rate_tolerance,
73
71
  )
74
72
 
@@ -4875,9 +4873,6 @@ class CLIManager:
4875
4873
  if all_netuids and not json_output:
4876
4874
  html_output = True
4877
4875
 
4878
- if html_output and is_linux():
4879
- print_linux_dependency_message()
4880
-
4881
4876
  return self._run_command(
4882
4877
  price.price(
4883
4878
  self.initialize_chain(network),
@@ -5655,7 +5650,7 @@ class CLIManager:
5655
5650
  help="Coldkey SS58 address to view dashboard for",
5656
5651
  ),
5657
5652
  use_wry: bool = typer.Option(
5658
- False, "--use-wry", help="Use PyWry instead of browser window"
5653
+ False, "--use-wry", "--html", help="Display output in browser window."
5659
5654
  ),
5660
5655
  save_file: bool = typer.Option(
5661
5656
  False, "--save-file", "--save", help="Save the dashboard HTML file"
@@ -5668,12 +5663,10 @@ class CLIManager:
5668
5663
  Display html dashboard with subnets list, stake, and neuron information.
5669
5664
  """
5670
5665
  self.verbosity_handler(quiet, verbose)
5671
- if use_wry and is_linux():
5672
- print_linux_dependency_message()
5673
5666
 
5674
5667
  if use_wry and save_file:
5675
- print_error("Cannot save file when using PyWry.")
5676
- raise typer.Exit()
5668
+ print_error("Cannot save file when using browser output.")
5669
+ return
5677
5670
 
5678
5671
  if save_file:
5679
5672
  if not dashboard_path:
@@ -57,7 +57,7 @@ async def transfer_extrinsic(
57
57
  """
58
58
  call = await subtensor.substrate.compose_call(
59
59
  call_module="Balances",
60
- call_function="transfer_allow_death",
60
+ call_function="transfer_keep_alive",
61
61
  call_params={"dest": destination, "value": amount.rao},
62
62
  )
63
63
 
@@ -82,7 +82,7 @@ async def transfer_extrinsic(
82
82
  """
83
83
  call = await subtensor.substrate.compose_call(
84
84
  call_module="Balances",
85
- call_function="transfer_allow_death",
85
+ call_function="transfer_keep_alive",
86
86
  call_params={"dest": destination, "value": amount.rao},
87
87
  )
88
88
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
@@ -15,7 +15,7 @@ from bittensor_wallet import Wallet, Keypair
15
15
  from bittensor_wallet.utils import SS58_FORMAT
16
16
  from bittensor_wallet.errors import KeyFileError, PasswordError
17
17
  from bittensor_wallet import utils
18
- from jinja2 import Template
18
+ from jinja2 import Template, Environment, PackageLoader, select_autoescape
19
19
  from markupsafe import Markup
20
20
  import numpy as np
21
21
  from numpy.typing import NDArray
@@ -38,6 +38,11 @@ json_console = Console()
38
38
  err_console = Console(stderr=True)
39
39
  verbose_console = Console(quiet=True)
40
40
 
41
+ jinja_env = Environment(
42
+ loader=PackageLoader("bittensor_cli", "src/bittensor/templates"),
43
+ autoescape=select_autoescape(),
44
+ )
45
+
41
46
  UnlockStatus = namedtuple("UnlockStatus", ["success", "message"])
42
47
 
43
48
 
@@ -835,11 +840,7 @@ def update_metadata_table(table_name: str, values: dict[str, str]) -> None:
835
840
  """
836
841
  with DB() as (conn, cursor):
837
842
  cursor.execute(
838
- "CREATE TABLE IF NOT EXISTS metadata ("
839
- "TableName TEXT, "
840
- "Key TEXT, "
841
- "Value TEXT"
842
- ")"
843
+ "CREATE TABLE IF NOT EXISTS metadata (TableName TEXT, Key TEXT, Value TEXT)"
843
844
  )
844
845
  conn.commit()
845
846
  for key, value in values.items():
@@ -1295,37 +1296,6 @@ def get_subnet_name(subnet_info, max_length: int = 20) -> str:
1295
1296
  return name
1296
1297
 
1297
1298
 
1298
- def print_linux_dependency_message():
1299
- """Prints the WebKit dependency message for Linux systems."""
1300
- console.print("[red]This command requires WebKit dependencies on Linux.[/red]")
1301
- console.print(
1302
- "\nPlease make sure these packages are installed on your system for PyWry to work:"
1303
- )
1304
- console.print("\nArch Linux / Manjaro:")
1305
- console.print("[green]sudo pacman -S webkit2gtk[/green]")
1306
- console.print("\nDebian / Ubuntu:")
1307
- console.print("[green]sudo apt install libwebkit2gtk-4.0-dev[/green]")
1308
- console.print("\nNote for Ubuntu 24.04+ & Debian 13+:")
1309
- console.print("You may need these additional steps to install libwebkit2gtk:")
1310
- console.print(
1311
- "\tCreate a new source file with: [green]sudo vim /etc/apt/sources.list.d/jammy-temp.list[/green]"
1312
- )
1313
- console.print(
1314
- "\tAdd this into the file and save: [green]deb http://archive.ubuntu.com/ubuntu jammy main universe[/green]"
1315
- )
1316
- console.print(
1317
- "\tUpdate the repository and install the webkit dependency: [green]sudo apt update && sudo apt install "
1318
- "libwebkit2gtk-4.0-dev[/green]"
1319
- )
1320
- console.print("\nFedora / CentOS / AlmaLinux:")
1321
- console.print("[green]sudo dnf install gtk3-devel webkit2gtk3-devel[/green]\n\n")
1322
-
1323
-
1324
- def is_linux():
1325
- """Returns True if the operating system is Linux."""
1326
- return platform.system().lower() == "linux"
1327
-
1328
-
1329
1299
  def validate_rate_tolerance(value: Optional[float]) -> Optional[float]:
1330
1300
  """Validates rate tolerance input"""
1331
1301
  if value is not None:
@@ -1337,7 +1307,7 @@ def validate_rate_tolerance(value: Optional[float]) -> Optional[float]:
1337
1307
  raise typer.BadParameter("Rate tolerance cannot be greater than 1 (100%).")
1338
1308
  if value > 0.5:
1339
1309
  console.print(
1340
- f"[yellow]Warning: High rate tolerance of {value*100}% specified. "
1310
+ f"[yellow]Warning: High rate tolerance of {value * 100}% specified. "
1341
1311
  "This may result in unfavorable transaction execution.[/yellow]"
1342
1312
  )
1343
1313
  return value
@@ -586,7 +586,7 @@ def _define_stake_table(
586
586
 
587
587
  if safe_staking:
588
588
  table.add_column(
589
- f"Rate with tolerance: [blue]({rate_tolerance*100}%)[/blue]",
589
+ f"Rate with tolerance: [blue]({rate_tolerance * 100}%)[/blue]",
590
590
  justify="center",
591
591
  style=COLOR_PALETTE["POOLS"]["RATE"],
592
592
  )
@@ -1266,7 +1266,7 @@ def _create_unstake_table(
1266
1266
  )
1267
1267
  if safe_staking:
1268
1268
  table.add_column(
1269
- f"Rate with tolerance: [blue]({rate_tolerance*100}%)[/blue]",
1269
+ f"Rate with tolerance: [blue]({rate_tolerance * 100}%)[/blue]",
1270
1270
  justify="center",
1271
1271
  style=COLOR_PALETTE["POOLS"]["RATE"],
1272
1272
  )
@@ -1,7 +1,9 @@
1
1
  import asyncio
2
2
  import json
3
3
  import math
4
- from pywry import PyWry
4
+ import tempfile
5
+ import webbrowser
6
+
5
7
  from typing import TYPE_CHECKING
6
8
 
7
9
  import plotille
@@ -14,6 +16,7 @@ from bittensor_cli.src.bittensor.utils import (
14
16
  get_subnet_name,
15
17
  print_error,
16
18
  json_console,
19
+ jinja_env,
17
20
  )
18
21
 
19
22
  if TYPE_CHECKING:
@@ -180,11 +183,7 @@ def _process_subnet_data(block_numbers, all_subnet_infos, netuids, all_netuids):
180
183
 
181
184
 
182
185
  def _generate_html_single_subnet(
183
- netuid,
184
- data,
185
- block_numbers,
186
- interval_hours,
187
- log_scale,
186
+ netuid, data, block_numbers, interval_hours, log_scale, title: str
188
187
  ):
189
188
  """
190
189
  Generate an HTML chart for a single subnet.
@@ -242,119 +241,22 @@ def _generate_html_single_subnet(
242
241
  type="log" if log_scale else "linear",
243
242
  )
244
243
 
245
- # Price change color
246
- price_change_class = "text-green" if stats["change_pct"] > 0 else "text-red"
247
- # Change sign
248
- sign_icon = "▲" if stats["change_pct"] > 0 else "▼"
249
-
250
- fig_dict = fig.to_dict()
251
- fig_json = json.dumps(fig_dict)
252
- html_content = f"""
253
- <!DOCTYPE html>
254
- <html lang="en">
255
- <head>
256
- <meta charset="UTF-8">
257
- <title>Subnet Price View</title>
258
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
259
- <style>
260
- body {{
261
- background-color: #000;
262
- color: #fff;
263
- font-family: Arial, sans-serif;
264
- margin: 0;
265
- padding: 20px;
266
- }}
267
- .header-container {{
268
- display: flex;
269
- align-items: flex-start;
270
- justify-content: space-between;
271
- margin-bottom: 20px;
272
- }}
273
- .price-info {{
274
- max-width: 60%;
275
- }}
276
- .main-price {{
277
- font-size: 36px;
278
- font-weight: 600;
279
- margin-bottom: 5px;
280
- }}
281
- .price-change {{
282
- font-size: 18px;
283
- margin-left: 8px;
284
- font-weight: 500;
285
- }}
286
- .text-green {{ color: #00FF00; }}
287
- .text-red {{ color: #FF5555; }}
288
- .text-blue {{ color: #87CEEB; }}
289
- .text-steel {{ color: #4682B4; }}
290
- .text-purple{{ color: #DDA0DD; }}
291
- .text-gold {{ color: #FFD700; }}
292
-
293
- .sub-stats-row {{
294
- display: flex;
295
- flex-wrap: wrap;
296
- margin-top: 10px;
297
- }}
298
- .stat-item {{
299
- margin-right: 20px;
300
- margin-bottom: 6px;
301
- font-size: 14px;
302
- }}
303
- .side-stats {{
304
- min-width: 220px;
305
- display: flex;
306
- flex-direction: column;
307
- align-items: flex-start;
308
- }}
309
- .side-stats div {{
310
- margin-bottom: 6px;
311
- font-size: 14px;
312
- }}
313
- #chart-container {{
314
- margin-top: 20px;
315
- width: 100%;
316
- height: 600px;
317
- }}
318
- </style>
319
- </head>
320
- <body>
321
- <div class="header-container">
322
- <div class="price-info">
323
- <div class="main-price">
324
- {stats['current_price']:.6f} {stats['symbol']}
325
- <span class="price-change {price_change_class}">
326
- {sign_icon} {abs(stats['change_pct']):.2f}%
327
- </span>
328
- </div>
329
- <div class="sub-stats-row">
330
- <div class="stat-item">
331
- {interval_hours}h High: <span class="text-green">{stats['high']:.6f} {stats['symbol']}</span>
332
- </div>
333
- <div class="stat-item">
334
- {interval_hours}h Low: <span class="text-red">{stats['low']:.6f} {stats['symbol']}</span>
335
- </div>
336
- </div>
337
- </div>
338
- <div class="side-stats">
339
- <div>Supply: <span class="text-blue">{stats['supply']:.2f} {stats['symbol']}</span></div>
340
- <div>Market Cap: <span class="text-steel">{stats['market_cap']:.2f} τ</span></div>
341
- <div>Emission: <span class="text-purple">{stats['emission']:.2f} {stats['symbol']}</span></div>
342
- <div>Stake: <span class="text-gold">{stats['stake']:.2f} {stats['symbol']}</span></div>
343
- </div>
344
- </div>
345
- <div id="chart-container"></div>
346
- <script>
347
- var figData = {fig_json};
348
- Plotly.newPlot('chart-container', figData.data, figData.layout);
349
- </script>
350
- </body>
351
- </html>
352
- """
244
+ fig_json = fig.to_json()
353
245
 
246
+ template = jinja_env.get_template("price-single.j2")
247
+ html_content = template.render(
248
+ fig_json=fig_json,
249
+ stats=stats,
250
+ change_pct=abs(stats["change_pct"]),
251
+ interval_hours=interval_hours,
252
+ title=title,
253
+ )
354
254
  return html_content
355
255
 
356
256
 
357
- def _generate_html_multi_subnet(subnet_data, block_numbers, interval_hours, log_scale):
257
+ def _generate_html_multi_subnet(
258
+ subnet_data, block_numbers, interval_hours, log_scale, title: str
259
+ ):
358
260
  """
359
261
  Generate an HTML chart for multiple subnets.
360
262
  """
@@ -573,143 +475,17 @@ def _generate_html_multi_subnet(subnet_data, block_numbers, interval_hours, log_
573
475
  }
574
476
 
575
477
  fig_json = fig.to_json()
576
- all_visibility_json = json.dumps(all_visibility)
577
- all_annotations_json = json.dumps(all_annotations)
578
-
579
- subnet_modes_json = {}
580
- for netuid, mode_data in subnet_modes.items():
581
- subnet_modes_json[netuid] = {
582
- "visible": json.dumps(mode_data["visible"]),
583
- "annotations": json.dumps(mode_data["annotations"]),
584
- }
585
478
 
586
- # We sort netuids by market cap but for buttons, they are ordered by netuid
587
- sorted_subnet_keys = sorted(subnet_data.keys())
588
- all_button_html = (
589
- '<button class="subnet-button active" onclick="setAll()">All</button>'
479
+ template = jinja_env.get_template("price-multi.j2")
480
+ html_content = template.render(
481
+ title=title,
482
+ # We sort netuids by market cap but for buttons, they are ordered by netuid
483
+ sorted_subnet_keys=sorted(subnet_data.keys()),
484
+ fig_json=fig_json,
485
+ all_visibility=all_visibility,
486
+ all_annotations=all_annotations,
487
+ subnet_modes=subnet_modes,
590
488
  )
591
- subnet_buttons_html = ""
592
- for netuid in sorted_subnet_keys:
593
- subnet_buttons_html += f'<button class="subnet-button" onclick="setSubnet({netuid})">S{netuid}</button> '
594
-
595
- html_content = f"""
596
- <!DOCTYPE html>
597
- <html>
598
- <head>
599
- <title>Multi-Subnet Price Chart</title>
600
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
601
- <style>
602
- body {{
603
- background-color: #000;
604
- color: #fff;
605
- font-family: Arial, sans-serif;
606
- margin: 0;
607
- padding: 20px;
608
- }}
609
- .container {{
610
- display: flex;
611
- flex-direction: column;
612
- gap: 60px;
613
- }}
614
- #multi-subnet-chart {{
615
- width: 90vw;
616
- height: 70vh;
617
- margin-bottom: 40px;
618
- }}
619
- .subnet-buttons {{
620
- display: grid;
621
- grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
622
- gap: 8px;
623
- max-height: 120px;
624
- overflow-y: auto;
625
- padding-right: 10px;
626
- margin-top: 50px;
627
- border-top: 1px solid rgba(255,255,255,0.1);
628
- padding-top: 50px;
629
- position: relative;
630
- bottom: 0;
631
- }}
632
- .subnet-buttons::-webkit-scrollbar {{
633
- width: 8px;
634
- }}
635
- .subnet-buttons::-webkit-scrollbar-track {{
636
- background: rgba(50,50,50,0.3);
637
- border-radius: 4px;
638
- }}
639
- .subnet-buttons::-webkit-scrollbar-thumb {{
640
- background: rgba(100,100,100,0.8);
641
- border-radius: 4px;
642
- }}
643
- .subnet-button {{
644
- background-color: rgba(50,50,50,0.8);
645
- border: 1px solid rgba(70,70,70,0.9);
646
- color: white;
647
- padding: 8px 16px;
648
- cursor: pointer;
649
- border-radius: 4px;
650
- font-size: 14px;
651
- transition: background-color 0.2s;
652
- white-space: nowrap;
653
- overflow: hidden;
654
- text-overflow: ellipsis;
655
- }}
656
- .subnet-button:hover {{
657
- background-color: rgba(70,70,70,0.9);
658
- }}
659
- .subnet-button.active {{
660
- background-color: rgba(100,100,100,0.9);
661
- border-color: rgba(120,120,120,1);
662
- }}
663
- </style>
664
- </head>
665
- <body>
666
- <div class="container">
667
- <div id="multi-subnet-chart"></div>
668
- <div class="subnet-buttons">
669
- {all_button_html}
670
- {subnet_buttons_html}
671
- </div>
672
- </div>
673
- <script>
674
- var figData = {fig_json};
675
- var allVisibility = {all_visibility_json};
676
- var allAnnotations = {all_annotations_json};
677
-
678
- var subnetModes = {json.dumps(subnet_modes_json)};
679
- // parse back to arrays/objects
680
- for (var netuid in subnetModes) {{
681
- subnetModes[netuid].visible = JSON.parse(subnetModes[netuid].visible);
682
- subnetModes[netuid].annotations = JSON.parse(subnetModes[netuid].annotations);
683
- }}
684
-
685
- Plotly.newPlot('multi-subnet-chart', figData.data, figData.layout);
686
-
687
- function clearActiveButtons() {{
688
- document.querySelectorAll('.subnet-button').forEach(btn => btn.classList.remove('active'));
689
- }}
690
-
691
- function setAll() {{
692
- clearActiveButtons();
693
- event.currentTarget.classList.add('active');
694
- Plotly.update('multi-subnet-chart',
695
- {{visible: allVisibility}},
696
- {{annotations: allAnnotations}}
697
- );
698
- }}
699
-
700
- function setSubnet(netuid) {{
701
- clearActiveButtons();
702
- event.currentTarget.classList.add('active');
703
- var mode = subnetModes[netuid];
704
- Plotly.update('multi-subnet-chart',
705
- {{visible: mode.visible}},
706
- {{annotations: mode.annotations}}
707
- );
708
- }}
709
- </script>
710
- </body>
711
- </html>
712
- """
713
489
  return html_content
714
490
 
715
491
 
@@ -720,7 +496,7 @@ async def _generate_html_output(
720
496
  log_scale: bool = False,
721
497
  ):
722
498
  """
723
- Start PyWry and display the price chart in a window.
499
+ Display HTML output in browser
724
500
  """
725
501
  try:
726
502
  subnet_keys = list(subnet_data.keys())
@@ -730,42 +506,34 @@ async def _generate_html_output(
730
506
  netuid = subnet_keys[0]
731
507
  data = subnet_data[netuid]
732
508
  html_content = _generate_html_single_subnet(
733
- netuid, data, block_numbers, interval_hours, log_scale
509
+ netuid,
510
+ data,
511
+ block_numbers,
512
+ interval_hours,
513
+ log_scale,
514
+ title=f"Subnet {netuid} Price View",
734
515
  )
735
- title = f"Subnet {netuid} Price View"
516
+
736
517
  else:
737
518
  # Multi-subnet
738
519
  html_content = _generate_html_multi_subnet(
739
- subnet_data, block_numbers, interval_hours, log_scale
520
+ subnet_data,
521
+ block_numbers,
522
+ interval_hours,
523
+ log_scale,
524
+ title="Subnets Price Chart",
740
525
  )
741
- title = "Subnets Price Chart"
526
+
742
527
  console.print(
743
- "[dark_sea_green3]Opening price chart in a window. Press Ctrl+C to close.[/dark_sea_green3]"
744
- )
745
- handler = PyWry()
746
- handler.send_html(
747
- html=html_content,
748
- title=title,
749
- width=1200,
750
- height=800,
528
+ "[dark_sea_green3]Opening price chart in a window.[/dark_sea_green3]"
751
529
  )
752
- handler.start()
753
- await asyncio.sleep(5)
754
-
755
- # TODO: Improve this logic
756
- try:
757
- while True:
758
- if _has_exited(handler):
759
- break
760
- await asyncio.sleep(1)
761
- except KeyboardInterrupt:
762
- pass
763
- finally:
764
- if not _has_exited(handler):
765
- try:
766
- handler.close()
767
- except Exception:
768
- pass
530
+ with tempfile.NamedTemporaryFile(
531
+ "w", delete=False, suffix=".html"
532
+ ) as dashboard_file:
533
+ url = f"file://{dashboard_file.name}"
534
+ dashboard_file.write(html_content)
535
+
536
+ webbrowser.open(url, new=1)
769
537
  except Exception as e:
770
538
  print_error(f"Error generating price chart: {e}")
771
539
 
@@ -858,12 +626,3 @@ def _generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale):
858
626
  )
859
627
 
860
628
  console.print(stats_text)
861
-
862
-
863
- def _has_exited(handler) -> bool:
864
- """Check if PyWry process has cleanly exited with returncode 0."""
865
- return (
866
- hasattr(handler, "runner")
867
- and handler.runner is not None
868
- and handler.runner.returncode == 0
869
- )
@@ -525,7 +525,8 @@ async def set_take_extrinsic(
525
525
 
526
526
  if current_take_u16 < take_u16:
527
527
  console.print(
528
- 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}%."
528
+ f"Current take is [{COLOR_PALETTE.P.RATE}]{current_take * 100.0:.2f}%[/{COLOR_PALETTE.P.RATE}]. "
529
+ f"Increasing to [{COLOR_PALETTE.P.RATE}]{take * 100:.2f}%."
529
530
  )
530
531
  with console.status(
531
532
  f":satellite: Sending decrease_take_extrinsic call on [white]{subtensor}[/white] ..."
@@ -542,7 +543,8 @@ async def set_take_extrinsic(
542
543
 
543
544
  else:
544
545
  console.print(
545
- 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}%."
546
+ f"Current take is [{COLOR_PALETTE.P.RATE}]{current_take * 100.0:.2f}%[/{COLOR_PALETTE.P.RATE}]. "
547
+ f"Decreasing to [{COLOR_PALETTE.P.RATE}]{take * 100:.2f}%."
546
548
  )
547
549
  with console.status(
548
550
  f":satellite: Sending increase_take_extrinsic call on [white]{subtensor}[/white] ..."
@@ -871,7 +873,7 @@ async def get_current_take(subtensor: "SubtensorInterface", wallet: Wallet):
871
873
  async def display_current_take(subtensor: "SubtensorInterface", wallet: Wallet) -> None:
872
874
  current_take = await get_current_take(subtensor, wallet)
873
875
  console.print(
874
- f"Current take is [{COLOR_PALETTE['POOLS']['RATE']}]{current_take * 100.:.2f}%"
876
+ f"Current take is [{COLOR_PALETTE.P.RATE}]{current_take * 100.0:.2f}%"
875
877
  )
876
878
 
877
879
 
@@ -891,7 +893,9 @@ async def set_take(
891
893
  )
892
894
  if not len(netuids_registered) > 0:
893
895
  err_console.print(
894
- 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."
896
+ f"Hotkey [{COLOR_PALETTE.G.HK}]{wallet.hotkey.ss58_address}[/{COLOR_PALETTE.G.HK}] is not registered to"
897
+ f" any subnet. Please register using [{COLOR_PALETTE.G.SUBHEAD}]`btcli subnets register`"
898
+ f"[{COLOR_PALETTE.G.SUBHEAD}] and try again."
895
899
  )
896
900
  return False
897
901
 
@@ -908,12 +912,12 @@ async def set_take(
908
912
  else:
909
913
  new_take = await get_current_take(subtensor, wallet)
910
914
  console.print(
911
- f"New take is [{COLOR_PALETTE['POOLS']['RATE']}]{new_take * 100.:.2f}%"
915
+ f"New take is [{COLOR_PALETTE.P.RATE}]{new_take * 100.0:.2f}%"
912
916
  )
913
917
  return True
914
918
 
915
919
  console.print(
916
- f"Setting take on [{COLOR_PALETTE['GENERAL']['LINKS']}]network: {subtensor.network}"
920
+ f"Setting take on [{COLOR_PALETTE.G.LINKS}]network: {subtensor.network}"
917
921
  )
918
922
 
919
923
  if not unlock_key(wallet, "hot").success and unlock_key(wallet, "cold").success: