meshtensor-cli 9.18.1__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 (74) hide show
  1. meshtensor_cli/__init__.py +22 -0
  2. meshtensor_cli/cli.py +10742 -0
  3. meshtensor_cli/doc_generation_helper.py +4 -0
  4. meshtensor_cli/src/__init__.py +1085 -0
  5. meshtensor_cli/src/commands/__init__.py +0 -0
  6. meshtensor_cli/src/commands/axon/__init__.py +0 -0
  7. meshtensor_cli/src/commands/axon/axon.py +132 -0
  8. meshtensor_cli/src/commands/crowd/__init__.py +0 -0
  9. meshtensor_cli/src/commands/crowd/contribute.py +621 -0
  10. meshtensor_cli/src/commands/crowd/contributors.py +200 -0
  11. meshtensor_cli/src/commands/crowd/create.py +783 -0
  12. meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
  13. meshtensor_cli/src/commands/crowd/refund.py +233 -0
  14. meshtensor_cli/src/commands/crowd/update.py +418 -0
  15. meshtensor_cli/src/commands/crowd/utils.py +124 -0
  16. meshtensor_cli/src/commands/crowd/view.py +991 -0
  17. meshtensor_cli/src/commands/governance/__init__.py +0 -0
  18. meshtensor_cli/src/commands/governance/governance.py +794 -0
  19. meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
  20. meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
  21. meshtensor_cli/src/commands/liquidity/utils.py +202 -0
  22. meshtensor_cli/src/commands/proxy.py +700 -0
  23. meshtensor_cli/src/commands/stake/__init__.py +0 -0
  24. meshtensor_cli/src/commands/stake/add.py +799 -0
  25. meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
  26. meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
  27. meshtensor_cli/src/commands/stake/claim.py +770 -0
  28. meshtensor_cli/src/commands/stake/list.py +738 -0
  29. meshtensor_cli/src/commands/stake/move.py +1211 -0
  30. meshtensor_cli/src/commands/stake/remove.py +1466 -0
  31. meshtensor_cli/src/commands/stake/wizard.py +323 -0
  32. meshtensor_cli/src/commands/subnets/__init__.py +0 -0
  33. meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
  34. meshtensor_cli/src/commands/subnets/price.py +733 -0
  35. meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
  36. meshtensor_cli/src/commands/sudo.py +1294 -0
  37. meshtensor_cli/src/commands/tc/__init__.py +0 -0
  38. meshtensor_cli/src/commands/tc/tc.py +190 -0
  39. meshtensor_cli/src/commands/treasury/__init__.py +0 -0
  40. meshtensor_cli/src/commands/treasury/treasury.py +194 -0
  41. meshtensor_cli/src/commands/view.py +354 -0
  42. meshtensor_cli/src/commands/wallets.py +2311 -0
  43. meshtensor_cli/src/commands/weights.py +467 -0
  44. meshtensor_cli/src/meshtensor/__init__.py +0 -0
  45. meshtensor_cli/src/meshtensor/balances.py +313 -0
  46. meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
  47. meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
  48. meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
  49. meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
  50. meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
  51. meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
  52. meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
  53. meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
  54. meshtensor_cli/src/meshtensor/minigraph.py +254 -0
  55. meshtensor_cli/src/meshtensor/networking.py +12 -0
  56. meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
  57. meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
  58. meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
  59. meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
  60. meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
  61. meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
  62. meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
  63. meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
  64. meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
  65. meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
  66. meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
  67. meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
  68. meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
  69. meshtensor_cli/src/meshtensor/utils.py +2007 -0
  70. meshtensor_cli/version.py +23 -0
  71. meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
  72. meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
  73. meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
  74. meshtensor_cli-9.18.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,254 @@
