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,2908 @@
1
+ import asyncio
2
+ import json
3
+ import sqlite3
4
+ from typing import TYPE_CHECKING, Optional, cast
5
+
6
+ from async_substrate_interface import AsyncExtrinsicReceipt
7
+ from async_substrate_interface.utils.storage import StorageKey
8
+ from meshtensor_wallet import Wallet
9
+ from rich.prompt import Prompt
10
+ from rich.console import Group
11
+ from rich.progress import Progress, BarColumn, TextColumn
12
+ from rich.table import Column, Table
13
+ from rich import box
14
+
15
+ from meshtensor_cli.src import COLOR_PALETTE
16
+ from meshtensor_cli.src.meshtensor.balances import Balance
17
+ from meshtensor_cli.src.meshtensor.extrinsics.registration import (
18
+ register_extrinsic,
19
+ burned_register_extrinsic,
20
+ )
21
+ from meshtensor_cli.src.meshtensor.extrinsics.root import root_register_extrinsic
22
+ from meshtensor_cli.src.meshtensor.extrinsics.mev_shield import (
23
+ extract_mev_shield_id,
24
+ wait_for_extrinsic_by_hash,
25
+ )
26
+ from rich.live import Live
27
+ from meshtensor_cli.src.meshtensor.minigraph import MiniGraph
28
+ from meshtensor_cli.src.commands.wallets import set_id, get_id
29
+ from meshtensor_cli.src.meshtensor.utils import (
30
+ confirm_action,
31
+ console,
32
+ create_and_populate_table,
33
+ print_success,
34
+ print_verbose,
35
+ print_error,
36
+ get_metadata_table,
37
+ millify_tao,
38
+ render_table,
39
+ update_metadata_table,
40
+ prompt_for_identity,
41
+ get_subnet_name,
42
+ unlock_key,
43
+ blocks_to_duration,
44
+ json_console,
45
+ get_hotkey_pub_ss58,
46
+ print_extrinsic_id,
47
+ check_img_mimetype,
48
+ )
49
+
50
+ if TYPE_CHECKING:
51
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
52
+
53
+ MESH_WEIGHT = 0.18
54
+
55
+ # helpers and extrinsics
56
+
57
+
58
+ def format_claim_type_for_root(claim_info: dict, total_subnets: int) -> str:
59
+ """
60
+ Format claim type for root network metagraph.
61
+
62
+ Args:
63
+ claim_info: Claim type dict {"type": "...", "subnets": [...]}
64
+ total_subnets: Total number of subnets in network (excluding netuid 0)
65
+
66
+ Returns:
67
+ Formatted string showing keep/swap counts
68
+
69
+ Examples:
70
+ {"type": "Keep"} → "Keep all"
71
+ {"type": "Swap"} → "Swap all"
72
+ {"type": "KeepSubnets", "subnets": [1,2,3]} → "Keep (3), Swap (54)"
73
+ """
74
+ claim_type = claim_info.get("type", "Swap")
75
+
76
+ if claim_type == "Keep":
77
+ return "Keep all"
78
+ elif claim_type == "Swap":
79
+ return "Swap all"
80
+ else:
81
+ keep_subnets = claim_info.get("subnets", [])
82
+ keep_count = len(keep_subnets)
83
+ swap_count = total_subnets - keep_count
84
+ return f"Keep ({keep_count}), Swap ({swap_count})"
85
+
86
+
87
+ def format_claim_type_for_subnet(claim_info: dict, current_netuid: int) -> str:
88
+ """
89
+ Format claim type for specific subnet metagraph.
90
+ Shows whether THIS subnet's emissions are kept or swapped.
91
+
92
+ Args:
93
+ claim_info: Claim type dict {"type": "...", "subnets": [...]}
94
+ current_netuid: The netuid being viewed
95
+
96
+ Returns:
97
+ "Keep" if this subnet is kept, "Swap" if swapped
98
+
99
+ Examples:
100
+ {"type": "Keep"}, netuid=5 → "Keep"
101
+ {"type": "Swap"}, netuid=5 → "Swap"
102
+ {"type": "KeepSubnets", "subnets": [1,5,10]}, netuid=5 → "Keep"
103
+ {"type": "KeepSubnets", "subnets": [1,5,10]}, netuid=3 → "Swap"
104
+ """
105
+ claim_type = claim_info.get("type", "Swap")
106
+
107
+ if claim_type == "Keep":
108
+ return "Keep"
109
+ elif claim_type == "Swap":
110
+ return "Swap"
111
+ else:
112
+ keep_subnets = claim_info.get("subnets", [])
113
+ return "Keep" if current_netuid in keep_subnets else "Swap"
114
+
115
+
116
+ async def register_subnetwork_extrinsic(
117
+ meshtensor: "MeshtensorInterface",
118
+ wallet: Wallet,
119
+ subnet_identity: dict,
120
+ proxy: Optional[str],
121
+ wait_for_inclusion: bool = False,
122
+ wait_for_finalization: bool = True,
123
+ prompt: bool = False,
124
+ decline: bool = False,
125
+ quiet: bool = False,
126
+ mev_protection: bool = True,
127
+ ) -> tuple[bool, Optional[int], Optional[str]]:
128
+ """Registers a new subnetwork.
129
+
130
+ wallet (meshtensor.wallet):
131
+ meshtensor wallet object.
132
+ wait_for_inclusion (bool):
133
+ If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout.
134
+ wait_for_finalization (bool):
135
+ If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout.
136
+ prompt (bool):
137
+ If true, the call waits for confirmation from the user before proceeding.
138
+ Returns:
139
+ tuple including:
140
+ success: Flag is `True` if extrinsic was finalized or included in the block.
141
+ If we did not wait for finalization/inclusion, the response is `True`.
142
+ error_message: Optional error message.
143
+ extrinsic_identifier: Optional extrinsic identifier, if the extrinsic was included.
144
+ """
145
+
146
+ # TODO why doesn't this have an era?
147
+ async def _find_event_attributes_in_extrinsic_receipt(
148
+ response_: AsyncExtrinsicReceipt, event_name: str
149
+ ) -> list:
150
+ """
151
+ Searches for the attributes of a specified event within an extrinsic receipt.
152
+
153
+ :param response_: The receipt of the extrinsic to be searched.
154
+ :param event_name: The name of the event to search for.
155
+
156
+ :return: A list of attributes for the specified event. Returns [-1] if the event is not found.
157
+ """
158
+ for event in await response_.triggered_events:
159
+ # Access the event details
160
+ event_details = event["event"]
161
+ # Check if the event_id is 'NetworkAdded'
162
+ if event_details["event_id"] == event_name:
163
+ # Once found, you can access the attributes of the event_name
164
+ return event_details["attributes"]
165
+ return []
166
+
167
+ print_verbose("Fetching balance")
168
+
169
+ your_balance = await meshtensor.get_balance(proxy or wallet.coldkeypub.ss58_address)
170
+
171
+ print_verbose("Fetching burn_cost")
172
+ sn_burn_cost = await burn_cost(meshtensor)
173
+ if sn_burn_cost > your_balance:
174
+ print_error(
175
+ f"Your balance of: [{COLOR_PALETTE.POOLS.MESH}]{your_balance}[{COLOR_PALETTE.POOLS.MESH}]"
176
+ f" is not enough to burn "
177
+ f"[{COLOR_PALETTE.POOLS.MESH}]{sn_burn_cost}[{COLOR_PALETTE.POOLS.MESH}] "
178
+ f"to register a subnet."
179
+ )
180
+ return False, None, None
181
+
182
+ if prompt:
183
+ console.print(
184
+ f"Your balance is: [{COLOR_PALETTE['POOLS']['MESH']}]{your_balance}"
185
+ )
186
+ if not confirm_action(
187
+ f"Do you want to burn [{COLOR_PALETTE['POOLS']['MESH']}]{sn_burn_cost} to register a subnet?",
188
+ decline=decline,
189
+ quiet=quiet,
190
+ ):
191
+ return False, None, None
192
+
193
+ call_params = {
194
+ "hotkey": get_hotkey_pub_ss58(wallet),
195
+ "mechid": 1,
196
+ }
197
+ call_function = "register_network"
198
+
199
+ has_identity = any(subnet_identity.values())
200
+ if has_identity:
201
+ identity_data = {
202
+ "subnet_name": subnet_identity["subnet_name"].encode()
203
+ if subnet_identity.get("subnet_name")
204
+ else b"",
205
+ "github_repo": subnet_identity["github_repo"].encode()
206
+ if subnet_identity.get("github_repo")
207
+ else b"",
208
+ "subnet_contact": subnet_identity["subnet_contact"].encode()
209
+ if subnet_identity.get("subnet_contact")
210
+ else b"",
211
+ "subnet_url": subnet_identity["subnet_url"].encode()
212
+ if subnet_identity.get("subnet_url")
213
+ else b"",
214
+ "discord": subnet_identity["discord"].encode()
215
+ if subnet_identity.get("discord")
216
+ else b"",
217
+ "description": subnet_identity["description"].encode()
218
+ if subnet_identity.get("description")
219
+ else b"",
220
+ "logo_url": subnet_identity["logo_url"].encode()
221
+ if subnet_identity.get("logo_url")
222
+ else b"",
223
+ "additional": subnet_identity["additional"].encode()
224
+ if subnet_identity.get("additional")
225
+ else b"",
226
+ }
227
+ call_params["identity"] = identity_data
228
+ call_function = "register_network_with_identity"
229
+ for field, value in identity_data.items():
230
+ max_size = 64 # bytes
231
+ if len(value) > max_size:
232
+ print_error(
233
+ f"Error: Identity field [white]{field}[/white] must be <= {max_size} bytes.\n"
234
+ f"Value '{value.decode()}' is {len(value)} bytes."
235
+ )
236
+ return False, None, None
237
+
238
+ if not unlock_key(wallet).success:
239
+ return False, None, None
240
+
241
+ coldkey_ss58 = wallet.coldkeypub.ss58_address
242
+
243
+ with console.status(":satellite: Registering subnet...", spinner="earth") as status:
244
+ substrate = meshtensor.substrate
245
+ call, next_nonce = await asyncio.gather(
246
+ substrate.compose_call(
247
+ call_module="MeshtensorModule",
248
+ call_function=call_function,
249
+ call_params=call_params,
250
+ ),
251
+ substrate.get_account_next_index(coldkey_ss58),
252
+ )
253
+ success, err_msg, response = await meshtensor.sign_and_send_extrinsic(
254
+ call=call,
255
+ wallet=wallet,
256
+ wait_for_inclusion=wait_for_inclusion,
257
+ wait_for_finalization=wait_for_finalization,
258
+ proxy=proxy,
259
+ nonce=next_nonce,
260
+ mev_protection=mev_protection,
261
+ )
262
+
263
+ # We only wait here if we expect finalization.
264
+ if not wait_for_finalization and not wait_for_inclusion:
265
+ return True, None, None
266
+
267
+ if not success:
268
+ print_error(f"Failed: {err_msg}")
269
+ return False, None, None
270
+ else:
271
+ # Check for MEV shield execution
272
+ if mev_protection:
273
+ inner_hash = err_msg
274
+ mev_shield_id = await extract_mev_shield_id(response)
275
+ mev_success, mev_error, response = await wait_for_extrinsic_by_hash(
276
+ meshtensor=meshtensor,
277
+ extrinsic_hash=inner_hash,
278
+ shield_id=mev_shield_id,
279
+ submit_block_hash=response.block_hash,
280
+ status=status,
281
+ )
282
+ if not mev_success:
283
+ status.stop()
284
+ print_error(f"Failed: MEV execution failed: {mev_error}")
285
+ return False, None, None
286
+
287
+ # Successful registration, final check for membership
288
+
289
+ attributes = await _find_event_attributes_in_extrinsic_receipt(
290
+ response, "NetworkAdded"
291
+ )
292
+ await print_extrinsic_id(response)
293
+ ext_id = await response.get_extrinsic_identifier()
294
+ if not attributes:
295
+ console.print(
296
+ ":exclamation: [yellow]A possible error has occurred[/yellow]. The extrinsic reports success, but "
297
+ "we are unable to locate the 'NetworkAdded' event inside the extrinsic's events."
298
+ ""
299
+ )
300
+ else:
301
+ print_success(
302
+ f"[dark_sea_green3]Registered subnetwork with netuid: {attributes[0]}"
303
+ )
304
+ return True, int(attributes[0]), ext_id
305
+
306
+
307
+ # commands
308
+
309
+
310
+ async def subnets_list(
311
+ meshtensor: "MeshtensorInterface",
312
+ reuse_last: bool,
313
+ html_output: bool,
314
+ no_cache: bool,
315
+ verbose: bool,
316
+ live: bool,
317
+ json_output: bool,
318
+ ):
319
+ """List all subnet netuids in the network."""
320
+
321
+ async def fetch_subnet_data():
322
+ block_hash = await meshtensor.substrate.get_chain_head()
323
+ subnets_, mechanisms, block_number_, ema_tao_inflow = await asyncio.gather(
324
+ meshtensor.all_subnets(block_hash=block_hash),
325
+ meshtensor.get_all_subnet_mechanisms(block_hash=block_hash),
326
+ meshtensor.substrate.get_block_number(block_hash=block_hash),
327
+ meshtensor.get_all_subnet_ema_tao_inflow(block_hash=block_hash),
328
+ )
329
+
330
+ # Sort subnets by market cap, keeping the root subnet in the first position
331
+ root_subnet = next(s for s in subnets_ if s.netuid == 0)
332
+ other_subnets = sorted(
333
+ [s for s in subnets_ if s.netuid != 0],
334
+ key=lambda x: (x.alpha_in.tao + x.alpha_out.tao) * x.price.tao,
335
+ reverse=True,
336
+ )
337
+ sorted_subnets = [root_subnet] + other_subnets
338
+ return sorted_subnets, block_number_, mechanisms, ema_tao_inflow
339
+
340
+ def calculate_emission_stats(
341
+ subnets_: list, block_number_: int
342
+ ) -> tuple[Balance, str]:
343
+ # We do not include the root subnet in the emission calculation
344
+ total_tao_emitted = sum(
345
+ subnet.tao_in.tao for subnet in subnets_ if subnet.netuid != 0
346
+ )
347
+ emission_percentage = (total_tao_emitted / block_number_) * 100
348
+ percentage_color = "dark_sea_green" if emission_percentage < 100 else "red"
349
+ formatted_percentage = (
350
+ f"[{percentage_color}]{emission_percentage:.2f}%[/{percentage_color}]"
351
+ )
352
+ if not verbose:
353
+ percentage_string = f"τ {millify_tao(total_tao_emitted)}/{millify_tao(block_number_)} ({formatted_percentage})"
354
+ else:
355
+ percentage_string = (
356
+ f"τ {total_tao_emitted:.1f}/{block_number_} ({formatted_percentage})"
357
+ )
358
+ return total_tao_emitted, percentage_string
359
+
360
+ def define_table(
361
+ total_emissions: float,
362
+ total_rate: float,
363
+ total_netuids: int,
364
+ tao_emission_percentage: str,
365
+ total_tao_flow_ema: float,
366
+ ):
367
+ defined_table = Table(
368
+ title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnets"
369
+ f"\nNetwork: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{meshtensor.network}\n\n",
370
+ show_footer=True,
371
+ show_edge=False,
372
+ header_style="bold white",
373
+ border_style="bright_black",
374
+ style="bold",
375
+ title_justify="center",
376
+ show_lines=False,
377
+ pad_edge=True,
378
+ )
379
+
380
+ defined_table.add_column(
381
+ "[bold white]Netuid",
382
+ style="grey89",
383
+ justify="center",
384
+ footer=str(total_netuids),
385
+ )
386
+ defined_table.add_column("[bold white]Name", style="cyan", justify="left")
387
+ defined_table.add_column(
388
+ f"[bold white]Price \n({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)",
389
+ style="dark_sea_green2",
390
+ justify="left",
391
+ footer=f"τ {total_rate}",
392
+ )
393
+ defined_table.add_column(
394
+ f"[bold white]Market Cap \n({Balance.get_unit(1)} * Price)",
395
+ style="steel_blue3",
396
+ justify="left",
397
+ )
398
+ defined_table.add_column(
399
+ f"[bold white]Emission ({Balance.get_unit(0)})",
400
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
401
+ justify="left",
402
+ footer=f"τ {total_emissions}",
403
+ )
404
+ defined_table.add_column(
405
+ f"[bold white]Net Inflow EMA ({Balance.get_unit(0)})",
406
+ style=COLOR_PALETTE["POOLS"]["ALPHA_OUT"],
407
+ justify="left",
408
+ footer=f"τ {total_tao_flow_ema}",
409
+ )
410
+ defined_table.add_column(
411
+ f"[bold white]P ({Balance.get_unit(0)}_in, {Balance.get_unit(1)}_in)",
412
+ style=COLOR_PALETTE["STAKE"]["MESH"],
413
+ justify="left",
414
+ footer=f"{tao_emission_percentage}",
415
+ )
416
+ defined_table.add_column(
417
+ f"[bold white]Stake ({Balance.get_unit(1)}_out)",
418
+ style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
419
+ justify="left",
420
+ )
421
+ defined_table.add_column(
422
+ f"[bold white]Supply ({Balance.get_unit(1)})",
423
+ style=COLOR_PALETTE["POOLS"]["ALPHA_IN"],
424
+ justify="left",
425
+ )
426
+
427
+ defined_table.add_column(
428
+ "[bold white]Tempo (k/n)",
429
+ style=COLOR_PALETTE["GENERAL"]["TEMPO"],
430
+ justify="left",
431
+ overflow="fold",
432
+ )
433
+ defined_table.add_column(
434
+ "[bold white]Mechanisms",
435
+ style=COLOR_PALETTE["GENERAL"]["SUBHEADING_EXTRA_1"],
436
+ justify="center",
437
+ )
438
+ return defined_table
439
+
440
+ # Non-live mode
441
+ def _create_table(subnets_, block_number_, mechanisms, ema_tao_inflow):
442
+ rows = []
443
+ _, percentage_string = calculate_emission_stats(subnets_, block_number_)
444
+
445
+ for subnet in subnets_:
446
+ netuid = subnet.netuid
447
+ # The default symbols for 123 and 124 are visually identical:
448
+ # 123: 𑀀
449
+ # 124: 𑀁
450
+ # and the symbol for 125 is basically a colon
451
+ # 125: 𑀂
452
+ # however, because they're in Brahmi, which very few fonts support, they don't render properly
453
+ # This patches them.
454
+ replacements = {69632: "˙", 69633: "˙", 69634: ":"}
455
+ if (sso := ord(subnet.symbol)) in replacements.keys():
456
+ subnet.symbol = replacements[sso]
457
+
458
+ symbol = f"{subnet.symbol}\u200e"
459
+
460
+ if netuid == 0:
461
+ emission_tao = 0.0
462
+ else:
463
+ emission_tao = subnet.tao_in_emission.tao
464
+
465
+ alpha_in_value = (
466
+ f"{millify_tao(subnet.alpha_in.tao)}"
467
+ if not verbose
468
+ else f"{subnet.alpha_in.tao:,.4f}"
469
+ )
470
+ alpha_out_value = (
471
+ f"{millify_tao(subnet.alpha_out.tao)}"
472
+ if not verbose
473
+ else f"{subnet.alpha_out.tao:,.4f}"
474
+ )
475
+ price_value = f"{subnet.price.tao:,.4f}"
476
+
477
+ # Market Cap
478
+ market_cap = (subnet.alpha_in.tao + subnet.alpha_out.tao) * subnet.price.tao
479
+ market_cap_value = (
480
+ f"{millify_tao(market_cap)}" if not verbose else f"{market_cap:,.4f}"
481
+ )
482
+
483
+ # Liquidity
484
+ tao_in_cell = (
485
+ (
486
+ f"τ {millify_tao(subnet.tao_in.tao)}"
487
+ if not verbose
488
+ else f"τ {subnet.tao_in.tao:,.4f}"
489
+ )
490
+ if netuid != 0
491
+ else "-"
492
+ )
493
+ alpha_in_cell = f"{alpha_in_value} {symbol}" if netuid != 0 else "-"
494
+ liquidity_cell = f"{tao_in_cell}, {alpha_in_cell}"
495
+
496
+ # Supply
497
+ supply = subnet.alpha_in.tao + subnet.alpha_out.tao
498
+ supply_value = f"{millify_tao(supply)}" if not verbose else f"{supply:,.4f}"
499
+
500
+ # Prepare cells
501
+ netuid_cell = str(netuid)
502
+ subnet_name_cell = (
503
+ f"[{COLOR_PALETTE.G.SYM}]{subnet.symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE.G.SYM}]"
504
+ f" {get_subnet_name(subnet)}"
505
+ )
506
+ emission_cell = f"τ {emission_tao:,.4f}"
507
+
508
+ if netuid in ema_tao_inflow:
509
+ ema_value = ema_tao_inflow[netuid].tao
510
+ else:
511
+ ema_value = 0.0
512
+ ema_flow_cell = f"τ {ema_value:,.4f}"
513
+
514
+ price_cell = f"{price_value} τ/{symbol}"
515
+ alpha_out_cell = (
516
+ f"{alpha_out_value} {symbol}"
517
+ if netuid != 0
518
+ else f"{symbol} {alpha_out_value}"
519
+ )
520
+ market_cap_cell = f"τ {market_cap_value}"
521
+ supply_cell = f"{supply_value} {symbol} [#806DAF]/21M"
522
+
523
+ if netuid != 0:
524
+ tempo_cell = f"{subnet.blocks_since_last_step}/{subnet.tempo}"
525
+ else:
526
+ tempo_cell = "-/-"
527
+
528
+ mechanisms_cell = str(mechanisms.get(netuid, 1))
529
+
530
+ rows.append(
531
+ (
532
+ netuid_cell, # Netuid
533
+ subnet_name_cell, # Name
534
+ price_cell, # Rate τ_in/α_in
535
+ market_cap_cell, # Market Cap
536
+ emission_cell, # Emission (τ)
537
+ ema_flow_cell, # EMA MESH Inflow (τ)
538
+ liquidity_cell, # Liquidity (t_in, a_in)
539
+ alpha_out_cell, # Stake α_out
540
+ supply_cell, # Supply
541
+ tempo_cell, # Tempo k/n
542
+ mechanisms_cell, # Mechanism count
543
+ )
544
+ )
545
+
546
+ total_emissions = round(
547
+ sum(
548
+ subnet.tao_in_emission.tao for subnet in subnets_ if subnet.netuid != 0
549
+ ),
550
+ 4,
551
+ )
552
+ total_tao_flow_ema = round(
553
+ sum(inflow.tao for inflow in ema_tao_inflow.values()), 4
554
+ )
555
+ total_rate = round(
556
+ sum(float(subnet.price.tao) for subnet in subnets_ if subnet.netuid != 0), 4
557
+ )
558
+ total_netuids = len(subnets_)
559
+ defined_table = define_table(
560
+ total_emissions,
561
+ total_rate,
562
+ total_netuids,
563
+ percentage_string,
564
+ total_tao_flow_ema,
565
+ )
566
+
567
+ for row in rows:
568
+ defined_table.add_row(*row)
569
+ return defined_table
570
+
571
+ def dict_table(subnets_, block_number_, mechanisms, ema_tao_inflow) -> dict:
572
+ subnet_rows = {}
573
+ total_tao_emitted, _ = calculate_emission_stats(subnets_, block_number_)
574
+ total_emissions = 0.0
575
+ total_tao_flow_ema = 0.0
576
+ total_rate = 0.0
577
+ total_netuids = len(subnets_)
578
+ emission_percentage = (total_tao_emitted / block_number_) * 100
579
+ for subnet in subnets_:
580
+ total_emissions += subnet.tao_in_emission.tao
581
+ total_rate += subnet.price.tao
582
+ netuid = subnet.netuid
583
+ if netuid == 0:
584
+ emission_tao = 0.0
585
+ else:
586
+ emission_tao = subnet.tao_in_emission.tao
587
+ alpha_in_value = subnet.alpha_in.tao
588
+ alpha_out_value = subnet.alpha_out.tao
589
+ price_value = subnet.price.tao
590
+ market_cap = (subnet.alpha_in.tao + subnet.alpha_out.tao) * subnet.price.tao
591
+ tao_in = subnet.tao_in.tao if netuid != 0 else None
592
+ alpha_in = alpha_in_value if netuid != 0 else None
593
+ alpha_out = alpha_out_value if netuid != 0 else None
594
+ supply = subnet.alpha_in.tao + subnet.alpha_out.tao
595
+ subnet_name = get_subnet_name(subnet)
596
+ tempo = {
597
+ "blocks_since_last_step": (
598
+ subnet.blocks_since_last_step if netuid != 0 else None
599
+ ),
600
+ "sn_tempo": (subnet.tempo if netuid != 0 else None),
601
+ }
602
+ tao_flow_ema = None
603
+ if netuid in ema_tao_inflow:
604
+ tao_flow_ema = ema_tao_inflow[netuid].tao
605
+ total_tao_flow_ema += tao_flow_ema.tao
606
+ subnet_rows[netuid] = {
607
+ "netuid": netuid,
608
+ "subnet_name": subnet_name,
609
+ "price": price_value,
610
+ "market_cap": market_cap,
611
+ "emission": emission_tao,
612
+ "tao_flow_ema": tao_flow_ema,
613
+ "liquidity": {"tao_in": tao_in, "alpha_in": alpha_in},
614
+ "alpha_out": alpha_out,
615
+ "supply": supply,
616
+ "tempo": tempo,
617
+ "mechanisms": mechanisms.get(netuid, 1),
618
+ }
619
+ output = {
620
+ "total_tao_emitted": total_tao_emitted,
621
+ "total_emissions": total_emissions,
622
+ "total_rate": total_rate,
623
+ "total_netuids": total_netuids,
624
+ "emission_percentage": emission_percentage,
625
+ "total_tao_flow_ema": total_tao_flow_ema,
626
+ "subnets": subnet_rows,
627
+ }
628
+ return output
629
+
630
+ # Live mode
631
+ def create_table_live(
632
+ subnets_, previous_data_, block_number_, mechanisms, ema_tao_inflow
633
+ ):
634
+ def format_cell(
635
+ value, previous_value, unit="", unit_first=False, precision=4, millify=False
636
+ ):
637
+ if previous_value is not None:
638
+ change = value - previous_value
639
+ if abs(change) > 10 ** (-precision):
640
+ formatted_change = (
641
+ f"{change:.{precision}f}"
642
+ if not millify
643
+ else f"{millify_tao(change)}"
644
+ )
645
+ change_text = (
646
+ f" [pale_green3](+{formatted_change})[/pale_green3]"
647
+ if change > 0
648
+ else f" [hot_pink3]({formatted_change})[/hot_pink3]"
649
+ )
650
+ else:
651
+ change_text = ""
652
+ else:
653
+ change_text = ""
654
+ formatted_value = (
655
+ f"{value:,.{precision}f}" if not millify else millify_tao(value)
656
+ )
657
+ return (
658
+ f"{formatted_value} {unit}{change_text}"
659
+ if not unit_first
660
+ else f"{unit} {formatted_value}{change_text}"
661
+ )
662
+
663
+ def format_liquidity_cell(
664
+ tao_val,
665
+ alpha_val,
666
+ prev_tao,
667
+ prev_alpha,
668
+ symbol,
669
+ precision=4,
670
+ millify=False,
671
+ netuid=None,
672
+ ):
673
+ """Format liquidity cell with combined changes"""
674
+
675
+ tao_str = (
676
+ f"τ {millify_tao(tao_val)}"
677
+ if millify
678
+ else f"τ {tao_val:,.{precision}f}"
679
+ )
680
+ _alpha_str = f"{millify_tao(alpha_val) if millify else f'{alpha_val:,.{precision}f}'}"
681
+ alpha_str = (
682
+ f"{_alpha_str} {symbol}" if netuid != 0 else f"{symbol} {_alpha_str}"
683
+ )
684
+
685
+ # Show delta
686
+ if prev_tao is not None and prev_alpha is not None:
687
+ tao_change = tao_val - prev_tao
688
+ alpha_change = alpha_val - prev_alpha
689
+
690
+ # Show changes if either value changed
691
+ if abs(tao_change) > 10 ** (-precision) or abs(alpha_change) > 10 ** (
692
+ -precision
693
+ ):
694
+ if millify:
695
+ tao_change_str = (
696
+ f"+{millify_tao(tao_change)}"
697
+ if tao_change > 0
698
+ else f"{millify_tao(tao_change)}"
699
+ )
700
+ alpha_change_str = (
701
+ f"+{millify_tao(alpha_change)}"
702
+ if alpha_change > 0
703
+ else f"{millify_tao(alpha_change)}"
704
+ )
705
+ else:
706
+ tao_change_str = (
707
+ f"+{tao_change:.{precision}f}"
708
+ if tao_change > 0
709
+ else f"{tao_change:.{precision}f}"
710
+ )
711
+ alpha_change_str = (
712
+ f"+{alpha_change:.{precision}f}"
713
+ if alpha_change > 0
714
+ else f"{alpha_change:.{precision}f}"
715
+ )
716
+
717
+ changes_str = (
718
+ f" [pale_green3]({tao_change_str}[/pale_green3]"
719
+ if tao_change > 0
720
+ else f" [hot_pink3]({tao_change_str}[/hot_pink3]"
721
+ if tao_change < 0
722
+ else f" [white]({tao_change_str}[/white]"
723
+ )
724
+ changes_str += (
725
+ f"[pale_green3],{alpha_change_str})[/pale_green3]"
726
+ if alpha_change > 0
727
+ else f"[hot_pink3],{alpha_change_str})[/hot_pink3]"
728
+ if alpha_change < 0
729
+ else f"[white],{alpha_change_str})[/white]"
730
+ )
731
+ return f"{tao_str}, {alpha_str}{changes_str}"
732
+
733
+ return f"{tao_str}, {alpha_str}"
734
+
735
+ rows = []
736
+ current_data = {} # To store current values for comparison in the next update
737
+ _, percentage_string = calculate_emission_stats(subnets_, block_number_)
738
+
739
+ for subnet in subnets_:
740
+ netuid = subnet.netuid
741
+ symbol = f"{subnet.symbol}\u200e"
742
+
743
+ if netuid == 0:
744
+ emission_tao = 0.0
745
+ else:
746
+ emission_tao = subnet.tao_in_emission.tao
747
+
748
+ market_cap = (subnet.alpha_in.tao + subnet.alpha_out.tao) * subnet.price.tao
749
+ supply = subnet.alpha_in.tao + subnet.alpha_out.tao
750
+
751
+ tao_flow_ema = 0.0
752
+ if netuid in ema_tao_inflow:
753
+ tao_flow_ema = ema_tao_inflow[netuid].tao
754
+
755
+ # Store current values for comparison
756
+ current_data[netuid] = {
757
+ "market_cap": market_cap,
758
+ "emission_tao": emission_tao,
759
+ "tao_flow_ema": tao_flow_ema,
760
+ "alpha_out": subnet.alpha_out.tao,
761
+ "tao_in": subnet.tao_in.tao,
762
+ "alpha_in": subnet.alpha_in.tao,
763
+ "price": subnet.price.tao,
764
+ "supply": supply,
765
+ "blocks_since_last_step": subnet.blocks_since_last_step,
766
+ }
767
+ prev = previous_data_.get(netuid, {}) if previous_data_ else {}
768
+
769
+ # Prepare cells
770
+ if netuid == 0:
771
+ unit_first = True
772
+ else:
773
+ unit_first = False
774
+
775
+ netuid_cell = str(netuid)
776
+ subnet_name_cell = (
777
+ f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{subnet.symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
778
+ f" {get_subnet_name(subnet)}"
779
+ )
780
+ emission_cell = format_cell(
781
+ emission_tao,
782
+ prev.get("emission_tao"),
783
+ unit="τ",
784
+ unit_first=True,
785
+ precision=4,
786
+ )
787
+
788
+ tao_flow_ema_cell = format_cell(
789
+ tao_flow_ema,
790
+ prev.get("tao_flow_ema"),
791
+ unit="τ",
792
+ unit_first=True,
793
+ precision=4,
794
+ )
795
+
796
+ price_cell = format_cell(
797
+ subnet.price.tao,
798
+ prev.get("price"),
799
+ unit=f"τ/{symbol}",
800
+ precision=4,
801
+ millify=False,
802
+ )
803
+
804
+ alpha_out_cell = format_cell(
805
+ subnet.alpha_out.tao,
806
+ prev.get("alpha_out"),
807
+ unit=f"{symbol}",
808
+ unit_first=unit_first,
809
+ precision=5,
810
+ millify=True if not verbose else False,
811
+ )
812
+ liquidity_cell = (
813
+ format_liquidity_cell(
814
+ subnet.tao_in.tao,
815
+ subnet.alpha_in.tao,
816
+ prev.get("tao_in"),
817
+ prev.get("alpha_in"),
818
+ symbol,
819
+ precision=4,
820
+ millify=not verbose,
821
+ netuid=netuid,
822
+ )
823
+ if netuid != 0
824
+ else "-, -"
825
+ )
826
+
827
+ market_cap_cell = format_cell(
828
+ market_cap,
829
+ prev.get("market_cap"),
830
+ unit="τ",
831
+ unit_first=True,
832
+ precision=4,
833
+ millify=True if not verbose else False,
834
+ )
835
+
836
+ # Supply cell
837
+ supply_cell = format_cell(
838
+ supply,
839
+ prev.get("supply"),
840
+ unit=f"{symbol} [#806DAF]/21M",
841
+ unit_first=False,
842
+ precision=2,
843
+ millify=True if not verbose else False,
844
+ )
845
+
846
+ # Tempo cell
847
+ prev_blocks_since_last_step = prev.get("blocks_since_last_step")
848
+ if prev_blocks_since_last_step is not None:
849
+ if subnet.blocks_since_last_step >= prev_blocks_since_last_step:
850
+ block_change = (
851
+ subnet.blocks_since_last_step - prev_blocks_since_last_step
852
+ )
853
+ else:
854
+ # Tempo restarted
855
+ block_change = (
856
+ subnet.blocks_since_last_step + subnet.tempo + 1
857
+ ) - prev_blocks_since_last_step
858
+ if block_change > 0:
859
+ block_change_text = f" [pale_green3](+{block_change})[/pale_green3]"
860
+ elif block_change < 0:
861
+ block_change_text = f" [hot_pink3]({block_change})[/hot_pink3]"
862
+ else:
863
+ block_change_text = ""
864
+ else:
865
+ block_change_text = ""
866
+ tempo_cell = (
867
+ (f"{subnet.blocks_since_last_step}/{subnet.tempo}{block_change_text}")
868
+ if netuid != 0
869
+ else "-/-"
870
+ )
871
+
872
+ rows.append(
873
+ (
874
+ netuid_cell, # Netuid
875
+ subnet_name_cell, # Name
876
+ price_cell, # Rate τ_in/α_in
877
+ market_cap_cell, # Market Cap
878
+ emission_cell, # Emission (τ)
879
+ tao_flow_ema_cell, # EMA MESH Inflow (τ)
880
+ liquidity_cell, # Liquidity (t_in, a_in)
881
+ alpha_out_cell, # Stake α_out
882
+ supply_cell, # Supply
883
+ tempo_cell, # Tempo k/n
884
+ str(mechanisms.get(netuid, 1)), # Mechanisms
885
+ )
886
+ )
887
+
888
+ # Calculate totals
889
+ total_netuids = len(subnets_)
890
+ _total_emissions = sum(
891
+ subnet.tao_in_emission.tao for subnet in subnets_ if subnet.netuid != 0
892
+ )
893
+ total_emissions = (
894
+ f"{millify_tao(_total_emissions)}"
895
+ if not verbose
896
+ else f"{_total_emissions:,.2f}"
897
+ )
898
+ _total_tao_flow_ema = sum(inflow.tao for inflow in ema_tao_inflow.values())
899
+ total_tao_flow_ema = (
900
+ f"{millify_tao(_total_tao_flow_ema)}"
901
+ if not verbose
902
+ else f"{_total_tao_flow_ema:,.2f}"
903
+ )
904
+ total_rate = sum(subnet.price.tao for subnet in subnets_ if subnet.netuid != 0)
905
+ total_rate = (
906
+ f"{millify_tao(total_rate)}" if not verbose else f"{total_rate:,.2f}"
907
+ )
908
+ table = define_table(
909
+ total_emissions,
910
+ total_rate,
911
+ total_netuids,
912
+ percentage_string,
913
+ total_tao_flow_ema,
914
+ )
915
+
916
+ for row in rows:
917
+ table.add_row(*row)
918
+ return table, current_data
919
+
920
+ # Live mode
921
+ if live:
922
+ refresh_interval = 10 # seconds
923
+
924
+ progress = Progress(
925
+ TextColumn("[progress.description]{task.description}"),
926
+ BarColumn(bar_width=20, style="green", complete_style="green"),
927
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
928
+ console=console,
929
+ auto_refresh=True,
930
+ )
931
+ progress_task = progress.add_task("Updating:", total=refresh_interval)
932
+
933
+ previous_block = None
934
+ current_block = None
935
+ previous_data = None
936
+
937
+ with Live(console=console, auto_refresh=True) as live:
938
+ try:
939
+ while True:
940
+ (
941
+ subnets,
942
+ block_number,
943
+ mechanisms,
944
+ ema_tao_inflow,
945
+ ) = await fetch_subnet_data()
946
+
947
+ # Update block numbers
948
+ previous_block = current_block
949
+ current_block = block_number
950
+ new_blocks = (
951
+ "N/A"
952
+ if previous_block is None
953
+ else str(current_block - previous_block)
954
+ )
955
+
956
+ table, current_data = create_table_live(
957
+ subnets, previous_data, block_number, mechanisms, ema_tao_inflow
958
+ )
959
+ previous_data = current_data
960
+ progress.reset(progress_task)
961
+ start_time = asyncio.get_event_loop().time()
962
+
963
+ block_info = (
964
+ f"Previous: [dark_sea_green]{previous_block if previous_block else 'N/A'}[/dark_sea_green] "
965
+ f"Current: [dark_sea_green]{current_block}[/dark_sea_green] "
966
+ f"Diff: [dark_sea_green]{new_blocks}[/dark_sea_green] "
967
+ )
968
+
969
+ message = f"Live view active. Press [bold red]Ctrl + C[/bold red] to exit\n{block_info}"
970
+
971
+ live_render = Group(message, progress, table)
972
+ live.update(live_render)
973
+
974
+ while not progress.finished:
975
+ await asyncio.sleep(0.1)
976
+ elapsed = asyncio.get_event_loop().time() - start_time
977
+ progress.update(progress_task, completed=elapsed)
978
+
979
+ except KeyboardInterrupt:
980
+ pass # Ctrl + C
981
+ else:
982
+ # Non-live mode
983
+ subnets, block_number, mechanisms, ema_tao_inflow = await fetch_subnet_data()
984
+ if json_output:
985
+ json_console.print(
986
+ json.dumps(
987
+ dict_table(subnets, block_number, mechanisms, ema_tao_inflow)
988
+ )
989
+ )
990
+ else:
991
+ table = _create_table(subnets, block_number, mechanisms, ema_tao_inflow)
992
+ console.print(table)
993
+
994
+ return
995
+ # TODO: Temporarily returning till we update docs
996
+ display_table = Prompt.ask(
997
+ "\nPress Enter to view column descriptions or type 'q' to skip:",
998
+ choices=["", "q"],
999
+ default="",
1000
+ ).lower()
1001
+
1002
+ if display_table == "q":
1003
+ console.print(
1004
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped."
1005
+ )
1006
+ else:
1007
+ header = """
1008
+ [bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows:
1009
+ """
1010
+ console.print(header)
1011
+ description_table = Table(
1012
+ show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True
1013
+ )
1014
+
1015
+ fields = [
1016
+ ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."),
1017
+ (
1018
+ "[bold tan]Symbol[/bold tan]",
1019
+ "The symbol for the subnet's dynamic MESH token.",
1020
+ ),
1021
+ (
1022
+ "[bold tan]Emission (τ)[/bold tan]",
1023
+ "Shows how the one τ per block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's alpha token price by the sum of all alpha prices across all the subnets. This fraction of MESH is then added to the MESH Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#emissions[/blue].",
1024
+ ),
1025
+ (
1026
+ "[bold tan]MESH Pool (τ_in)[/bold tan]",
1027
+ 'Number of MESH in the MESH reserves of the pool for this subnet. Attached to every subnet is a subnet pool, containing a MESH reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#subnet-pool[/blue].',
1028
+ ),
1029
+ (
1030
+ "[bold tan]Alpha Pool (α_in)[/bold tan]",
1031
+ "Number of subnet alpha tokens in the alpha reserves of the pool for this subnet. This reserve, together with 'MESH Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#subnet-pool[/blue].",
1032
+ ),
1033
+ (
1034
+ "[bold tan]STAKE (α_out)[/bold tan]",
1035
+ "Total stake in the subnet, expressed in the subnet's alpha token currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#stake-%CE%B1_out-or-alpha-out-%CE%B1_out[/blue].",
1036
+ ),
1037
+ (
1038
+ "[bold tan]RATE (τ_in/α_in)[/bold tan]",
1039
+ "Exchange rate between MESH and subnet dMESH token. Calculated as the reserve ratio: (MESH Pool (τ_in) / Alpha Pool (α_in)). Note that the terms relative price, alpha token price, alpha price are the same as exchange rate. This rate can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#rate-%CF%84_in%CE%B1_in[/blue].",
1040
+ ),
1041
+ (
1042
+ "[bold tan]Tempo (k/n)[/bold tan]",
1043
+ 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#tempo-kn[/blue].',
1044
+ ),
1045
+ ]
1046
+
1047
+ description_table.add_column("Field", no_wrap=True, style="bold tan")
1048
+ description_table.add_column("Description", overflow="fold")
1049
+ for field_name, description in fields:
1050
+ description_table.add_row(field_name, description)
1051
+ console.print(description_table)
1052
+
1053
+
1054
+ async def show(
1055
+ meshtensor: "MeshtensorInterface",
1056
+ netuid: int,
1057
+ mechanism_id: Optional[int] = None,
1058
+ mechanism_count: Optional[int] = None,
1059
+ sort: bool = False,
1060
+ max_rows: Optional[int] = None,
1061
+ delegate_selection: bool = False,
1062
+ verbose: bool = False,
1063
+ prompt: bool = True,
1064
+ json_output: bool = False,
1065
+ ) -> Optional[str]:
1066
+ async def show_root():
1067
+ # TODO json_output for this, don't forget
1068
+ with console.status(":satellite: Retrieving root network information..."):
1069
+ block_hash = await meshtensor.substrate.get_chain_head()
1070
+ (
1071
+ all_subnets,
1072
+ root_state,
1073
+ identities,
1074
+ old_identities,
1075
+ root_claim_types,
1076
+ ) = await asyncio.gather(
1077
+ meshtensor.all_subnets(block_hash=block_hash),
1078
+ meshtensor.get_subnet_state(netuid=0, block_hash=block_hash),
1079
+ meshtensor.query_all_identities(block_hash=block_hash),
1080
+ meshtensor.get_delegate_identities(block_hash=block_hash),
1081
+ meshtensor.get_all_coldkeys_claim_type(block_hash=block_hash),
1082
+ )
1083
+ root_info = next((s for s in all_subnets if s.netuid == 0), None)
1084
+ if root_info is None:
1085
+ print_error("The root subnet does not exist")
1086
+ return False
1087
+
1088
+ if root_state is None:
1089
+ print_error("The root subnet does not exist")
1090
+ return
1091
+
1092
+ if len(root_state.hotkeys) == 0:
1093
+ print_error("The root-subnet is currently empty with 0 UIDs registered.")
1094
+ return
1095
+
1096
+ tao_sum = sum(root_state.tao_stake).tao
1097
+
1098
+ table = Table(
1099
+ title=f"[{COLOR_PALETTE.G.HEADER}]Root Network\n[{COLOR_PALETTE.G.SUBHEAD}]"
1100
+ f"Network: {meshtensor.network}[/{COLOR_PALETTE.G.SUBHEAD}]\n",
1101
+ show_footer=True,
1102
+ show_edge=False,
1103
+ header_style="bold white",
1104
+ border_style="bright_black",
1105
+ style="bold",
1106
+ title_justify="center",
1107
+ show_lines=False,
1108
+ pad_edge=True,
1109
+ )
1110
+
1111
+ table.add_column("[bold white]Position", style="white", justify="center")
1112
+ table.add_column(
1113
+ "Tao (τ)",
1114
+ style=COLOR_PALETTE["POOLS"]["EXTRA_2"],
1115
+ no_wrap=True,
1116
+ justify="right",
1117
+ footer=f"{tao_sum:.4f} τ" if verbose else f"{millify_tao(tao_sum)} τ",
1118
+ )
1119
+ table.add_column(
1120
+ f"[bold white]Emission ({Balance.get_unit(0)}/block)",
1121
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
1122
+ justify="center",
1123
+ )
1124
+ table.add_column(
1125
+ "[bold white]Hotkey",
1126
+ style=COLOR_PALETTE["GENERAL"]["HOTKEY"],
1127
+ justify="center",
1128
+ )
1129
+ table.add_column(
1130
+ "[bold white]Coldkey",
1131
+ style=COLOR_PALETTE["GENERAL"]["COLDKEY"],
1132
+ justify="center",
1133
+ )
1134
+ table.add_column(
1135
+ "[bold white]Identity",
1136
+ style=COLOR_PALETTE["GENERAL"]["SYMBOL"],
1137
+ justify="left",
1138
+ )
1139
+ table.add_column(
1140
+ "[bold white]Claim Type",
1141
+ style=COLOR_PALETTE["GENERAL"]["SUBHEADING"],
1142
+ justify="center",
1143
+ )
1144
+
1145
+ sorted_hotkeys = sorted(
1146
+ enumerate(root_state.hotkeys),
1147
+ key=lambda x: root_state.tao_stake[x[0]],
1148
+ reverse=True,
1149
+ )
1150
+ sorted_rows = []
1151
+ sorted_hks_delegation = []
1152
+ for pos, (idx, hk) in enumerate(sorted_hotkeys):
1153
+ total_emission_per_block = 0
1154
+ for netuid_ in range(len(all_subnets)):
1155
+ subnet = all_subnets[netuid_]
1156
+ emission_on_subnet = root_state.emission_history[netuid_][idx] / (
1157
+ subnet.tempo or 1
1158
+ )
1159
+ total_emission_per_block += subnet.alpha_to_tao(
1160
+ Balance.from_meshlet(emission_on_subnet)
1161
+ )
1162
+
1163
+ # Get identity for this validator
1164
+ coldkey_identity = identities.get(root_state.coldkeys[idx], {}).get(
1165
+ "name", ""
1166
+ )
1167
+ hotkey_identity = old_identities.get(root_state.hotkeys[idx])
1168
+ validator_identity = (
1169
+ coldkey_identity
1170
+ if coldkey_identity
1171
+ else (hotkey_identity.display if hotkey_identity else "")
1172
+ )
1173
+
1174
+ coldkey_ss58 = root_state.coldkeys[idx]
1175
+ claim_type_info = root_claim_types.get(coldkey_ss58, {"type": "Swap"})
1176
+ total_subnets = len([n for n in all_subnets if n != 0])
1177
+ claim_type = format_claim_type_for_root(claim_type_info, total_subnets)
1178
+
1179
+ sorted_rows.append(
1180
+ (
1181
+ str((pos + 1)), # Position
1182
+ # f"τ {millify_tao(root_state.total_stake[idx].tao)}"
1183
+ # if not verbose
1184
+ # else f"{root_state.total_stake[idx]}", # Total Stake
1185
+ # f"τ {root_state.alpha_stake[idx].tao:.4f}"
1186
+ # if verbose
1187
+ # else f"τ {millify_tao(root_state.alpha_stake[idx])}", # Alpha Stake
1188
+ f"τ {root_state.tao_stake[idx].tao:.4f}"
1189
+ if verbose
1190
+ else f"τ {millify_tao(root_state.tao_stake[idx])}", # Tao Stake
1191
+ f"{total_emission_per_block}", # Emission
1192
+ f"{root_state.hotkeys[idx][:6]}"
1193
+ if not verbose
1194
+ else f"{root_state.hotkeys[idx]}", # Hotkey
1195
+ f"{root_state.coldkeys[idx][:6]}"
1196
+ if not verbose
1197
+ else f"{root_state.coldkeys[idx]}", # Coldkey
1198
+ validator_identity, # Identity
1199
+ claim_type, # Root Claim Type
1200
+ )
1201
+ )
1202
+ sorted_hks_delegation.append(root_state.hotkeys[idx])
1203
+
1204
+ for pos, row in enumerate(sorted_rows, 1):
1205
+ table_row = []
1206
+ # if delegate_selection:
1207
+ # table_row.append(str(pos))
1208
+ table_row.extend(row)
1209
+ table.add_row(*table_row)
1210
+ if delegate_selection and pos == max_rows:
1211
+ break
1212
+ # Print the table
1213
+ console.print(table)
1214
+ console.print("\n")
1215
+
1216
+ if not delegate_selection:
1217
+ tao_pool = (
1218
+ f"{millify_tao(root_info.tao_in.tao)}"
1219
+ if not verbose
1220
+ else f"{root_info.tao_in.tao:,.4f}"
1221
+ )
1222
+ stake = (
1223
+ f"{millify_tao(root_info.alpha_out.tao)}"
1224
+ if not verbose
1225
+ else f"{root_info.alpha_out.tao:,.5f}"
1226
+ )
1227
+ rate = (
1228
+ f"{millify_tao(root_info.price.tao)}"
1229
+ if not verbose
1230
+ else f"{root_info.price.tao:,.4f}"
1231
+ )
1232
+ console.print(
1233
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Root Network (Subnet 0)[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
1234
+ f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{rate} τ/τ[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]"
1235
+ f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]τ 0[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]"
1236
+ f"\n MESH Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]τ {tao_pool}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]"
1237
+ f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]τ {stake}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]"
1238
+ f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{root_info.blocks_since_last_step}/{root_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]"
1239
+ )
1240
+ console.print(
1241
+ """
1242
+ Description:
1243
+ The table displays the root subnet participants and their metrics.
1244
+ The columns are as follows:
1245
+ - Position: The sorted position of the hotkey by total MESH.
1246
+ - MESH: The sum of all MESH balances for this hotkey across all subnets.
1247
+ - Stake: The stake balance of this hotkey on root (measured in MESH).
1248
+ - Emission: The emission accrued to this hotkey across all subnets every block measured in MESH.
1249
+ - Hotkey: The hotkey ss58 address.
1250
+ - Coldkey: The coldkey ss58 address.
1251
+ - Root Claim: The root claim type for this coldkey. 'Swap' converts Alpha to MESH every epoch. 'Keep' keeps Alpha emissions.
1252
+ 'Keep (count)' indicates how many subnets this coldkey is keeping Alpha emissions for.
1253
+ """
1254
+ )
1255
+ if delegate_selection:
1256
+ valid_uids = [str(row[0]) for row in sorted_rows[:max_rows]]
1257
+ while True:
1258
+ selection = Prompt.ask(
1259
+ "\nEnter the Position of the delegate you want to stake to [dim](or press Enter to cancel)[/dim]",
1260
+ default="",
1261
+ choices=[""] + valid_uids,
1262
+ show_choices=False,
1263
+ show_default=False,
1264
+ )
1265
+
1266
+ if selection == "":
1267
+ return None
1268
+
1269
+ position = int(selection)
1270
+ idx = position - 1
1271
+ original_idx = sorted_hotkeys[idx][0]
1272
+ selected_hotkey = root_state.hotkeys[original_idx]
1273
+
1274
+ coldkey_identity = identities.get(
1275
+ root_state.coldkeys[original_idx], {}
1276
+ ).get("name", "")
1277
+ hotkey_identity = old_identities.get(selected_hotkey)
1278
+ validator_identity = (
1279
+ coldkey_identity
1280
+ if coldkey_identity
1281
+ else (hotkey_identity.display if hotkey_identity else "")
1282
+ )
1283
+ identity_str = f" ({validator_identity})" if validator_identity else ""
1284
+
1285
+ console.print(
1286
+ f"\nSelected delegate: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey}{identity_str}"
1287
+ )
1288
+ return selected_hotkey
1289
+
1290
+ async def show_subnet(
1291
+ netuid_: int,
1292
+ mechanism_id: Optional[int],
1293
+ mechanism_count: Optional[int],
1294
+ ):
1295
+ with console.status(":satellite: Retrieving subnet information..."):
1296
+ block_hash = await meshtensor.substrate.get_chain_head()
1297
+ if not await meshtensor.subnet_exists(netuid=netuid_, block_hash=block_hash):
1298
+ print_error(f"Subnet {netuid_} does not exist")
1299
+ return False
1300
+ (
1301
+ subnet_info,
1302
+ identities,
1303
+ old_identities,
1304
+ current_burn_cost,
1305
+ root_claim_types,
1306
+ ema_tao_inflow,
1307
+ ) = await asyncio.gather(
1308
+ meshtensor.subnet(netuid=netuid_, block_hash=block_hash),
1309
+ meshtensor.query_all_identities(block_hash=block_hash),
1310
+ meshtensor.get_delegate_identities(block_hash=block_hash),
1311
+ meshtensor.get_hyperparameter(
1312
+ param_name="Burn", netuid=netuid_, block_hash=block_hash
1313
+ ),
1314
+ meshtensor.get_all_coldkeys_claim_type(block_hash=block_hash),
1315
+ meshtensor.get_subnet_ema_tao_inflow(
1316
+ netuid=netuid_, block_hash=block_hash
1317
+ ),
1318
+ )
1319
+
1320
+ selected_mechanism_id = mechanism_id or 0
1321
+
1322
+ metagraph_info = await meshtensor.get_mechagraph_info(
1323
+ netuid_, selected_mechanism_id, block_hash=block_hash
1324
+ )
1325
+
1326
+ if metagraph_info is None:
1327
+ print_error(
1328
+ f"Subnet {netuid_} with mechanism: {selected_mechanism_id} does not exist"
1329
+ )
1330
+ return False
1331
+
1332
+ if subnet_info is None:
1333
+ print_error(f"Subnet {netuid_} does not exist")
1334
+ return False
1335
+
1336
+ if len(metagraph_info.hotkeys) == 0:
1337
+ print_error(f"Subnet {netuid_} is currently empty with 0 UIDs registered.")
1338
+ return False
1339
+
1340
+ # Define table properties
1341
+ mechanism_label = f"Mechanism {selected_mechanism_id}"
1342
+
1343
+ table = Table(
1344
+ title=f"[{COLOR_PALETTE['GENERAL']['HEADER']}]Subnet [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}"
1345
+ f"{': ' + get_subnet_name(subnet_info)}"
1346
+ f"\n[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Network: {meshtensor.network} • {mechanism_label}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\n",
1347
+ show_footer=True,
1348
+ show_edge=False,
1349
+ header_style="bold white",
1350
+ border_style="bright_black",
1351
+ style="bold",
1352
+ title_justify="center",
1353
+ show_lines=False,
1354
+ pad_edge=True,
1355
+ )
1356
+
1357
+ # For table footers
1358
+ alpha_sum = sum(stake.tao for stake in metagraph_info.alpha_stake)
1359
+ stake_sum = sum(stake.tao for stake in metagraph_info.total_stake)
1360
+ tao_sum = sum((stake * MESH_WEIGHT).tao for stake in metagraph_info.tao_stake)
1361
+ dividends_sum = sum(metagraph_info.dividends)
1362
+ emission_sum = sum(emission.tao for emission in metagraph_info.emission)
1363
+
1364
+ owner_hotkeys = await meshtensor.get_owned_hotkeys(subnet_info.owner_coldkey)
1365
+ if subnet_info.owner_hotkey not in owner_hotkeys:
1366
+ owner_hotkeys.append(subnet_info.owner_hotkey)
1367
+
1368
+ owner_identity = identities.get(subnet_info.owner_coldkey, {}).get("name", "")
1369
+ if not owner_identity:
1370
+ # If no coldkey identity found, try each owner hotkey
1371
+ for hotkey in owner_hotkeys:
1372
+ if hotkey_identity := old_identities.get(hotkey):
1373
+ owner_identity = hotkey_identity.display
1374
+ break
1375
+
1376
+ sorted_indices = sorted(
1377
+ range(len(metagraph_info.hotkeys)),
1378
+ key=lambda i: (
1379
+ # If sort is True, sort only by UIDs
1380
+ i
1381
+ if sort
1382
+ else (
1383
+ # Otherwise
1384
+ # Sort by owner status first
1385
+ not (
1386
+ metagraph_info.coldkeys[i] == subnet_info.owner_coldkey
1387
+ or metagraph_info.hotkeys[i] in owner_hotkeys
1388
+ ),
1389
+ # Then sort by stake amount (higher stakes first)
1390
+ -metagraph_info.total_stake[i].tao,
1391
+ )
1392
+ ),
1393
+ )
1394
+
1395
+ rows = []
1396
+ json_out_rows = []
1397
+ for idx in sorted_indices:
1398
+ # Get identity for this uid
1399
+ coldkey_identity = identities.get(metagraph_info.coldkeys[idx], {}).get(
1400
+ "name", ""
1401
+ )
1402
+ hotkey_identity = old_identities.get(metagraph_info.hotkeys[idx])
1403
+ uid_identity = (
1404
+ coldkey_identity
1405
+ if coldkey_identity
1406
+ else (hotkey_identity.display if hotkey_identity else "~")
1407
+ )
1408
+
1409
+ if (
1410
+ metagraph_info.coldkeys[idx] == subnet_info.owner_coldkey
1411
+ or metagraph_info.hotkeys[idx] in owner_hotkeys
1412
+ ):
1413
+ if uid_identity == "~":
1414
+ uid_identity = (
1415
+ "[dark_sea_green3](*Owner controlled)[/dark_sea_green3]"
1416
+ )
1417
+ else:
1418
+ uid_identity = (
1419
+ f"[dark_sea_green3]{uid_identity} (*Owner)[/dark_sea_green3]"
1420
+ )
1421
+
1422
+ # Modify mesh stake with MESH_WEIGHT
1423
+ tao_stake = metagraph_info.tao_stake[idx] * MESH_WEIGHT
1424
+
1425
+ # Get claim type for this coldkey if applicable MESH stake
1426
+ coldkey_ss58 = metagraph_info.coldkeys[idx]
1427
+ claim_type_info = {"type": "Swap"} # Default
1428
+ claim_type = "-"
1429
+
1430
+ if tao_stake.tao > 0:
1431
+ claim_type_info = root_claim_types.get(coldkey_ss58, {"type": "Swap"})
1432
+ claim_type = format_claim_type_for_subnet(claim_type_info, netuid_)
1433
+
1434
+ rows.append(
1435
+ (
1436
+ str(idx), # UID
1437
+ f"{metagraph_info.total_stake[idx].tao:.4f} {subnet_info.symbol}"
1438
+ if verbose
1439
+ else f"{millify_tao(metagraph_info.total_stake[idx])} {subnet_info.symbol}", # Stake
1440
+ f"{metagraph_info.alpha_stake[idx].tao:.4f} {subnet_info.symbol}"
1441
+ if verbose
1442
+ else f"{millify_tao(metagraph_info.alpha_stake[idx])} {subnet_info.symbol}", # Alpha Stake
1443
+ f"τ {tao_stake.tao:.4f}"
1444
+ if verbose
1445
+ else f"τ {millify_tao(tao_stake)}", # Tao Stake
1446
+ f"{metagraph_info.dividends[idx]:.6f}", # Dividends
1447
+ f"{metagraph_info.incentives[idx]:.6f}", # Incentive
1448
+ f"{Balance.from_tao(metagraph_info.emission[idx].tao).set_unit(netuid_).tao:.6f} {subnet_info.symbol}", # Emissions
1449
+ f"{metagraph_info.hotkeys[idx][:6]}"
1450
+ if not verbose
1451
+ else f"{metagraph_info.hotkeys[idx]}", # Hotkey
1452
+ f"{metagraph_info.coldkeys[idx][:6]}"
1453
+ if not verbose
1454
+ else f"{metagraph_info.coldkeys[idx]}", # Coldkey
1455
+ uid_identity, # Identity
1456
+ claim_type, # Root Claim Type
1457
+ )
1458
+ )
1459
+ json_out_rows.append(
1460
+ {
1461
+ "uid": idx,
1462
+ "stake": metagraph_info.total_stake[idx].tao,
1463
+ "alpha_stake": metagraph_info.alpha_stake[idx].tao,
1464
+ "tao_stake": tao_stake.tao,
1465
+ "dividends": metagraph_info.dividends[idx],
1466
+ "incentive": metagraph_info.incentives[idx],
1467
+ "emissions": Balance.from_tao(metagraph_info.emission[idx].tao)
1468
+ .set_unit(netuid_)
1469
+ .tao,
1470
+ "hotkey": metagraph_info.hotkeys[idx],
1471
+ "coldkey": metagraph_info.coldkeys[idx],
1472
+ "identity": uid_identity,
1473
+ "claim_type": claim_type_info.get("type")
1474
+ if tao_stake.tao > 0
1475
+ else None,
1476
+ "claim_type_subnets": claim_type_info.get("subnets")
1477
+ if claim_type_info.get("type") == "KeepSubnets"
1478
+ else None,
1479
+ }
1480
+ )
1481
+
1482
+ # Add columns to the table
1483
+ table.add_column("UID", style="grey89", no_wrap=True, justify="center")
1484
+ table.add_column(
1485
+ f"Stake ({subnet_info.symbol})",
1486
+ style=COLOR_PALETTE["POOLS"]["ALPHA_IN"],
1487
+ no_wrap=True,
1488
+ justify="right",
1489
+ footer=f"{stake_sum:.4f} {subnet_info.symbol}"
1490
+ if verbose
1491
+ else f"{millify_tao(stake_sum)} {subnet_info.symbol}",
1492
+ )
1493
+ table.add_column(
1494
+ f"Alpha ({subnet_info.symbol})",
1495
+ style=COLOR_PALETTE["POOLS"]["EXTRA_2"],
1496
+ no_wrap=True,
1497
+ justify="right",
1498
+ footer=f"{alpha_sum:.4f} {subnet_info.symbol}"
1499
+ if verbose
1500
+ else f"{millify_tao(alpha_sum)} {subnet_info.symbol}",
1501
+ )
1502
+ table.add_column(
1503
+ "Tao (τ)",
1504
+ style=COLOR_PALETTE["POOLS"]["EXTRA_2"],
1505
+ no_wrap=True,
1506
+ justify="right",
1507
+ footer=f"{tao_sum:.4f} {subnet_info.symbol}"
1508
+ if verbose
1509
+ else f"{millify_tao(tao_sum)} {subnet_info.symbol}",
1510
+ )
1511
+ table.add_column(
1512
+ "Dividends",
1513
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
1514
+ no_wrap=True,
1515
+ justify="center",
1516
+ footer=f"{dividends_sum:.3f}",
1517
+ )
1518
+ table.add_column("Incentive", style="#5fd7ff", no_wrap=True, justify="center")
1519
+ table.add_column(
1520
+ f"Emissions ({subnet_info.symbol})",
1521
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
1522
+ no_wrap=True,
1523
+ justify="center",
1524
+ footer=f"{emission_sum:.4f} {subnet_info.symbol}",
1525
+ )
1526
+ table.add_column(
1527
+ "Hotkey",
1528
+ style=COLOR_PALETTE["GENERAL"]["HOTKEY"],
1529
+ no_wrap=True,
1530
+ justify="center",
1531
+ )
1532
+ table.add_column(
1533
+ "Coldkey",
1534
+ style=COLOR_PALETTE["GENERAL"]["COLDKEY"],
1535
+ no_wrap=True,
1536
+ justify="center",
1537
+ )
1538
+ table.add_column(
1539
+ "Identity",
1540
+ style=COLOR_PALETTE["GENERAL"]["SYMBOL"],
1541
+ no_wrap=True,
1542
+ justify="left",
1543
+ )
1544
+ table.add_column(
1545
+ "Claim Type",
1546
+ style=COLOR_PALETTE["GENERAL"]["SUBHEADING"],
1547
+ no_wrap=True,
1548
+ justify="center",
1549
+ )
1550
+ for pos, row in enumerate(rows, 1):
1551
+ table_row = []
1552
+ table_row.extend(row)
1553
+ table.add_row(*table_row)
1554
+ if delegate_selection and pos == max_rows:
1555
+ break
1556
+
1557
+ # Print the table
1558
+ console.print("\n\n")
1559
+ console.print(table)
1560
+ console.print("\n")
1561
+
1562
+ if not delegate_selection:
1563
+ subnet_name_display = f": {get_subnet_name(subnet_info)}"
1564
+ tao_pool = (
1565
+ f"{millify_tao(subnet_info.tao_in.tao)}"
1566
+ if not verbose
1567
+ else f"{subnet_info.tao_in.tao:,.4f}"
1568
+ )
1569
+ alpha_pool = (
1570
+ f"{millify_tao(subnet_info.alpha_in.tao)}"
1571
+ if not verbose
1572
+ else f"{subnet_info.alpha_in.tao:,.4f}"
1573
+ )
1574
+ current_registration_burn = (
1575
+ Balance.from_meshlet(int(current_burn_cost))
1576
+ if current_burn_cost
1577
+ else Balance(0)
1578
+ )
1579
+ total_mechanisms = mechanism_count if mechanism_count is not None else 1
1580
+
1581
+ output_dict = {
1582
+ "netuid": netuid_,
1583
+ "mechanism_id": selected_mechanism_id,
1584
+ **(
1585
+ {"mechanism_count": mechanism_count}
1586
+ if mechanism_count is not None
1587
+ else {}
1588
+ ),
1589
+ "name": subnet_name_display,
1590
+ "owner": subnet_info.owner_coldkey,
1591
+ "owner_identity": owner_identity,
1592
+ "rate": subnet_info.price.tao,
1593
+ "emission": subnet_info.emission.tao,
1594
+ "tao_pool": subnet_info.tao_in.tao,
1595
+ "alpha_pool": subnet_info.alpha_in.tao,
1596
+ "tempo": {
1597
+ "block_since_last_step": subnet_info.blocks_since_last_step,
1598
+ "tempo": subnet_info.tempo,
1599
+ },
1600
+ "registration_cost": current_registration_burn.tao,
1601
+ "uids": json_out_rows,
1602
+ }
1603
+ if json_output:
1604
+ json_console.print(json.dumps(output_dict))
1605
+
1606
+ mech_line = (
1607
+ f"\n Mechanism ID: [{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]#{selected_mechanism_id}"
1608
+ f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]"
1609
+ if total_mechanisms > 1
1610
+ else ""
1611
+ )
1612
+ total_mech_line = (
1613
+ f"\n Total mechanisms: [{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_2']}]"
1614
+ f"{total_mechanisms}[/{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_2']}]"
1615
+ )
1616
+
1617
+ console.print(
1618
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]Subnet {netuid_}{subnet_name_display}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
1619
+ f"{mech_line}"
1620
+ f"{total_mech_line}"
1621
+ f"\n Owner: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{subnet_info.owner_coldkey}{' (' + owner_identity + ')' if owner_identity else ''}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]"
1622
+ f"\n Rate: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{subnet_info.price.tao:.4f} τ/{subnet_info.symbol}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]"
1623
+ f"\n EMA MESH Inflow: [{COLOR_PALETTE['STAKE']['MESH']}]τ {ema_tao_inflow.tao:.4f}[/{COLOR_PALETTE['STAKE']['MESH']}]"
1624
+ f"\n Emission: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]τ {subnet_info.tao_in_emission.tao:,.4f}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]"
1625
+ f"\n MESH Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]τ {tao_pool}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]"
1626
+ f"\n Alpha Pool: [{COLOR_PALETTE['POOLS']['ALPHA_IN']}]{alpha_pool} {subnet_info.symbol}[/{COLOR_PALETTE['POOLS']['ALPHA_IN']}]"
1627
+ # f"\n Stake: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.alpha_out.tao:,.5f} {subnet_info.symbol}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]"
1628
+ f"\n Tempo: [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]{subnet_info.blocks_since_last_step}/{subnet_info.tempo}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]"
1629
+ f"\n Registration cost (recycled): [{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]τ {current_registration_burn.tao:.4f}[/{COLOR_PALETTE['STAKE']['STAKE_ALPHA']}]"
1630
+ )
1631
+ # console.print(
1632
+ # """
1633
+ # Description:
1634
+ # The table displays the subnet participants and their metrics.
1635
+ # The columns are as follows:
1636
+ # - UID: The hotkey index in the subnet.
1637
+ # - MESH: The sum of all MESH balances for this hotkey across all subnets.
1638
+ # - Stake: The stake balance of this hotkey on this subnet.
1639
+ # - Weight: The stake-weight of this hotkey on this subnet. Computed as an average of the normalized MESH and Stake columns of this subnet.
1640
+ # - Dividends: Validating dividends earned by the hotkey.
1641
+ # - Incentives: Mining incentives earned by the hotkey (always zero in the MESHLET demo.)
1642
+ # - Emission: The emission accrued to this hokey on this subnet every block (in staking units).
1643
+ # - Hotkey: The hotkey ss58 address.
1644
+ # - Coldkey: The coldkey ss58 address.
1645
+ # """
1646
+ # )
1647
+
1648
+ if delegate_selection:
1649
+ while True:
1650
+ valid_uids = [str(row[0]) for row in rows[:max_rows]]
1651
+ selection = Prompt.ask(
1652
+ "\nEnter the UID of the delegate you want to stake to [dim](or press Enter to cancel)[/dim]",
1653
+ default="",
1654
+ choices=[""] + valid_uids,
1655
+ show_choices=False,
1656
+ show_default=False,
1657
+ )
1658
+
1659
+ if selection == "":
1660
+ return None
1661
+
1662
+ try:
1663
+ uid = int(selection)
1664
+ # Check if the UID exists in the subnet
1665
+ if uid in [int(row[0]) for row in rows]:
1666
+ row_data = next(row for row in rows if int(row[0]) == uid)
1667
+ hotkey = metagraph_info.hotkeys[uid]
1668
+ identity = "" if row_data[9] == "~" else row_data[9]
1669
+ identity_str = f" ({identity})" if identity else ""
1670
+ console.print(
1671
+ f"\nSelected delegate: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{hotkey}{identity_str}"
1672
+ )
1673
+ return hotkey
1674
+ else:
1675
+ console.print(
1676
+ "[red]Invalid UID. Please enter a valid UID from the table above[/red]"
1677
+ )
1678
+ except ValueError:
1679
+ console.print("[red]Please enter a valid number[/red]")
1680
+
1681
+ return None
1682
+
1683
+ if netuid == 0:
1684
+ result = await show_root()
1685
+ return result
1686
+ else:
1687
+ result = await show_subnet(netuid, mechanism_id, mechanism_count)
1688
+ return result
1689
+
1690
+
1691
+ async def burn_cost(
1692
+ meshtensor: "MeshtensorInterface", json_output: bool = False
1693
+ ) -> Optional[Balance]:
1694
+ """View locking cost of creating a new subnetwork"""
1695
+ with console.status(
1696
+ f":satellite:Retrieving lock cost from {meshtensor.network}...",
1697
+ spinner="aesthetic",
1698
+ ):
1699
+ current_burn_cost = await meshtensor.burn_cost()
1700
+ if current_burn_cost:
1701
+ if json_output:
1702
+ json_console.print(
1703
+ json.dumps({"burn_cost": current_burn_cost.to_dict(), "error": ""})
1704
+ )
1705
+ else:
1706
+ console.print(
1707
+ f"Subnet burn cost: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_burn_cost}"
1708
+ )
1709
+ return current_burn_cost
1710
+ else:
1711
+ if json_output:
1712
+ json_console.print(
1713
+ json.dumps(
1714
+ {"burn_cost": None, "error": "Failed to get subnet burn cost"}
1715
+ )
1716
+ )
1717
+ else:
1718
+ print_error("Subnet burn cost: Failed to get subnet burn cost")
1719
+ return None
1720
+
1721
+
1722
+ async def create(
1723
+ wallet: Wallet,
1724
+ meshtensor: "MeshtensorInterface",
1725
+ subnet_identity: dict,
1726
+ proxy: Optional[str],
1727
+ json_output: bool,
1728
+ prompt: bool,
1729
+ decline: bool = False,
1730
+ quiet: bool = False,
1731
+ mev_protection: bool = True,
1732
+ ):
1733
+ """Register a subnetwork"""
1734
+ coldkey_ss58 = proxy or wallet.coldkeypub.ss58_address
1735
+ # Call register command.
1736
+ success, netuid, ext_id = await register_subnetwork_extrinsic(
1737
+ meshtensor=meshtensor,
1738
+ wallet=wallet,
1739
+ subnet_identity=subnet_identity,
1740
+ prompt=prompt,
1741
+ decline=decline,
1742
+ quiet=quiet,
1743
+ proxy=proxy,
1744
+ mev_protection=mev_protection,
1745
+ )
1746
+ if json_output:
1747
+ # technically, netuid can be `None`, but only if not wait for finalization/inclusion. However, as of present
1748
+ # (2025/04/03), we always use the default `wait_for_finalization=True`, so it will always have a netuid.
1749
+ json_console.print(
1750
+ json.dumps(
1751
+ {"success": success, "netuid": netuid, "extrinsic_identifier": ext_id}
1752
+ )
1753
+ )
1754
+ return success
1755
+ if success and prompt:
1756
+ # Prompt for user to set identity.
1757
+ do_set_identity = confirm_action(
1758
+ "Would you like to set your own [blue]identity?[/blue]",
1759
+ decline=decline,
1760
+ quiet=quiet,
1761
+ )
1762
+
1763
+ if do_set_identity:
1764
+ current_identity = await get_id(
1765
+ meshtensor, coldkey_ss58, "Current on-chain identity"
1766
+ )
1767
+ if prompt:
1768
+ if not confirm_action(
1769
+ "\nCost to register an [blue]Identity[/blue] is [blue]0.1 MESH[/blue],"
1770
+ " are you sure you wish to continue?",
1771
+ decline=decline,
1772
+ quiet=quiet,
1773
+ ):
1774
+ print_error("Aborted!")
1775
+ return False
1776
+
1777
+ identity = prompt_for_identity(
1778
+ current_identity=current_identity,
1779
+ name=None,
1780
+ web_url=None,
1781
+ image_url=None,
1782
+ discord=None,
1783
+ description=None,
1784
+ additional=None,
1785
+ github_repo=None,
1786
+ )
1787
+
1788
+ await set_id(
1789
+ wallet=wallet,
1790
+ meshtensor=meshtensor,
1791
+ name=identity["name"],
1792
+ web_url=identity["url"],
1793
+ image_url=identity["image"],
1794
+ discord=identity["discord"],
1795
+ description=identity["description"],
1796
+ additional=identity["additional"],
1797
+ github_repo=identity["github_repo"],
1798
+ proxy=proxy,
1799
+ )
1800
+
1801
+
1802
+ async def pow_register(
1803
+ wallet: Wallet,
1804
+ meshtensor: "MeshtensorInterface",
1805
+ netuid,
1806
+ processors,
1807
+ update_interval,
1808
+ output_in_place,
1809
+ verbose,
1810
+ use_cuda,
1811
+ dev_id,
1812
+ threads_per_block,
1813
+ prompt: bool,
1814
+ ):
1815
+ """Register neuron."""
1816
+
1817
+ await register_extrinsic(
1818
+ meshtensor,
1819
+ wallet=wallet,
1820
+ netuid=netuid,
1821
+ prompt=prompt,
1822
+ tpb=threads_per_block,
1823
+ update_interval=update_interval,
1824
+ num_processes=processors,
1825
+ cuda=use_cuda,
1826
+ dev_id=dev_id,
1827
+ output_in_place=output_in_place,
1828
+ log_verbose=verbose,
1829
+ )
1830
+
1831
+
1832
+ async def register(
1833
+ wallet: Wallet,
1834
+ meshtensor: "MeshtensorInterface",
1835
+ netuid: int,
1836
+ era: Optional[int],
1837
+ json_output: bool,
1838
+ prompt: bool,
1839
+ decline: bool = False,
1840
+ quiet: bool = False,
1841
+ proxy: Optional[str] = None,
1842
+ ):
1843
+ """Register neuron by recycling some MESH."""
1844
+
1845
+ async def _storage_key(storage_fn: str) -> StorageKey:
1846
+ """
1847
+ Generates a MeshtensorModule storage key with [netuid] as the param for a given storage function
1848
+ """
1849
+ return await meshtensor.substrate.create_storage_key(
1850
+ pallet="MeshtensorModule",
1851
+ storage_function=storage_fn,
1852
+ params=[netuid],
1853
+ block_hash=block_hash,
1854
+ )
1855
+
1856
+ coldkey_ss58 = proxy or wallet.coldkeypub.ss58_address
1857
+ # Verify subnet exists
1858
+ print_verbose("Checking subnet status")
1859
+ block_hash = await meshtensor.substrate.get_chain_head()
1860
+ if not await meshtensor.subnet_exists(netuid=netuid, block_hash=block_hash):
1861
+ print_error(f"Subnet {netuid} does not exist")
1862
+ if json_output:
1863
+ json_console.print_json(
1864
+ data={
1865
+ "success": False,
1866
+ "msg": f"Subnet {netuid} does not exist",
1867
+ "extrinsic_identifier": None,
1868
+ }
1869
+ )
1870
+ return
1871
+
1872
+ # Check current recycle amount
1873
+ print_verbose("Fetching recycle amount")
1874
+ current_recycle_, balance = await asyncio.gather(
1875
+ meshtensor.get_hyperparameter(
1876
+ param_name="Burn", netuid=netuid, block_hash=block_hash
1877
+ ),
1878
+ meshtensor.get_balance(coldkey_ss58, block_hash=block_hash),
1879
+ )
1880
+ current_recycle = (
1881
+ Balance.from_meshlet(int(current_recycle_)) if current_recycle_ else Balance(0)
1882
+ )
1883
+
1884
+ # Check balance is sufficient
1885
+ if balance < current_recycle:
1886
+ err_msg = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} MESH"
1887
+ print_error(err_msg)
1888
+ if json_output:
1889
+ json_console.print_json(
1890
+ data={"success": False, "msg": err_msg, "extrinsic_identifier": None}
1891
+ )
1892
+ return
1893
+
1894
+ if prompt and not json_output:
1895
+ # TODO make this a reusable function, also used in subnets list
1896
+ # Show creation table.
1897
+ table = Table(
1898
+ title=(
1899
+ f"\n[{COLOR_PALETTE.G.HEADER}]"
1900
+ f"Register to [{COLOR_PALETTE.G.SUBHEAD}]netuid: {netuid}[/{COLOR_PALETTE.G.SUBHEAD}]"
1901
+ f"\nNetwork: [{COLOR_PALETTE.G.SUBHEAD}]{meshtensor.network}[/{COLOR_PALETTE.G.SUBHEAD}]\n"
1902
+ ),
1903
+ show_footer=True,
1904
+ show_edge=False,
1905
+ header_style="bold white",
1906
+ border_style="bright_black",
1907
+ style="bold",
1908
+ title_justify="center",
1909
+ show_lines=False,
1910
+ pad_edge=True,
1911
+ )
1912
+ table.add_column(
1913
+ "Netuid", style="rgb(253,246,227)", no_wrap=True, justify="center"
1914
+ )
1915
+ table.add_column(
1916
+ "Symbol",
1917
+ style=COLOR_PALETTE["GENERAL"]["SYMBOL"],
1918
+ no_wrap=True,
1919
+ justify="center",
1920
+ )
1921
+ table.add_column(
1922
+ f"Cost ({Balance.get_unit(0)})",
1923
+ style=COLOR_PALETTE["POOLS"]["MESH"],
1924
+ no_wrap=True,
1925
+ justify="center",
1926
+ )
1927
+ table.add_column(
1928
+ "Hotkey",
1929
+ style=COLOR_PALETTE["GENERAL"]["HOTKEY"],
1930
+ no_wrap=True,
1931
+ justify="center",
1932
+ )
1933
+ table.add_column(
1934
+ "Coldkey",
1935
+ style=COLOR_PALETTE["GENERAL"]["COLDKEY"],
1936
+ no_wrap=True,
1937
+ justify="center",
1938
+ )
1939
+ table.add_row(
1940
+ str(netuid),
1941
+ f"{Balance.get_unit(netuid)}",
1942
+ f"τ {current_recycle.tao:.4f}",
1943
+ f"{get_hotkey_pub_ss58(wallet)}",
1944
+ f"{coldkey_ss58}",
1945
+ )
1946
+ console.print(table)
1947
+ if not (
1948
+ confirm_action(
1949
+ f"Your balance is: [{COLOR_PALETTE.G.BAL}]{balance}[/{COLOR_PALETTE.G.BAL}]\n"
1950
+ f"The cost to register by recycle is "
1951
+ f"[{COLOR_PALETTE.G.COST}]{current_recycle}[/{COLOR_PALETTE.G.COST}]\n"
1952
+ f"Do you want to continue?",
1953
+ default=False,
1954
+ decline=decline,
1955
+ quiet=quiet,
1956
+ )
1957
+ ):
1958
+ return
1959
+
1960
+ if netuid == 0:
1961
+ success, msg, ext_id = await root_register_extrinsic(
1962
+ meshtensor, wallet=wallet, proxy=proxy
1963
+ )
1964
+ else:
1965
+ success, msg, ext_id = await burned_register_extrinsic(
1966
+ meshtensor,
1967
+ wallet=wallet,
1968
+ netuid=netuid,
1969
+ old_balance=balance,
1970
+ era=era,
1971
+ proxy=proxy,
1972
+ )
1973
+ if not success:
1974
+ print_error(f"Failure: {msg}")
1975
+ print_verbose("Checking registration allowed and limits")
1976
+ storage_key_results, current_block = await asyncio.gather(
1977
+ meshtensor.substrate.query_multi(
1978
+ [
1979
+ await _storage_key("NetworkRegistrationAllowed"),
1980
+ await _storage_key("TargetRegistrationsPerInterval"),
1981
+ await _storage_key("RegistrationsThisInterval"),
1982
+ await _storage_key("LastAdjustmentBlock"),
1983
+ await _storage_key("AdjustmentInterval"),
1984
+ ]
1985
+ ),
1986
+ meshtensor.substrate.get_block_number(block_hash),
1987
+ )
1988
+ (
1989
+ registration_allowed,
1990
+ target_registrations_per_interval,
1991
+ registrations_this_interval,
1992
+ last_adjustment_block,
1993
+ adjustment_interval,
1994
+ ) = [x[1] for x in storage_key_results]
1995
+
1996
+ if not registration_allowed:
1997
+ print_error(f"Registration to subnet {netuid} is not allowed")
1998
+ if json_output:
1999
+ json_console.print_json(
2000
+ data={
2001
+ "success": False,
2002
+ "msg": f"Registration to subnet {netuid} is not allowed",
2003
+ "extrinsic_identifier": None,
2004
+ }
2005
+ )
2006
+ return
2007
+
2008
+ if registrations_this_interval >= target_registrations_per_interval * 3:
2009
+ next_adjustment_block = last_adjustment_block + adjustment_interval
2010
+ remaining_blocks = next_adjustment_block - current_block
2011
+ print_error(
2012
+ f"Registration to subnet {netuid} is full for this interval. "
2013
+ f"Try again in {remaining_blocks} blocks."
2014
+ )
2015
+ if json_output:
2016
+ json_console.print_json(
2017
+ data={
2018
+ "success": False,
2019
+ "msg": f"Registration to subnet {netuid} is full for this interval. "
2020
+ f"Try again in {remaining_blocks} blocks.",
2021
+ "extrinsic_identifier": None,
2022
+ }
2023
+ )
2024
+ return
2025
+ if json_output:
2026
+ json_console.print(
2027
+ json.dumps({"success": success, "msg": msg, "extrinsic_identifier": ext_id})
2028
+ )
2029
+
2030
+
2031
+ # TODO: Confirm emissions, incentive, Dividends are to be fetched from subnet_state or keep NeuronInfo
2032
+ async def metagraph_cmd(
2033
+ meshtensor: Optional["MeshtensorInterface"],
2034
+ netuid: Optional[int],
2035
+ reuse_last: bool,
2036
+ html_output: bool,
2037
+ no_cache: bool,
2038
+ display_cols: dict,
2039
+ ):
2040
+ """Prints an entire metagraph."""
2041
+ # TODO allow config to set certain columns
2042
+ if not reuse_last:
2043
+ cast("MeshtensorInterface", meshtensor)
2044
+ cast(int, netuid)
2045
+ with console.status(
2046
+ f":satellite: Syncing with chain: [white]{meshtensor.network}[/white] ...",
2047
+ spinner="aesthetic",
2048
+ ) as status:
2049
+ block_hash = await meshtensor.substrate.get_chain_head()
2050
+
2051
+ if not await meshtensor.subnet_exists(netuid, block_hash):
2052
+ print_error(f"Subnet with netuid: {netuid} does not exist", status)
2053
+ return False
2054
+
2055
+ (
2056
+ neurons,
2057
+ difficulty_,
2058
+ total_issuance_,
2059
+ block,
2060
+ subnet_state,
2061
+ ) = await asyncio.gather(
2062
+ meshtensor.neurons(netuid, block_hash=block_hash),
2063
+ meshtensor.get_hyperparameter(
2064
+ param_name="Difficulty", netuid=netuid, block_hash=block_hash
2065
+ ),
2066
+ meshtensor.query(
2067
+ module="MeshtensorModule",
2068
+ storage_function="TotalIssuance",
2069
+ params=[],
2070
+ block_hash=block_hash,
2071
+ ),
2072
+ meshtensor.substrate.get_block_number(block_hash=block_hash),
2073
+ meshtensor.get_subnet_state(netuid=netuid),
2074
+ )
2075
+
2076
+ difficulty = int(difficulty_)
2077
+ total_issuance = Balance.from_meshlet(total_issuance_)
2078
+ metagraph = MiniGraph(
2079
+ netuid=netuid,
2080
+ neurons=neurons,
2081
+ meshtensor=meshtensor,
2082
+ subnet_state=subnet_state,
2083
+ block=block,
2084
+ )
2085
+ table_data = []
2086
+ db_table = []
2087
+ total_global_stake = 0.0
2088
+ total_local_stake = 0.0
2089
+ total_rank = 0.0
2090
+ total_validator_trust = 0.0
2091
+ total_trust = 0.0
2092
+ total_consensus = 0.0
2093
+ total_incentive = 0.0
2094
+ total_dividends = 0.0
2095
+ total_emission = 0
2096
+ for uid in metagraph.uids:
2097
+ neuron = metagraph.neurons[uid]
2098
+ ep = metagraph.axons[uid]
2099
+ row = [
2100
+ str(neuron.uid),
2101
+ "{:.4f}".format(metagraph.global_stake[uid]),
2102
+ "{:.4f}".format(metagraph.local_stake[uid]),
2103
+ "{:.4f}".format(metagraph.stake_weights[uid]),
2104
+ "{:.5f}".format(metagraph.ranks[uid]),
2105
+ "{:.5f}".format(metagraph.trust[uid]),
2106
+ "{:.5f}".format(metagraph.consensus[uid]),
2107
+ "{:.5f}".format(metagraph.incentive[uid]),
2108
+ "{:.5f}".format(metagraph.dividends[uid]),
2109
+ "{}".format(int(metagraph.emission[uid] * 1000000000)),
2110
+ "{:.5f}".format(metagraph.validator_trust[uid]),
2111
+ "*" if metagraph.validator_permit[uid] else "",
2112
+ str(metagraph.block.item() - metagraph.last_update[uid].item()),
2113
+ str(metagraph.active[uid].item()),
2114
+ (
2115
+ ep.ip + ":" + str(ep.port)
2116
+ if ep.is_serving
2117
+ else "[light_goldenrod2]none[/light_goldenrod2]"
2118
+ ),
2119
+ ep.hotkey[:10],
2120
+ ep.coldkey[:10],
2121
+ ]
2122
+ db_row = [
2123
+ neuron.uid,
2124
+ float(metagraph.global_stake[uid]),
2125
+ float(metagraph.local_stake[uid]),
2126
+ float(metagraph.stake_weights[uid]),
2127
+ float(metagraph.ranks[uid]),
2128
+ float(metagraph.trust[uid]),
2129
+ float(metagraph.consensus[uid]),
2130
+ float(metagraph.incentive[uid]),
2131
+ float(metagraph.dividends[uid]),
2132
+ int(metagraph.emission[uid] * 1000000000),
2133
+ float(metagraph.validator_trust[uid]),
2134
+ bool(metagraph.validator_permit[uid]),
2135
+ metagraph.block.item() - metagraph.last_update[uid].item(),
2136
+ metagraph.active[uid].item(),
2137
+ (ep.ip + ":" + str(ep.port) if ep.is_serving else "ERROR"),
2138
+ ep.hotkey[:10],
2139
+ ep.coldkey[:10],
2140
+ ]
2141
+ db_table.append(db_row)
2142
+ total_global_stake += metagraph.global_stake[uid]
2143
+ total_local_stake += metagraph.local_stake[uid]
2144
+ total_rank += metagraph.ranks[uid]
2145
+ total_validator_trust += metagraph.validator_trust[uid]
2146
+ total_trust += metagraph.trust[uid]
2147
+ total_consensus += metagraph.consensus[uid]
2148
+ total_incentive += metagraph.incentive[uid]
2149
+ total_dividends += metagraph.dividends[uid]
2150
+ total_emission += int(metagraph.emission[uid] * 1000000000)
2151
+ table_data.append(row)
2152
+ metadata_info = {
2153
+ "total_global_stake": "\u03c4 {:.5f}".format(total_global_stake),
2154
+ "total_local_stake": f"{Balance.get_unit(netuid)} "
2155
+ + "{:.5f}".format(total_local_stake),
2156
+ "rank": "{:.5f}".format(total_rank),
2157
+ "validator_trust": "{:.5f}".format(total_validator_trust),
2158
+ "trust": "{:.5f}".format(total_trust),
2159
+ "consensus": "{:.5f}".format(total_consensus),
2160
+ "incentive": "{:.5f}".format(total_incentive),
2161
+ "dividends": "{:.5f}".format(total_dividends),
2162
+ "emission": "\u03c1{}".format(int(total_emission)),
2163
+ "net": f"{meshtensor.network}:{metagraph.netuid}",
2164
+ "block": str(metagraph.block.item()),
2165
+ "N": f"{sum(metagraph.active.tolist())}/{metagraph.n.item()}",
2166
+ "N0": str(sum(metagraph.active.tolist())),
2167
+ "N1": str(metagraph.n.item()),
2168
+ "issuance": str(total_issuance),
2169
+ "difficulty": str(difficulty),
2170
+ "total_neurons": str(len(metagraph.uids)),
2171
+ "table_data": json.dumps(table_data),
2172
+ }
2173
+ if not no_cache:
2174
+ update_metadata_table("metagraph", metadata_info)
2175
+ create_and_populate_table(
2176
+ "metagraph",
2177
+ columns=[
2178
+ ("UID", "INTEGER"),
2179
+ ("GLOBAL_STAKE", "REAL"),
2180
+ ("LOCAL_STAKE", "REAL"),
2181
+ ("STAKE_WEIGHT", "REAL"),
2182
+ ("RANK", "REAL"),
2183
+ ("TRUST", "REAL"),
2184
+ ("CONSENSUS", "REAL"),
2185
+ ("INCENTIVE", "REAL"),
2186
+ ("DIVIDENDS", "REAL"),
2187
+ ("EMISSION", "INTEGER"),
2188
+ ("VTRUST", "REAL"),
2189
+ ("VAL", "INTEGER"),
2190
+ ("UPDATED", "INTEGER"),
2191
+ ("ACTIVE", "INTEGER"),
2192
+ ("AXON", "TEXT"),
2193
+ ("HOTKEY", "TEXT"),
2194
+ ("COLDKEY", "TEXT"),
2195
+ ],
2196
+ rows=db_table,
2197
+ )
2198
+ else:
2199
+ try:
2200
+ metadata_info = get_metadata_table("metagraph")
2201
+ table_data = json.loads(metadata_info["table_data"])
2202
+ except sqlite3.OperationalError:
2203
+ print_error(
2204
+ "Error: Unable to retrieve table data. This is usually caused by attempting to use "
2205
+ "`--reuse-last` before running the command a first time. In rare cases, this could also be due to "
2206
+ "a corrupted database. Re-run the command (do not use `--reuse-last`) and see if that resolves your "
2207
+ "issue."
2208
+ )
2209
+ return
2210
+
2211
+ if html_output:
2212
+ try:
2213
+ render_table(
2214
+ table_name="metagraph",
2215
+ table_info=f"Metagraph | "
2216
+ f"net: {metadata_info['net']}, "
2217
+ f"block: {metadata_info['block']}, "
2218
+ f"N: {metadata_info['N']}, "
2219
+ f"stake: {metadata_info['stake']}, "
2220
+ f"issuance: {metadata_info['issuance']}, "
2221
+ f"difficulty: {metadata_info['difficulty']}",
2222
+ columns=[
2223
+ {"title": "UID", "field": "UID"},
2224
+ {
2225
+ "title": "Global Stake",
2226
+ "field": "GLOBAL_STAKE",
2227
+ "formatter": "money",
2228
+ "formatterParams": {"symbol": "τ", "precision": 5},
2229
+ },
2230
+ {
2231
+ "title": "Local Stake",
2232
+ "field": "LOCAL_STAKE",
2233
+ "formatter": "money",
2234
+ "formatterParams": {
2235
+ "symbol": f"{Balance.get_unit(netuid)}",
2236
+ "precision": 5,
2237
+ },
2238
+ },
2239
+ {
2240
+ "title": "Stake Weight",
2241
+ "field": "STAKE_WEIGHT",
2242
+ "formatter": "money",
2243
+ "formatterParams": {"precision": 5},
2244
+ },
2245
+ {
2246
+ "title": "Rank",
2247
+ "field": "RANK",
2248
+ "formatter": "money",
2249
+ "formatterParams": {"precision": 5},
2250
+ },
2251
+ {
2252
+ "title": "Trust",
2253
+ "field": "TRUST",
2254
+ "formatter": "money",
2255
+ "formatterParams": {"precision": 5},
2256
+ },
2257
+ {
2258
+ "title": "Consensus",
2259
+ "field": "CONSENSUS",
2260
+ "formatter": "money",
2261
+ "formatterParams": {"precision": 5},
2262
+ },
2263
+ {
2264
+ "title": "Incentive",
2265
+ "field": "INCENTIVE",
2266
+ "formatter": "money",
2267
+ "formatterParams": {"precision": 5},
2268
+ },
2269
+ {
2270
+ "title": "Dividends",
2271
+ "field": "DIVIDENDS",
2272
+ "formatter": "money",
2273
+ "formatterParams": {"precision": 5},
2274
+ },
2275
+ {"title": "Emission", "field": "EMISSION"},
2276
+ {
2277
+ "title": "VTrust",
2278
+ "field": "VTRUST",
2279
+ "formatter": "money",
2280
+ "formatterParams": {"precision": 5},
2281
+ },
2282
+ {"title": "Validated", "field": "VAL"},
2283
+ {"title": "Updated", "field": "UPDATED"},
2284
+ {"title": "Active", "field": "ACTIVE"},
2285
+ {"title": "Axon", "field": "AXON"},
2286
+ {"title": "Hotkey", "field": "HOTKEY"},
2287
+ {"title": "Coldkey", "field": "COLDKEY"},
2288
+ ],
2289
+ )
2290
+ except sqlite3.OperationalError:
2291
+ print_error(
2292
+ "Error: Unable to retrieve table data. This may indicate that your database is corrupted, "
2293
+ "or was not able to load with the most recent data."
2294
+ )
2295
+ return
2296
+ else:
2297
+ cols: dict[str, tuple[int, Column]] = {
2298
+ "UID": (
2299
+ 0,
2300
+ Column(
2301
+ "[bold white]UID",
2302
+ footer=f"[white]{metadata_info['total_neurons']}[/white]",
2303
+ style="white",
2304
+ justify="right",
2305
+ ratio=0.75,
2306
+ ),
2307
+ ),
2308
+ "GLOBAL_STAKE": (
2309
+ 1,
2310
+ Column(
2311
+ "[bold white]GLOBAL STAKE(\u03c4)",
2312
+ footer=metadata_info["total_global_stake"],
2313
+ style="bright_cyan",
2314
+ justify="right",
2315
+ no_wrap=True,
2316
+ ratio=1.6,
2317
+ ),
2318
+ ),
2319
+ "LOCAL_STAKE": (
2320
+ 2,
2321
+ Column(
2322
+ f"[bold white]LOCAL STAKE({Balance.get_unit(netuid)})",
2323
+ footer=metadata_info["total_local_stake"],
2324
+ style="bright_green",
2325
+ justify="right",
2326
+ no_wrap=True,
2327
+ ratio=1.5,
2328
+ ),
2329
+ ),
2330
+ "STAKE_WEIGHT": (
2331
+ 3,
2332
+ Column(
2333
+ f"[bold white]WEIGHT (\u03c4x{Balance.get_unit(netuid)})",
2334
+ style="purple",
2335
+ justify="right",
2336
+ no_wrap=True,
2337
+ ratio=1.3,
2338
+ ),
2339
+ ),
2340
+ "RANK": (
2341
+ 4,
2342
+ Column(
2343
+ "[bold white]RANK",
2344
+ footer=metadata_info["rank"],
2345
+ style="medium_purple",
2346
+ justify="right",
2347
+ no_wrap=True,
2348
+ ratio=1,
2349
+ ),
2350
+ ),
2351
+ "TRUST": (
2352
+ 5,
2353
+ Column(
2354
+ "[bold white]TRUST",
2355
+ footer=metadata_info["trust"],
2356
+ style="dark_sea_green",
2357
+ justify="right",
2358
+ no_wrap=True,
2359
+ ratio=1,
2360
+ ),
2361
+ ),
2362
+ "CONSENSUS": (
2363
+ 6,
2364
+ Column(
2365
+ "[bold white]CONSENSUS",
2366
+ footer=metadata_info["consensus"],
2367
+ style="rgb(42,161,152)",
2368
+ justify="right",
2369
+ no_wrap=True,
2370
+ ratio=1,
2371
+ ),
2372
+ ),
2373
+ "INCENTIVE": (
2374
+ 7,
2375
+ Column(
2376
+ "[bold white]INCENTIVE",
2377
+ footer=metadata_info["incentive"],
2378
+ style="#5fd7ff",
2379
+ justify="right",
2380
+ no_wrap=True,
2381
+ ratio=1,
2382
+ ),
2383
+ ),
2384
+ "DIVIDENDS": (
2385
+ 8,
2386
+ Column(
2387
+ "[bold white]DIVIDENDS",
2388
+ footer=metadata_info["dividends"],
2389
+ style="#8787d7",
2390
+ justify="right",
2391
+ no_wrap=True,
2392
+ ratio=1,
2393
+ ),
2394
+ ),
2395
+ "EMISSION": (
2396
+ 9,
2397
+ Column(
2398
+ "[bold white]EMISSION(\u03c1)",
2399
+ footer=metadata_info["emission"],
2400
+ style="#d7d7ff",
2401
+ justify="right",
2402
+ no_wrap=True,
2403
+ ratio=1.5,
2404
+ ),
2405
+ ),
2406
+ "VTRUST": (
2407
+ 10,
2408
+ Column(
2409
+ "[bold white]VTRUST",
2410
+ footer=metadata_info["validator_trust"],
2411
+ style="magenta",
2412
+ justify="right",
2413
+ no_wrap=True,
2414
+ ratio=1,
2415
+ ),
2416
+ ),
2417
+ "VAL": (
2418
+ 11,
2419
+ Column(
2420
+ "[bold white]VAL",
2421
+ justify="center",
2422
+ style="bright_white",
2423
+ no_wrap=True,
2424
+ ratio=0.7,
2425
+ ),
2426
+ ),
2427
+ "UPDATED": (
2428
+ 12,
2429
+ Column("[bold white]UPDATED", justify="right", no_wrap=True, ratio=1),
2430
+ ),
2431
+ "ACTIVE": (
2432
+ 13,
2433
+ Column(
2434
+ "[bold white]ACTIVE",
2435
+ justify="center",
2436
+ style="#8787ff",
2437
+ no_wrap=True,
2438
+ ratio=1,
2439
+ ),
2440
+ ),
2441
+ "AXON": (
2442
+ 14,
2443
+ Column(
2444
+ "[bold white]AXON",
2445
+ justify="left",
2446
+ style="dark_orange",
2447
+ overflow="fold",
2448
+ ratio=2,
2449
+ ),
2450
+ ),
2451
+ "HOTKEY": (
2452
+ 15,
2453
+ Column(
2454
+ "[bold white]HOTKEY",
2455
+ justify="center",
2456
+ style="bright_magenta",
2457
+ overflow="fold",
2458
+ ratio=1.5,
2459
+ ),
2460
+ ),
2461
+ "COLDKEY": (
2462
+ 16,
2463
+ Column(
2464
+ "[bold white]COLDKEY",
2465
+ justify="center",
2466
+ style="bright_magenta",
2467
+ overflow="fold",
2468
+ ratio=1.5,
2469
+ ),
2470
+ ),
2471
+ }
2472
+ table_cols: list[Column] = []
2473
+ table_cols_indices: list[int] = []
2474
+ for k, (idx, v) in cols.items():
2475
+ if display_cols[k] is True:
2476
+ table_cols_indices.append(idx)
2477
+ table_cols.append(v)
2478
+
2479
+ table = Table(
2480
+ *table_cols,
2481
+ show_footer=True,
2482
+ show_edge=False,
2483
+ header_style="bold white",
2484
+ border_style="bright_black",
2485
+ style="bold",
2486
+ title_style="bold white",
2487
+ title_justify="center",
2488
+ show_lines=False,
2489
+ expand=True,
2490
+ title=(
2491
+ f"[underline dark_orange]Metagraph[/underline dark_orange]\n\n"
2492
+ f"Net: [bright_cyan]{metadata_info['net']}[/bright_cyan], "
2493
+ f"Block: [bright_cyan]{metadata_info['block']}[/bright_cyan], "
2494
+ f"N: [bright_green]{metadata_info['N0']}[/bright_green]/[bright_red]{metadata_info['N1']}[/bright_red], "
2495
+ f"Total Local Stake: [dark_orange]{metadata_info['total_local_stake']}[/dark_orange], "
2496
+ f"Issuance: [bright_blue]{metadata_info['issuance']}[/bright_blue], "
2497
+ f"Difficulty: [bright_cyan]{metadata_info['difficulty']}[/bright_cyan]\n"
2498
+ ),
2499
+ pad_edge=True,
2500
+ )
2501
+
2502
+ if all(x is False for x in display_cols.values()):
2503
+ console.print("You have selected no columns to display in your config.")
2504
+ table.add_row(" " * 256) # allows title to be printed
2505
+ elif any(x is False for x in display_cols.values()):
2506
+ console.print(
2507
+ "Limiting column display output based on your config settings. Hiding columns "
2508
+ f"{', '.join([k for (k, v) in display_cols.items() if v is False])}"
2509
+ )
2510
+ for row in table_data:
2511
+ new_row = [row[idx] for idx in table_cols_indices]
2512
+ table.add_row(*new_row)
2513
+ else:
2514
+ for row in table_data:
2515
+ table.add_row(*row)
2516
+
2517
+ console.print(table)
2518
+
2519
+
2520
+ def create_identity_table(title: str = None):
2521
+ if not title:
2522
+ title = "Subnet Identity"
2523
+
2524
+ table = Table(
2525
+ Column(
2526
+ "Item",
2527
+ justify="right",
2528
+ style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"],
2529
+ no_wrap=True,
2530
+ ),
2531
+ Column("Value", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]),
2532
+ title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{title}\n",
2533
+ show_footer=True,
2534
+ show_edge=False,
2535
+ header_style="bold white",
2536
+ border_style="bright_black",
2537
+ style="bold",
2538
+ title_justify="center",
2539
+ show_lines=False,
2540
+ pad_edge=True,
2541
+ )
2542
+ return table
2543
+
2544
+
2545
+ async def set_identity(
2546
+ wallet: "Wallet",
2547
+ meshtensor: "MeshtensorInterface",
2548
+ netuid: int,
2549
+ subnet_identity: dict,
2550
+ prompt: bool = False,
2551
+ decline: bool = False,
2552
+ quiet: bool = False,
2553
+ proxy: Optional[str] = None,
2554
+ ) -> tuple[bool, Optional[str]]:
2555
+ """Set identity information for a subnet"""
2556
+
2557
+ if prompt and (logo_url := subnet_identity.get("logo_url")):
2558
+ sn_exists, img_validation = await asyncio.gather(
2559
+ meshtensor.subnet_exists(netuid),
2560
+ check_img_mimetype(subnet_identity["logo_url"]),
2561
+ )
2562
+ img_valid, content_type, err_msg = img_validation
2563
+ if not img_valid:
2564
+ confirmation_msg = f"Are you sure you want to use [blue]{logo_url}[/blue] as your image URL?"
2565
+ if err_msg:
2566
+ if not confirm_action(
2567
+ f"{err_msg}\n{confirmation_msg}",
2568
+ decline=decline,
2569
+ quiet=quiet,
2570
+ ):
2571
+ return False, None
2572
+ else:
2573
+ if not confirm_action(
2574
+ f"The provided image's MIME type is {content_type}, which is not recognized as a valid"
2575
+ f" image MIME type.\n{confirmation_msg}",
2576
+ decline=decline,
2577
+ quiet=quiet,
2578
+ ):
2579
+ return False, None
2580
+
2581
+ else:
2582
+ sn_exists = await meshtensor.subnet_exists(netuid)
2583
+
2584
+ if not sn_exists:
2585
+ print_error(f"Subnet {netuid} does not exist")
2586
+ return False, None
2587
+
2588
+ identity_data = {
2589
+ "netuid": netuid,
2590
+ "subnet_name": subnet_identity.get("subnet_name", ""),
2591
+ "github_repo": subnet_identity.get("github_repo", ""),
2592
+ "subnet_contact": subnet_identity.get("subnet_contact", ""),
2593
+ "subnet_url": subnet_identity.get("subnet_url", ""),
2594
+ "discord": subnet_identity.get("discord", ""),
2595
+ "description": subnet_identity.get("description", ""),
2596
+ "logo_url": subnet_identity.get("logo_url", ""),
2597
+ "additional": subnet_identity.get("additional", ""),
2598
+ }
2599
+
2600
+ if not unlock_key(wallet).success:
2601
+ return False, None
2602
+
2603
+ if prompt:
2604
+ if not confirm_action(
2605
+ "Are you sure you want to set subnet's identity? This is subject to a fee.",
2606
+ decline=decline,
2607
+ quiet=quiet,
2608
+ ):
2609
+ return False, None
2610
+
2611
+ call = await meshtensor.substrate.compose_call(
2612
+ call_module="MeshtensorModule",
2613
+ call_function="set_subnet_identity",
2614
+ call_params=identity_data,
2615
+ )
2616
+
2617
+ with console.status(
2618
+ " :satellite: [dark_sea_green3]Setting subnet identity on-chain...",
2619
+ spinner="earth",
2620
+ ):
2621
+ success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
2622
+ call, wallet, proxy=proxy
2623
+ )
2624
+
2625
+ if not success:
2626
+ print_error(f"Failed: {err_msg}")
2627
+ return False, None
2628
+ ext_id = await ext_receipt.get_extrinsic_identifier()
2629
+ await print_extrinsic_id(ext_receipt)
2630
+ print_success("[dark_sea_green3]Successfully set subnet identity\n")
2631
+
2632
+ subnet = await meshtensor.subnet(netuid)
2633
+ identity = subnet.subnet_identity if subnet else None
2634
+
2635
+ if identity:
2636
+ table = create_identity_table(title=f"New Subnet {netuid} Identity")
2637
+ table.add_row("Netuid", str(netuid))
2638
+ for key in [
2639
+ "subnet_name",
2640
+ "github_repo",
2641
+ "subnet_contact",
2642
+ "subnet_url",
2643
+ "discord",
2644
+ "description",
2645
+ "logo_url",
2646
+ "additional",
2647
+ ]:
2648
+ value = getattr(identity, key, None)
2649
+ table.add_row(key, str(value) if value else "~")
2650
+ console.print(table)
2651
+
2652
+ return True, ext_id
2653
+
2654
+
2655
+ async def get_identity(
2656
+ meshtensor: "MeshtensorInterface",
2657
+ netuid: int,
2658
+ title: str = None,
2659
+ json_output: bool = False,
2660
+ ) -> Optional[dict]:
2661
+ """Fetch and display existing subnet identity information."""
2662
+ if not title:
2663
+ title = f"Current Subnet {netuid} Identity"
2664
+
2665
+ if not await meshtensor.subnet_exists(netuid):
2666
+ print_error(f"Subnet {netuid} does not exist.")
2667
+ return None
2668
+
2669
+ with console.status(
2670
+ ":satellite: [bold green]Querying subnet identity...", spinner="earth"
2671
+ ):
2672
+ subnet = await meshtensor.subnet(netuid)
2673
+ identity = subnet.subnet_identity if subnet else None
2674
+
2675
+ if not identity:
2676
+ print_error(
2677
+ f"Existing subnet identity not found"
2678
+ f" for subnet [blue]{netuid}[/blue]"
2679
+ f" on {meshtensor}"
2680
+ )
2681
+ if json_output:
2682
+ json_console.print("{}")
2683
+ return {}
2684
+ else:
2685
+ table = create_identity_table(title=title)
2686
+ dict_out = {}
2687
+ table.add_row("Netuid", str(netuid))
2688
+ for key in [
2689
+ "subnet_name",
2690
+ "github_repo",
2691
+ "subnet_contact",
2692
+ "subnet_url",
2693
+ "discord",
2694
+ "description",
2695
+ "logo_url",
2696
+ "additional",
2697
+ ]:
2698
+ value = getattr(identity, key, None)
2699
+ table.add_row(key, str(value) if value else "~")
2700
+ dict_out[key] = value
2701
+ if json_output:
2702
+ json_console.print(json.dumps(dict_out))
2703
+ else:
2704
+ console.print(table)
2705
+ return identity
2706
+
2707
+
2708
+ async def get_start_schedule(
2709
+ meshtensor: "MeshtensorInterface",
2710
+ netuid: int,
2711
+ ) -> None:
2712
+ """Fetch and display existing emission schedule information."""
2713
+
2714
+ if not await meshtensor.subnet_exists(netuid):
2715
+ print_error(f"Subnet {netuid} does not exist.")
2716
+ return None
2717
+ block_hash = await meshtensor.substrate.get_chain_head()
2718
+ registration_block, min_blocks_to_start_, current_block = await asyncio.gather(
2719
+ meshtensor.query(
2720
+ module="MeshtensorModule",
2721
+ storage_function="NetworkRegisteredAt",
2722
+ params=[netuid],
2723
+ block_hash=block_hash,
2724
+ ),
2725
+ meshtensor.substrate.get_constant(
2726
+ module_name="MeshtensorModule",
2727
+ constant_name="InitialStartCallDelay",
2728
+ block_hash=block_hash,
2729
+ ),
2730
+ meshtensor.substrate.get_block_number(block_hash=block_hash),
2731
+ )
2732
+ min_blocks_to_start = getattr(min_blocks_to_start_, "value", min_blocks_to_start_)
2733
+
2734
+ potential_start_block = registration_block + min_blocks_to_start
2735
+ if current_block < potential_start_block:
2736
+ blocks_to_wait = potential_start_block - current_block
2737
+ time_to_wait = blocks_to_duration(blocks_to_wait)
2738
+
2739
+ console.print(
2740
+ f"[blue]Subnet {netuid}[/blue]:\n"
2741
+ f"[blue]Registered at:[/blue] {registration_block}\n"
2742
+ f"[blue]Minimum start block:[/blue] {potential_start_block}\n"
2743
+ f"[blue]Current block:[/blue] {current_block}\n"
2744
+ f"[blue]Blocks remaining:[/blue] {blocks_to_wait}\n"
2745
+ f"[blue]Time to wait:[/blue] {time_to_wait}"
2746
+ )
2747
+ else:
2748
+ console.print(
2749
+ f"[blue]Subnet {netuid}[/blue]:\n"
2750
+ f"[blue]Registered at:[/blue] {registration_block}\n"
2751
+ f"[blue]Current block:[/blue] {current_block}\n"
2752
+ f"[blue]Minimum start block:[/blue] {potential_start_block}\n"
2753
+ f"[dark_sea_green3]Emission schedule can be started[/dark_sea_green3]"
2754
+ )
2755
+
2756
+ return
2757
+
2758
+
2759
+ async def start_subnet(
2760
+ wallet: "Wallet",
2761
+ meshtensor: "MeshtensorInterface",
2762
+ netuid: int,
2763
+ proxy: Optional[str],
2764
+ prompt: bool = False,
2765
+ decline: bool = False,
2766
+ quiet: bool = False,
2767
+ ) -> bool:
2768
+ """Start a subnet's emission schedule"""
2769
+ coldkey_ss58 = proxy or wallet.coldkeypub.ss58_address
2770
+ if not await meshtensor.subnet_exists(netuid):
2771
+ print_error(f"Subnet {netuid} does not exist.")
2772
+ return False
2773
+
2774
+ subnet_owner = await meshtensor.query(
2775
+ module="MeshtensorModule",
2776
+ storage_function="SubnetOwner",
2777
+ params=[netuid],
2778
+ )
2779
+ # TODO should this check against proxy as well?
2780
+ if subnet_owner != coldkey_ss58:
2781
+ print_error("This wallet doesn't own the specified subnet.")
2782
+ return False
2783
+
2784
+ if prompt:
2785
+ if not confirm_action(
2786
+ f"Are you sure you want to start subnet {netuid}'s emission schedule?",
2787
+ decline=decline,
2788
+ quiet=quiet,
2789
+ ):
2790
+ return False
2791
+
2792
+ if not unlock_key(wallet).success:
2793
+ return False
2794
+
2795
+ with console.status(
2796
+ f":satellite: Starting subnet {netuid}'s emission schedule...", spinner="earth"
2797
+ ):
2798
+ start_call = await meshtensor.substrate.compose_call(
2799
+ call_module="MeshtensorModule",
2800
+ call_function="start_call",
2801
+ call_params={"netuid": netuid},
2802
+ )
2803
+ success, error_msg, response = await meshtensor.sign_and_send_extrinsic(
2804
+ call=start_call,
2805
+ wallet=wallet,
2806
+ wait_for_inclusion=True,
2807
+ wait_for_finalization=True,
2808
+ proxy=proxy,
2809
+ )
2810
+
2811
+ if success:
2812
+ await print_extrinsic_id(response)
2813
+ print_success(f"Successfully started subnet {netuid}'s emission schedule.")
2814
+ return True
2815
+ else:
2816
+ if "FirstEmissionBlockNumberAlreadySet" in error_msg:
2817
+ console.print(
2818
+ f"[dark_sea_green3]Subnet {netuid} already has an emission schedule.[/dark_sea_green3]"
2819
+ )
2820
+ return True
2821
+
2822
+ await get_start_schedule(meshtensor, netuid)
2823
+ print_error(f"Failed to start subnet: {error_msg}")
2824
+ return False
2825
+
2826
+
2827
+ async def set_symbol(
2828
+ wallet: "Wallet",
2829
+ meshtensor: "MeshtensorInterface",
2830
+ netuid: int,
2831
+ symbol: str,
2832
+ proxy: Optional[str],
2833
+ period: int,
2834
+ prompt: bool = False,
2835
+ decline: bool = False,
2836
+ quiet: bool = False,
2837
+ json_output: bool = False,
2838
+ ) -> bool:
2839
+ """
2840
+ Set a meshtensor's symbol, given the netuid and symbol.
2841
+
2842
+ The symbol must be a symbol that meshtensor recognizes as available
2843
+ (defined in https://github.com/opentensor/meshtensor/blob/main/pallets/meshtensor/src/subnets/symbols.rs#L8)
2844
+ """
2845
+ if not await meshtensor.subnet_exists(netuid):
2846
+ err = f"Subnet {netuid} does not exist."
2847
+ if json_output:
2848
+ json_console.print_json(
2849
+ data={"success": False, "message": err, "extrinsic_identifier": None}
2850
+ )
2851
+ else:
2852
+ print_error(err)
2853
+ return False
2854
+
2855
+ if prompt and not json_output:
2856
+ sn_info = await meshtensor.subnet(netuid=netuid)
2857
+ if not confirm_action(
2858
+ f"Your current subnet symbol for SN{netuid} is {sn_info.symbol}. Do you want to update it to {symbol}?",
2859
+ decline=decline,
2860
+ quiet=quiet,
2861
+ ):
2862
+ return False
2863
+
2864
+ if not (unlock_status := unlock_key(wallet, print_out=False)).success:
2865
+ err = unlock_status.message
2866
+ if json_output:
2867
+ json_console.print_json(data={"success": False, "message": err})
2868
+ else:
2869
+ console.print(err)
2870
+ return False
2871
+
2872
+ start_call = await meshtensor.substrate.compose_call(
2873
+ call_module="MeshtensorModule",
2874
+ call_function="update_symbol",
2875
+ call_params={"netuid": netuid, "symbol": symbol.encode("utf-8")},
2876
+ )
2877
+
2878
+ success, err_msg, response = await meshtensor.sign_and_send_extrinsic(
2879
+ call=start_call, wallet=wallet, proxy=proxy, era={"period": period}
2880
+ )
2881
+
2882
+ if success:
2883
+ ext_id = await response.get_extrinsic_identifier()
2884
+ await print_extrinsic_id(response)
2885
+ message = f"Successfully updated SN{netuid}'s symbol to {symbol}."
2886
+ if json_output:
2887
+ json_console.print_json(
2888
+ data={
2889
+ "success": True,
2890
+ "message": message,
2891
+ "extrinsic_identifier": ext_id,
2892
+ }
2893
+ )
2894
+ else:
2895
+ print_success(f"[dark_sea_green3] {message}\n")
2896
+ return True
2897
+ else:
2898
+ if json_output:
2899
+ json_console.print_json(
2900
+ data={
2901
+ "success": False,
2902
+ "message": err_msg,
2903
+ "extrinsic_identifier": None,
2904
+ }
2905
+ )
2906
+ else:
2907
+ print_error(f"Failed: {err_msg}")
2908
+ return False