1
+ import asyncio
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+
6
+ from meshtensor_cli.src.meshtensor.chain_data import NeuronInfo, SubnetState
7
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
8
+ from meshtensor_cli.src.meshtensor.utils import (
9
+ convert_root_weight_uids_and_vals_to_tensor,
10
+ convert_weight_uids_and_vals_to_tensor,
11
+ convert_bond_uids_and_vals_to_tensor,
12
+ )
13
+
14
+
15
+ class MiniGraph:
16
+ def __init__(
17
+ self,
18
+ netuid: int,
19
+ neurons: list[NeuronInfo],
20
+ meshtensor: "MeshtensorInterface",
21
+ subnet_state: "SubnetState",
22
+ block: int,
23
+ ):
24
+ self.neurons = neurons
25
+ self.netuid = netuid
26
+ self.weights = None
27
+ self.meshtensor = meshtensor
28
+ self.network = meshtensor.network
29
+
30
+ self.axons = [n.axon_info for n in self.neurons]
31
+ self.n = self._create_tensor(len(self.neurons), dtype=np.int64)
32
+ self.block = self._create_tensor(block, dtype=np.int64)
33
+ self.uids = self._create_tensor(
34
+ [neuron.uid for neuron in self.neurons], dtype=np.int64
35
+ )
36
+ self.trust = self._create_tensor(
37
+ [neuron.trust for neuron in self.neurons], dtype=np.float32
38
+ )
39
+ self.consensus = self._create_tensor(
40
+ [neuron.consensus for neuron in self.neurons], dtype=np.float32
41
+ )
42
+ self.incentive = self._create_tensor(
43
+ [neuron.incentive for neuron in self.neurons], dtype=np.float32
44
+ )
45
+ self.dividends = self._create_tensor(
46
+ [neuron.dividends for neuron in self.neurons], dtype=np.float32
47
+ )
48
+ self.ranks = self._create_tensor(
49
+ [neuron.rank for neuron in self.neurons], dtype=np.float32
50
+ )
51
+ self.emission = self._create_tensor(
52
+ [neuron.emission for neuron in self.neurons], dtype=np.float32
53
+ )
54
+ self.active = self._create_tensor(
55
+ [neuron.active for neuron in self.neurons], dtype=np.int64
56
+ )
57
+ self.last_update = self._create_tensor(
58
+ [neuron.last_update for neuron in self.neurons], dtype=np.int64
59
+ )
60
+ self.validator_permit = self._create_tensor(
61
+ [neuron.validator_permit for neuron in self.neurons], dtype=bool
62
+ )
63
+ self.validator_trust = self._create_tensor(
64
+ [neuron.validator_trust for neuron in self.neurons], dtype=np.float32
65
+ )
66
+
67
+ # Fetch stakes from subnet_state until we get updated data in NeuronInfo
68
+ global_stake_list, local_stake_list, stake_weights_list = self._process_stakes(
69
+ neurons, subnet_state
70
+ )
71
+ self.global_stake = self._create_tensor(global_stake_list, dtype=np.float32)
72
+ self.local_stake = self._create_tensor(local_stake_list, dtype=np.float32)
73
+ self.stake_weights = self._create_tensor(stake_weights_list, dtype=np.float32)
74
+
75
+ async def __aenter__(self):
76
+ if not self.weights:
77
+ await self._set_weights_and_bonds()
78
+ return self
79
+
80
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
81
+ pass
82
+
83
+ @property
84
+ def hotkeys(self):
85
+ return [axon.hotkey for axon in self.axons]
86
+
87
+ @staticmethod
88
+ def _create_tensor(data, dtype) -> NDArray:
89
+ """
90
+ Creates a numpy array with the given data and data type. This method is a utility function used internally to encapsulate data into a np.array, making it compatible with the metagraph's numpy model structure.
91
+
92
+ Args:
93
+ data: The data to be included in the tensor. This could be any numeric data, like stakes, ranks, etc.
94
+ dtype: The data type for the tensor, typically a numpy data type like ``np.float32`` or ``np.int64``.
95
+
96
+ Returns:
97
+ A tensor parameter encapsulating the provided data.
98
+
99
+ Internal Usage:
100
+ Used internally to create tensor parameters for various metagraph attributes::
101
+
102
+ self.stake = self._create_tensor(neuron_stakes, dtype=np.float32)
103
+ """
104
+ # TODO: Check and test the creation of tensor
105
+ return np.array(data, dtype=dtype)
106
+
107
+ async def _set_weights_and_bonds(self):
108
+ """
109
+ Computes and sets the weights and bonds for each neuron in the metagraph. This method is responsible for
110
+ processing the raw weight and bond data obtained from the network and converting it into a structured format
111
+ suitable for the metagraph model.
112
+ """
113
+ # TODO: Check and test the computation of weights and bonds
114
+ if self.netuid == 0:
115
+ self.weights = await self._process_root_weights(
116
+ [neuron.weights for neuron in self.neurons], "weights"
117
+ )
118
+ else:
119
+ self.weights = self._process_weights_or_bonds(
120
+ [neuron.weights for neuron in self.neurons], "weights"
121
+ )
122
+ self.bonds = self._process_weights_or_bonds(
123
+ [neuron.bonds for neuron in self.neurons], "bonds"
124
+ )
125
+
126
+ def _process_stakes(
127
+ self,
128
+ neurons: list[NeuronInfo],
129
+ subnet_state: SubnetState,
130
+ ) -> tuple[list[float], list[float], list[float]]:
131
+ """
132
+ Processes the global_stake, local_stake, and stake_weights based on the neuron's hotkey.
133
+
134
+ Args:
135
+ neurons (List[NeuronInfo]): List of neurons.
136
+ subnet_state (SubnetState): The subnet state containing stake information.
137
+
138
+ Returns:
139
+ tuple[list[float], list[float], list[float]]: Lists of global_stake, local_stake, and stake_weights.
140
+ """
141
+ global_stake_list = []
142
+ local_stake_list = []
143
+ stake_weights_list = []
144
+ hotkey_to_index = {
145
+ hotkey: idx for idx, hotkey in enumerate(subnet_state.hotkeys)
146
+ }
147
+
148
+ for neuron in neurons:
149
+ idx = hotkey_to_index.get(neuron.hotkey)
150
+ if idx is not None:
151
+ global_stake_list.append(subnet_state.global_stake[idx].tao)
152
+ local_stake_list.append(subnet_state.local_stake[idx].tao)
153
+ stake_weights_list.append(subnet_state.stake_weight[idx])
154
+ else:
155
+ global_stake_list.append(0.0)
156
+ local_stake_list.append(0.0)
157
+ stake_weights_list.append(0.0)
158
+
159
+ return global_stake_list, local_stake_list, stake_weights_list
160
+
161
+ def _process_weights_or_bonds(self, data, attribute: str) -> NDArray:
162
+ """
163
+ Processes the raw weights or bonds data and converts it into a structured tensor format. This method handles
164
+ the transformation of neuron connection data (`weights` or `bonds`) from a list or other unstructured
165
+ format into a tensor that can be utilized within the metagraph model.
166
+
167
+ :param data: The raw weights or bonds data to be processed. This data typically comes from the meshtensor.
168
+ attribute: A string indicating whether the data is `weights` or `bonds`, which determines the
169
+ specific processing steps to be applied.
170
+
171
+ :return: A tensor parameter encapsulating the processed weights or bonds data.
172
+ """
173
+ data_array = []
174
+ for item in data:
175
+ if len(item) == 0:
176
+ data_array.append(np.zeros(len(self.neurons), dtype=np.float32)) # type: ignore
177
+ else:
178
+ uids, values = zip(*item)
179
+ # TODO: Validate and test the conversion of uids and values to tensor
180
+ if attribute == "weights":
181
+ data_array.append(
182
+ convert_weight_uids_and_vals_to_tensor(
183
+ len(self.neurons),
184
+ list(uids),
185
+ list(values), # type: ignore
186
+ )
187
+ )
188
+ else:
189
+ data_array.append(
190
+ convert_bond_uids_and_vals_to_tensor( # type: ignore
191
+ len(self.neurons), list(uids), list(values)
192
+ ).astype(np.float32)
193
+ )
194
+ tensor_param: NDArray = (
195
+ np.stack(data_array) if len(data_array) else np.array([], dtype=np.float32)
196
+ )
197
+ if len(data_array) == 0:
198
+ # meshtensor.logging.warning(
199
+ # f"Empty {attribute}_array on metagraph.sync(). The '{attribute}' tensor is empty."
200
+ # )
201
+ pass
202
+ return tensor_param
203
+
204
+ async def _process_root_weights(self, data, attribute: str) -> NDArray:
205
+ """
206
+ Specifically processes the root weights data for the metagraph. This method is similar to
207
+ `_process_weights_or_bonds` but is tailored for processing root weights, which have a different structure and
208
+ significance in the network.
209
+
210
+ Args:
211
+ :param data: The raw root weights data to be processed.
212
+ :param attribute: A string indicating the attribute type, here it's typically `weights`.
213
+
214
+ :return: A tensor parameter encapsulating the processed root weights data.
215
+ """
216
+
217
+ async def get_total_subnets():
218
+ _result = await self.meshtensor.query(
219
+ module="MeshtensorModule",
220
+ storage_function="TotalNetworks",
221
+ params=[],
222
+ reuse_block_hash=True,
223
+ )
224
+ return _result
225
+
226
+ async def get_subnets():
227
+ _result = await self.meshtensor.query(
228
+ module="MeshtensorModule",
229
+ storage_function="TotalNetworks",
230
+ )
231
+ return [i for i in range(_result)]
232
+
233
+ data_array = []
234
+ n_subnets, subnets = await asyncio.gather(get_total_subnets(), get_subnets())
235
+ for item in data:
236
+ if len(item) == 0:
237
+ data_array.append(np.zeros(n_subnets, dtype=np.float32)) # type: ignore
238
+ else:
239
+ uids, values = zip(*item)
240
+ data_array.append(
241
+ convert_root_weight_uids_and_vals_to_tensor( # type: ignore
242
+ n_subnets, list(uids), list(values), subnets
243
+ )
244
+ )
245
+
246
+ tensor_param: NDArray = (
247
+ np.stack(data_array) if len(data_array) else np.array([], dtype=np.float32)
248
+ )
249
+ if len(data_array) == 0:
250
+ pass
251
+ # meshtensor.logging.warning(
252
+ # f"Empty {attribute}_array on metagraph.sync(). The '{attribute}' tensor is empty."
253
+ # )
254
+ return tensor_param
@@ -0,0 +1,12 @@
1
+ import netaddr
2
+
3
+
4
+ def int_to_ip(int_val: int) -> str:
5
+ """Maps an integer to a unique ip-string
6
+ :param int_val: The integer representation of an ip. Must be in the range (0, 3.4028237e+38).
7
+
8
+ :return: The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6
9
+
10
+ :raises: netaddr.core.AddrFormatError (Exception): Raised when the passed int_vals is not a valid ip int value.
11
+ """
12
+ return str(netaddr.IPAddress(int_val))
@@ -0,0 +1,24 @@
1
+ <div class="filters-section">
2
+ <div class="search-box">
3
+ <input type="text" id="subnet-search" placeholder="search for name, or netuid..." onkeyup="filterSubnets()">
4
+ </div>
5
+ <div class="filter-toggles">
6
+ <label>
7
+ <input type="checkbox" id="show-verbose" onchange="toggleVerboseNumbers()">
8
+ Precise Numbers
9
+ </label>
10
+ <label>
11
+ <input type="checkbox" id="show-staked" onchange="filterSubnets()">
12
+ Show Only Staked
13
+ </label>
14
+ <label>
15
+ <input type="checkbox" id="show-tiles" onchange="toggleTileView()" checked>
16
+ Tile View
17
+ </label>
18
+ <label class="disabled-label" title="Coming soon">
19
+ <input type="checkbox" id="live-mode" disabled>
20
+ Live Mode (coming soon)
21
+ </label>
22
+ </div>
23
+ </div>
24
+ <div id="subnet-tiles-container" class="subnet-tiles-container"></div>
@@ -0,0 +1,36 @@
1
+ {#
2
+ vars:
3
+ wallet_info.coldkey, truncated_coldkey, wallet_info.balance, root_symbol_html, wallet_info.total_ideal_stake_value,
4
+ slippage_percentage, wallet_info.total_slippage_value, block_number
5
+ #}
6
+
7
+ <div class="header">
8
+ <meta charset="UTF-8">
9
+ <div class="wallet-info">
10
+ <span class="wallet-name">{{ wallet_info.name }}</span>
11
+ <div class="wallet-address-container" onclick="copyToClipboard('{{ coldkey }}', this)">
12
+ <span class="wallet-address" title="Click to copy">{{ truncated_coldkey }}}</span>
13
+ <span class="copy-indicator">Copy</span>
14
+ </div>
15
+ </div>
16
+ <div class="stake-metrics">
17
+ <div class="stake-metric">
18
+ <span class="metric-label">Block</span>
19
+ <span class="metric-value" style="color: #FF9900;">{{ block_number }}</span>
20
+ </div>
21
+ <div class="stake-metric">
22
+ <span class="metric-label">Balance</span>
23
+ <span class="metric-value">{{ "%.4f"|format(wallet_info.balance) }} {{ root_symbol_html }}</span>
24
+ </div>
25
+ <div class="stake-metric">
26
+ <span class="metric-label">Total Stake Value</span>
27
+ <span class="metric-value">{{ "%.4f"|format(wallet_info.total_ideal_stake_value) }} {{ root_symbol_html }}</span>
28
+ </div>
29
+ <div class="stake-metric">
30
+ <span class="metric-label">Slippage Impact</span>
31
+ <span class="metric-value slippage-value">
32
+ {{ "%.2f"|format(slippage_percentage) }}% <span class="slippage-detail">({{ "%.4f"|format(wallet_info.total_slippage_value) }} {{ root_symbol_html }})</span>
33
+ </span>
34
+ </div>
35
+ </div>
36
+ </div>
@@ -0,0 +1,111 @@
1
+ <div id="neuron-detail-container" style="display: none;">
2
+ <div class="neuron-detail-header">
3
+ <button class="back-button neuron-detail-back" onclick="closeNeuronDetails()">&larr; Back</button>
4
+ </div>
5
+ <div class="neuron-detail-content">
6
+ <div class="neuron-info-top">
7
+ <h2 class="neuron-name" id="neuron-name"></h2>
8
+ <div class="neuron-keys">
9
+ <div class="hotkey-label">
10
+ <span style="color: #FF9900;">Hotkey:</span>
11
+ <span id="neuron-hotkey" class="truncated-address"></span>
12
+ </div>
13
+ <div class="coldkey-label">
14
+ <span style="color: #FF9900;">Coldkey:</span>
15
+ <span id="neuron-coldkey" class="truncated-address"></span>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ <div class="neuron-cards-container">
20
+ <!-- First row: Stakes, Dividends, Incentive, Emissions -->
21
+ <div class="neuron-metrics-row">
22
+ <div class="metric-card">
23
+ <div class="metric-label">Stake Weight</div>
24
+ <div id="neuron-stake-total" class="metric-value formatted-number"
25
+ data-value="0" data-symbol=""></div>
26
+ </div>
27
+
28
+ <div class="metric-card">
29
+ <div class="metric-label">Stake (Alpha)</div>
30
+ <div id="neuron-stake-token" class="metric-value formatted-number"
31
+ data-value="0" data-symbol=""></div>
32
+ </div>
33
+
34
+ <div class="metric-card">
35
+ <div class="metric-label">Stake (Root)</div>
36
+ <div id="neuron-stake-root" class="metric-value formatted-number"
37
+ data-value="0" data-symbol="&#x03C4;"></div>
38
+ </div>
39
+
40
+ <div class="metric-card">
41
+ <div class="metric-label">Dividends</div>
42
+ <div id="neuron-dividends" class="metric-value formatted-number"
43
+ data-value="0" data-symbol=""></div>
44
+ </div>
45
+
46
+ <div class="metric-card">
47
+ <div class="metric-label">Incentive</div>
48
+ <div id="neuron-incentive" class="metric-value formatted-number"
49
+ data-value="0" data-symbol=""></div>
50
+ </div>
51
+
52
+ <div class="metric-card">
53
+ <div class="metric-label">Emissions</div>
54
+ <div id="neuron-emissions" class="metric-value formatted-number"
55
+ data-value="0" data-symbol=""></div>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- Second row: Rank, Trust, Pruning Score, Validator Permit, Consensus, Last Update -->
60
+ <div class="neuron-metrics-row">
61
+ <div class="metric-card">
62
+ <div class="metric-label">Rank</div>
63
+ <div id="neuron-rank" class="metric-value"></div>
64
+ </div>
65
+
66
+ <div class="metric-card">
67
+ <div class="metric-label">Trust</div>
68
+ <div id="neuron-trust" class="metric-value"></div>
69
+ </div>
70
+
71
+ <div class="metric-card">
72
+ <div class="metric-label">Pruning Score</div>
73
+ <div id="neuron-pruning-score" class="metric-value"></div>
74
+ </div>
75
+
76
+ <div class="metric-card">
77
+ <div class="metric-label">Validator Permit</div>
78
+ <div id="neuron-validator-permit" class="metric-value"></div>
79
+ </div>
80
+
81
+ <div class="metric-card">
82
+ <div class="metric-label">Consensus</div>
83
+ <div id="neuron-consensus" class="metric-value"></div>
84
+ </div>
85
+
86
+ <div class="metric-card">
87
+ <div class="metric-label">Last Update</div>
88
+ <div id="neuron-last-update" class="metric-value"></div>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Third row: Reg Block, IP Info, Active -->
93
+ <div class="neuron-metrics-row last-row">
94
+ <div class="metric-card">
95
+ <div class="metric-label">Reg Block</div>
96
+ <div id="neuron-reg-block" class="metric-value"></div>
97
+ </div>
98
+
99
+ <div class="metric-card">
100
+ <div class="metric-label">IP Info</div>
101
+ <div id="neuron-ipinfo" class="metric-value"></div>
102
+ </div>
103
+
104
+ <div class="metric-card">
105
+ <div class="metric-label">Active</div>
106
+ <div id="neuron-active" class="metric-value"></div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{ title }}</title>
5
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
6
+ <style>
7
+ body {
8
+ background-color: #000;
9
+ color: #fff;
10
+ font-family: Arial, sans-serif;
11
+ margin: 0;
12
+ padding: 20px;
13
+ }
14
+ .container {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 60px;
18
+ }
19
+ #multi-subnet-chart {
20
+ width: 90vw;
21
+ height: 70vh;
22
+ margin-bottom: 40px;
23
+ }
24
+ .subnet-buttons {
25
+ display: grid;
26
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
27
+ gap: 8px;
28
+ max-height: 120px;
29
+ overflow-y: auto;
30
+ padding-right: 10px;
31
+ margin-top: 50px;
32
+ border-top: 1px solid rgba(255,255,255,0.1);
33
+ padding-top: 50px;
34
+ position: relative;
35
+ bottom: 0;
36
+ }
37
+ .subnet-buttons::-webkit-scrollbar {
38
+ width: 8px;
39
+ }
40
+ .subnet-buttons::-webkit-scrollbar-track {
41
+ background: rgba(50,50,50,0.3);
42
+ border-radius: 4px;
43
+ }
44
+ .subnet-buttons::-webkit-scrollbar-thumb {
45
+ background: rgba(100,100,100,0.8);
46
+ border-radius: 4px;
47
+ }
48
+ .subnet-button {
49
+ background-color: rgba(50,50,50,0.8);
50
+ border: 1px solid rgba(70,70,70,0.9);
51
+ color: white;
52
+ padding: 8px 16px;
53
+ cursor: pointer;
54
+ border-radius: 4px;
55
+ font-size: 14px;
56
+ transition: background-color 0.2s;
57
+ white-space: nowrap;
58
+ overflow: hidden;
59
+ text-overflow: ellipsis;
60
+ }
61
+ .subnet-button:hover {
62
+ background-color: rgba(70,70,70,0.9);
63
+ }
64
+ .subnet-button.active {
65
+ background-color: rgba(100,100,100,0.9);
66
+ border-color: rgba(120,120,120,1);
67
+ }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <div class="container">
72
+ <div id="multi-subnet-chart"></div>
73
+ <div class="subnet-buttons">
74
+ <button class="subnet-button active" onclick="setAll()">All</button>
75
+ {% for netuid in sorted_subnet_keys %}
76
+ <button class="subnet-button" onclick="setSubnet({{ netuid }})">S{{ netuid }}</button>
77
+ {% endfor %}
78
+ </div>
79
+ </div>
80
+ <script>
81
+ const figData = {{ fig_json|safe }};
82
+ const allVisibility = {{ all_visibility|tojson|safe }};
83
+ const allAnnotations = {{ all_annotations|tojson|safe }};
84
+
85
+ const subnetModes = {{ subnet_modes|tojson|safe }};
86
+
87
+ Plotly.newPlot('multi-subnet-chart', figData.data, figData.layout);
88
+
89
+ function clearActiveButtons() {
90
+ document.querySelectorAll('.subnet-button').forEach(btn => btn.classList.remove('active'));
91
+ }
92
+
93
+ function setAll() {
94
+ clearActiveButtons();
95
+ event.currentTarget.classList.add('active');
96
+ Plotly.update('multi-subnet-chart',
97
+ {visible: allVisibility},
98
+ {annotations: allAnnotations}
99
+ );
100
+ }
101
+
102
+ function setSubnet(netuid) {
103
+ clearActiveButtons();
104
+ event.currentTarget.classList.add('active');
105
+ const mode = subnetModes[netuid];
106
+ Plotly.update('multi-subnet-chart',
107
+ {visible: mode.visible},
108
+ {annotations: mode.annotations}
109
+ );
110
+ }
111
+ </script>
112
+ </body>
113
+ </html>
@@ -0,0 +1,99 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>{{ title }}</title>
6
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
7
+ <style>
8
+ body {
9
+ background-color: #000;
10
+ color: #fff;
11
+ font-family: Arial, sans-serif;
12
+ margin: 0;
13
+ padding: 20px;
14
+ }
15
+ .header-container {
16
+ display: flex;
17
+ align-items: flex-start;
18
+ justify-content: space-between;
19
+ margin-bottom: 20px;
20
+ }
21
+ .price-info {
22
+ max-width: 60%;
23
+ }
24
+ .main-price {
25
+ font-size: 36px;
26
+ font-weight: 600;
27
+ margin-bottom: 5px;
28
+ }
29
+ .price-change {
30
+ font-size: 18px;
31
+ margin-left: 8px;
32
+ font-weight: 500;
33
+ }
34
+ .text-green { color: #00FF00; }
35
+ .text-red { color: #FF5555; }
36
+ .text-blue { color: #87CEEB; }
37
+ .text-steel { color: #4682B4; }
38
+ .text-purple{ color: #DDA0DD; }
39
+ .text-gold { color: #FFD700; }
40
+
41
+ .sub-stats-row {
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ margin-top: 10px;
45
+ }
46
+ .stat-item {
47
+ margin-right: 20px;
48
+ margin-bottom: 6px;
49
+ font-size: 14px;
50
+ }
51
+ .side-stats {
52
+ min-width: 220px;
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: flex-start;
56
+ }
57
+ .side-stats div {
58
+ margin-bottom: 6px;
59
+ font-size: 14px;
60
+ }
61
+ #chart-container {
62
+ margin-top: 20px;
63
+ width: 100%;
64
+ height: 600px;
65
+ }
66
+ </style>
67
+ </head>
68
+ <body>
69
+ <div class="header-container">
70
+ <div class="price-info">
71
+ <div class="main-price">
72
+ {{ "%.6f"|format(stats.current_price) }} {{ stats.symbol }}
73
+ <span class="price-change {{ "text-green" if stats.change_pct > 0 else "text-red" }}">
74
+ {{ "▲" if stats.change_pct > 0 else "▼" }} {{ "%.2f"|format(change_pct) }}%
75
+ </span>
76
+ </div>
77
+ <div class="sub-stats-row">
78
+ <div class="stat-item">
79
+ {{ interval_hours }}h High: <span class="text-green">{{ "%.6f"|format(stats.high) }} {{ stats.symbol }}</span>
80
+ </div>
81
+ <div class="stat-item">
82
+ {{ interval_hours }}h Low: <span class="text-red">{{ "%.6f"|format(stats.low) }} {{ stats.symbol }}</span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <div class="side-stats">
87
+ <div>Supply: <span class="text-blue">{{ "%.2f"|format(stats.supply) }} {{ stats.symbol }}</span></div>
88
+ <div>Market Cap: <span class="text-steel">{{ "%.2f"|format(stats.market_cap) }} τ</span></div>
89
+ <div>Emission: <span class="text-purple">{{ "%.2f"|format(stats.emission) }} {{ stats.symbol }}</span></div>
90
+ <div>Stake: <span class="text-gold">{{ "%.2f"|format(stats.stake) }} {{ stats.symbol }}</span></div>
91
+ </div>
92
+ </div>
93
+ <div id="chart-container"></div>
94
+ <script>
95
+ var figData = {{ fig_json|safe }};
96
+ Plotly.newPlot('chart-container', figData.data, figData.layout);
97
+ </script>
98
+ </body>
99
+ </html>