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,738 @@
1
+ import asyncio
2
+ import json
3
+ from collections import defaultdict
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from meshtensor_wallet import Wallet
7
+ from rich.prompt import Prompt
8
+ from rich.table import Table
9
+ from rich import box
10
+ from rich.progress import Progress, BarColumn, TextColumn
11
+ from rich.console import Group
12
+ from rich.live import Live
13
+
14
+ from meshtensor_cli.src import COLOR_PALETTE
15
+ from meshtensor_cli.src.meshtensor.balances import Balance
16
+ from meshtensor_cli.src.meshtensor.chain_data import StakeInfo
17
+ from meshtensor_cli.src.meshtensor.utils import (
18
+ console,
19
+ print_error,
20
+ millify_tao,
21
+ get_subnet_name,
22
+ json_console,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
27
+
28
+
29
+ async def stake_list(
30
+ wallet: Wallet,
31
+ coldkey_ss58: str,
32
+ meshtensor: "MeshtensorInterface",
33
+ live: bool = False,
34
+ verbose: bool = False,
35
+ prompt: bool = False,
36
+ json_output: bool = False,
37
+ ):
38
+ coldkey_address = coldkey_ss58 if coldkey_ss58 else wallet.coldkeypub.ss58_address
39
+
40
+ async def get_stake_data(block_hash_: str = None):
41
+ (
42
+ sub_stakes_,
43
+ registered_delegate_info_,
44
+ _dynamic_info,
45
+ ) = await asyncio.gather(
46
+ meshtensor.get_stake_for_coldkey(
47
+ coldkey_ss58=coldkey_address, block_hash=block_hash_
48
+ ),
49
+ meshtensor.get_delegate_identities(block_hash=block_hash_),
50
+ meshtensor.all_subnets(block_hash=block_hash_),
51
+ )
52
+
53
+ claimable_amounts_ = {}
54
+ if sub_stakes_:
55
+ claimable_amounts_ = await meshtensor.get_claimable_stakes_for_coldkey(
56
+ coldkey_ss58=coldkey_address,
57
+ stakes_info=sub_stakes_,
58
+ block_hash=block_hash_,
59
+ )
60
+ # sub_stakes = substakes[coldkey_address]
61
+ dynamic_info__ = {info.netuid: info for info in _dynamic_info}
62
+ return (
63
+ sub_stakes_,
64
+ registered_delegate_info_,
65
+ dynamic_info__,
66
+ claimable_amounts_,
67
+ )
68
+
69
+ def define_table(
70
+ hotkey_name_: str,
71
+ rows: list[list[str]],
72
+ total_tao_value_: Balance,
73
+ total_swapped_tao_value_: Balance,
74
+ ):
75
+ title = f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {hotkey_name_}\nNetwork: {meshtensor.network}\n\n"
76
+ # TODO: Add hint back in after adding columns descriptions
77
+ # if not live:
78
+ # title += f"[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n"
79
+ defined_table = Table(
80
+ title=title,
81
+ show_footer=True,
82
+ show_edge=False,
83
+ header_style="bold white",
84
+ border_style="bright_black",
85
+ style="bold",
86
+ title_justify="center",
87
+ show_lines=False,
88
+ pad_edge=True,
89
+ )
90
+ defined_table.add_column(
91
+ "[white]Netuid",
92
+ footer=f"{len(rows)}",
93
+ footer_style="overline white",
94
+ style="grey89",
95
+ )
96
+ defined_table.add_column(
97
+ "[white]Name",
98
+ style="cyan",
99
+ justify="left",
100
+ no_wrap=True,
101
+ )
102
+ defined_table.add_column(
103
+ f"[white]Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})",
104
+ footer_style="overline white",
105
+ style=COLOR_PALETTE["STAKE"]["MESH"],
106
+ justify="right",
107
+ footer=f"τ {millify_tao(total_tao_value_.tao)}"
108
+ if not verbose
109
+ else f"{total_tao_value_}",
110
+ )
111
+ defined_table.add_column(
112
+ f"[white]Stake ({Balance.get_unit(1)})",
113
+ footer_style="overline white",
114
+ style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
115
+ justify="center",
116
+ )
117
+ defined_table.add_column(
118
+ f"[white]Price \n({Balance.unit}_in/{Balance.get_unit(1)}_in)",
119
+ footer_style="white",
120
+ style=COLOR_PALETTE["POOLS"]["RATE"],
121
+ justify="center",
122
+ )
123
+ # defined_table.add_column(
124
+ # f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
125
+ # footer_style="overline white",
126
+ # style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
127
+ # justify="right",
128
+ # footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}"
129
+ # if not verbose
130
+ # else f"{total_swapped_tao_value_}",
131
+ # )
132
+ defined_table.add_column(
133
+ "[white]Registered",
134
+ style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
135
+ justify="right",
136
+ )
137
+ defined_table.add_column(
138
+ f"[white]Emission \n({Balance.get_unit(1)}/block)",
139
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
140
+ justify="right",
141
+ )
142
+ defined_table.add_column(
143
+ f"[white]Emission \n({Balance.get_unit(0)}/block)",
144
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
145
+ justify="right",
146
+ )
147
+ defined_table.add_column(
148
+ f"[white]Claimable \n({Balance.get_unit(1)})",
149
+ style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
150
+ justify="right",
151
+ )
152
+ return defined_table
153
+
154
+ def create_table(
155
+ hotkey_: str,
156
+ substakes_: list[StakeInfo],
157
+ claimable_amounts_: dict[str, dict[int, Balance]],
158
+ ):
159
+ name_ = (
160
+ f"{registered_delegate_info[hotkey_].display} ({hotkey_})"
161
+ if hotkey_ in registered_delegate_info
162
+ else hotkey_
163
+ )
164
+ rows = []
165
+ total_tao_value_ = Balance(0)
166
+ total_swapped_tao_value_ = Balance(0)
167
+ root_stakes = [s for s in substakes_ if s.netuid == 0]
168
+ other_stakes = sorted(
169
+ [s for s in substakes_ if s.netuid != 0],
170
+ key=lambda x: dynamic_info[x.netuid]
171
+ .alpha_to_tao(Balance.from_meshlet(int(x.stake.meshlet)).set_unit(x.netuid))
172
+ .tao,
173
+ reverse=True,
174
+ )
175
+ sorted_substakes = root_stakes + other_stakes
176
+ substakes_values = []
177
+ for substake_ in sorted_substakes:
178
+ netuid = substake_.netuid
179
+ pool = dynamic_info[netuid]
180
+ symbol = f"{Balance.get_unit(netuid)}\u200e"
181
+
182
+ # Alpha value cell
183
+ alpha_value = Balance.from_meshlet(int(substake_.stake.meshlet)).set_unit(netuid)
184
+
185
+ # MESH value cell
186
+ tao_value_ = pool.alpha_to_tao(alpha_value)
187
+ total_tao_value_ += tao_value_
188
+
189
+ # MESH value cell
190
+ tao_value_ = pool.alpha_to_tao(substake_.stake)
191
+ total_swapped_tao_value_ += tao_value_
192
+
193
+ if netuid == 0:
194
+ swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]"
195
+ else:
196
+ swap_value = (
197
+ f"τ {millify_tao(tao_value_.tao)}"
198
+ if not verbose
199
+ else f"{tao_value_}"
200
+ )
201
+
202
+ # Per block emission cell
203
+ per_block_emission = substake_.emission.tao / (pool.tempo or 1)
204
+ per_block_tao_emission = substake_.tao_emission.tao / (pool.tempo or 1)
205
+ # Alpha ownership and MESH ownership cells
206
+ if alpha_value.tao > 0.00009:
207
+ stake_value = (
208
+ millify_tao(substake_.stake.tao)
209
+ if not verbose
210
+ else f"{substake_.stake.tao:,.4f}"
211
+ )
212
+ subnet_name = get_subnet_name(dynamic_info[netuid])
213
+ subnet_name_cell = f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}] {subnet_name}"
214
+
215
+ # Claimable amount cell
216
+ claimable_amount = Balance.from_meshlet(0)
217
+ if (
218
+ hotkey_ in claimable_amounts_
219
+ and netuid in claimable_amounts_[hotkey_]
220
+ ):
221
+ claimable_amount = claimable_amounts_[hotkey_][netuid]
222
+
223
+ if claimable_amount.tao > 0.00001:
224
+ claimable_cell = (
225
+ f"{claimable_amount.tao:.5f} {symbol}"
226
+ if not verbose
227
+ else f"{claimable_amount}"
228
+ )
229
+ else:
230
+ claimable_cell = "-"
231
+
232
+ rows.append(
233
+ [
234
+ str(netuid), # Number
235
+ subnet_name_cell, # Symbol + name
236
+ f"τ {millify_tao(tao_value_.tao)}"
237
+ if not verbose
238
+ else f"{tao_value_}", # Value (α x τ/α)
239
+ f"{stake_value} {symbol}"
240
+ if netuid != 0
241
+ else f"{symbol} {stake_value}", # Stake (a)
242
+ f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a)
243
+ # f"τ {millify_tao(tao_ownership.tao)}" if not verbose else f"{tao_ownership}", # MESH equiv
244
+ # swap_value, # Swap(α) -> τ
245
+ "YES"
246
+ if substake_.is_registered
247
+ else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered
248
+ str(Balance.from_tao(per_block_emission).set_unit(netuid)),
249
+ # Removing this flag for now, TODO: Confirm correct values are here w.r.t CHKs
250
+ # if substake_.is_registered
251
+ # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block)
252
+ str(Balance.from_tao(per_block_tao_emission)),
253
+ claimable_cell, # Claimable amount
254
+ ]
255
+ )
256
+ substakes_values.append(
257
+ {
258
+ "netuid": netuid,
259
+ "subnet_name": subnet_name,
260
+ "value": tao_value_.tao,
261
+ "stake_value": substake_.stake.tao,
262
+ "rate": pool.price.tao,
263
+ # "swap_value": swap_value,
264
+ "registered": True if substake_.is_registered else False,
265
+ "emission": {
266
+ "alpha": per_block_emission,
267
+ "mesh": per_block_tao_emission,
268
+ },
269
+ "claimable": claimable_amount.tao,
270
+ }
271
+ )
272
+ created_table = define_table(
273
+ name_, rows, total_tao_value_, total_swapped_tao_value_
274
+ )
275
+ for row in rows:
276
+ created_table.add_row(*row)
277
+ console.print(created_table)
278
+ return total_tao_value_, total_swapped_tao_value_, substakes_values
279
+
280
+ def create_live_table(
281
+ substakes: list,
282
+ dynamic_info_for_lt: dict,
283
+ hotkey_name_: str,
284
+ claimable_amounts_: dict,
285
+ previous_data_: Optional[dict] = None,
286
+ ) -> tuple[Table, dict]:
287
+ rows = []
288
+ current_data_ = {}
289
+
290
+ total_tao_value_ = Balance(0)
291
+ total_swapped_tao_value_ = Balance(0)
292
+
293
+ def format_cell(
294
+ value,
295
+ previous_value,
296
+ unit="",
297
+ unit_first_=False,
298
+ precision=4,
299
+ millify=False,
300
+ ):
301
+ if previous_value is not None:
302
+ change = value - previous_value
303
+ if abs(change) > 10 ** (-precision):
304
+ formatted_change = (
305
+ f"{change:.{precision}f}"
306
+ if not millify
307
+ else f"{millify_tao(change)}"
308
+ )
309
+ change_text = (
310
+ f" [pale_green3](+{formatted_change})[/pale_green3]"
311
+ if change > 0
312
+ else f" [hot_pink3]({formatted_change})[/hot_pink3]"
313
+ )
314
+ else:
315
+ change_text = ""
316
+ else:
317
+ change_text = ""
318
+ formatted_value = (
319
+ f"{value:,.{precision}f}" if not millify else f"{millify_tao(value)}"
320
+ )
321
+ return (
322
+ f"{formatted_value} {unit}{change_text}"
323
+ if not unit_first_
324
+ else f"{unit} {formatted_value}{change_text}"
325
+ )
326
+
327
+ # Sort subnets by value
328
+ root_stakes = [s for s in substakes if s.netuid == 0]
329
+ other_stakes = sorted(
330
+ [s for s in substakes if s.netuid != 0],
331
+ key=lambda x: dynamic_info_for_lt[x.netuid]
332
+ .alpha_to_tao(Balance.from_meshlet(int(x.stake.meshlet)).set_unit(x.netuid))
333
+ .tao,
334
+ reverse=True,
335
+ )
336
+ sorted_substakes = root_stakes + other_stakes
337
+
338
+ # Process each stake
339
+ for substake_ in sorted_substakes:
340
+ netuid = substake_.netuid
341
+ pool = dynamic_info_for_lt.get(netuid)
342
+ if substake_.stake.meshlet == 0 or not pool:
343
+ continue
344
+
345
+ # Calculate base values
346
+ symbol = f"{Balance.get_unit(netuid)}\u200e"
347
+ alpha_value = Balance.from_meshlet(int(substake_.stake.meshlet)).set_unit(netuid)
348
+ tao_value_ = pool.alpha_to_tao(alpha_value)
349
+ total_tao_value_ += tao_value_
350
+ swapped_tao_value_ = pool.alpha_to_tao(substake_.stake)
351
+ total_swapped_tao_value_ += swapped_tao_value_
352
+
353
+ # Store current values for future delta tracking
354
+ current_data_[netuid] = {
355
+ "stake": alpha_value.tao,
356
+ "price": pool.price.tao,
357
+ "tao_value": tao_value_.tao,
358
+ "swapped_value": swapped_tao_value_.tao,
359
+ "emission": substake_.emission.tao / (pool.tempo or 1),
360
+ "tao_emission": substake_.tao_emission.tao / (pool.tempo or 1),
361
+ }
362
+
363
+ # Get previous values for delta tracking
364
+ prev = previous_data_.get(netuid, {}) if previous_data_ else {}
365
+ unit_first = True if netuid == 0 else False
366
+
367
+ stake_cell = format_cell(
368
+ alpha_value.tao,
369
+ prev.get("stake"),
370
+ unit=symbol,
371
+ unit_first_=unit_first,
372
+ precision=4,
373
+ millify=True if not verbose else False,
374
+ )
375
+
376
+ rate_cell = format_cell(
377
+ pool.price.tao,
378
+ prev.get("price"),
379
+ unit=f"τ/{symbol}",
380
+ unit_first_=False,
381
+ precision=5,
382
+ millify=True if not verbose else False,
383
+ )
384
+
385
+ exchange_cell = format_cell(
386
+ tao_value_.tao,
387
+ prev.get("tao_value"),
388
+ unit="τ",
389
+ unit_first_=True,
390
+ precision=4,
391
+ millify=True if not verbose else False,
392
+ )
393
+
394
+ if netuid != 0:
395
+ swap_cell = format_cell(
396
+ swapped_tao_value_.tao,
397
+ prev.get("swapped_value"),
398
+ unit="τ",
399
+ unit_first_=True,
400
+ precision=4,
401
+ millify=True if not verbose else False,
402
+ )
403
+ else:
404
+ swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]"
405
+
406
+ emission_value = substake_.emission.tao / (pool.tempo or 1)
407
+ emission_cell = format_cell(
408
+ emission_value,
409
+ prev.get("emission"),
410
+ unit=symbol,
411
+ unit_first_=unit_first,
412
+ precision=4,
413
+ )
414
+
415
+ tao_emission_value = substake_.tao_emission.tao / (pool.tempo or 1)
416
+ tao_emission_cell = format_cell(
417
+ tao_emission_value,
418
+ prev.get("tao_emission"),
419
+ unit="τ",
420
+ unit_first_=unit_first,
421
+ precision=4,
422
+ )
423
+
424
+ subnet_name_cell = (
425
+ f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
426
+ f" {get_subnet_name(dynamic_info_for_lt[netuid])}"
427
+ )
428
+
429
+ # Claimable amount cell
430
+ hotkey_ss58 = substake_.hotkey_ss58
431
+ claimable_amount = Balance.from_meshlet(0)
432
+ if (
433
+ hotkey_ss58 in claimable_amounts_
434
+ and netuid in claimable_amounts_[hotkey_ss58]
435
+ ):
436
+ claimable_amount = claimable_amounts_[hotkey_ss58][netuid]
437
+
438
+ current_data_[netuid]["claimable"] = claimable_amount.tao
439
+
440
+ claimable_cell = format_cell(
441
+ claimable_amount.tao,
442
+ prev.get("claimable"),
443
+ unit=symbol,
444
+ unit_first_=unit_first,
445
+ precision=5,
446
+ millify=True if not verbose else False,
447
+ )
448
+
449
+ rows.append(
450
+ [
451
+ str(netuid), # Netuid
452
+ subnet_name_cell,
453
+ exchange_cell, # Exchange value
454
+ stake_cell, # Stake amount
455
+ rate_cell, # Rate
456
+ # swap_cell, # Swap value
457
+ "YES"
458
+ if substake_.is_registered
459
+ else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status
460
+ emission_cell, # Emission rate
461
+ tao_emission_cell, # MESH emission rate
462
+ claimable_cell, # Claimable amount
463
+ ]
464
+ )
465
+
466
+ live_table = define_table(
467
+ hotkey_name_, rows, total_tao_value_, total_swapped_tao_value_
468
+ )
469
+
470
+ for row in rows:
471
+ live_table.add_row(*row)
472
+
473
+ return live_table, current_data_
474
+
475
+ # Main execution
476
+ block_hash = await meshtensor.substrate.get_chain_head()
477
+ (
478
+ (
479
+ sub_stakes,
480
+ registered_delegate_info,
481
+ dynamic_info,
482
+ claimable_amounts,
483
+ ),
484
+ balance,
485
+ ) = await asyncio.gather(
486
+ get_stake_data(block_hash),
487
+ meshtensor.get_balance(coldkey_address, block_hash=block_hash),
488
+ )
489
+
490
+ # Iterate over substakes and aggregate them by hotkey.
491
+ hotkeys_to_substakes: dict[str, list[StakeInfo]] = defaultdict(list)
492
+
493
+ for substake in sub_stakes:
494
+ if substake.stake.meshlet != 0:
495
+ hotkeys_to_substakes[substake.hotkey_ss58].append(substake)
496
+
497
+ if not hotkeys_to_substakes:
498
+ print_error(f"No stakes found for coldkey ss58: ({coldkey_address})")
499
+ return
500
+
501
+ if live:
502
+ # Select one hotkey for live monitoring
503
+ if len(hotkeys_to_substakes) > 1:
504
+ console.print(
505
+ "\n[bold]Multiple hotkeys found. Please select one for live monitoring:[/bold]"
506
+ )
507
+ for idx, hotkey in enumerate(hotkeys_to_substakes.keys()):
508
+ name = (
509
+ f"{registered_delegate_info[hotkey].display} ({hotkey})"
510
+ if hotkey in registered_delegate_info
511
+ else hotkey
512
+ )
513
+ console.print(f"[{idx}] [{COLOR_PALETTE['GENERAL']['HEADER']}]{name}")
514
+
515
+ selected_idx = Prompt.ask(
516
+ "Enter hotkey index",
517
+ choices=[str(i) for i in range(len(hotkeys_to_substakes))],
518
+ )
519
+ selected_hotkey = list(hotkeys_to_substakes.keys())[int(selected_idx)]
520
+ else:
521
+ selected_hotkey = list(hotkeys_to_substakes.keys())[0]
522
+
523
+ hotkey_name = (
524
+ f"{registered_delegate_info[selected_hotkey].display} ({selected_hotkey})"
525
+ if selected_hotkey in registered_delegate_info
526
+ else selected_hotkey
527
+ )
528
+
529
+ refresh_interval = 10 # seconds
530
+ progress = Progress(
531
+ TextColumn("[progress.description]{task.description}"),
532
+ BarColumn(bar_width=20),
533
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
534
+ console=console,
535
+ )
536
+ progress_task = progress.add_task("Updating: ", total=refresh_interval)
537
+
538
+ previous_block = None
539
+ current_block = None
540
+ previous_data = None
541
+
542
+ with Live(console=console, auto_refresh=True) as live:
543
+ try:
544
+ while True:
545
+ block_hash = await meshtensor.substrate.get_chain_head()
546
+ (
547
+ sub_stakes,
548
+ registered_delegate_info,
549
+ dynamic_info_,
550
+ claimable_amounts_live,
551
+ ) = await get_stake_data(block_hash)
552
+ selected_stakes = [
553
+ stake
554
+ for stake in sub_stakes
555
+ if stake.hotkey_ss58 == selected_hotkey
556
+ ]
557
+
558
+ block_number = await meshtensor.substrate.get_block_number(None)
559
+
560
+ previous_block = current_block
561
+ current_block = block_number
562
+ new_blocks = (
563
+ "N/A"
564
+ if previous_block is None
565
+ else str(current_block - previous_block)
566
+ )
567
+
568
+ table, current_data = create_live_table(
569
+ selected_stakes,
570
+ dynamic_info_,
571
+ hotkey_name,
572
+ claimable_amounts_live,
573
+ previous_data,
574
+ )
575
+
576
+ previous_data = current_data
577
+ progress.reset(progress_task)
578
+ start_time = asyncio.get_event_loop().time()
579
+
580
+ block_info = (
581
+ f"Previous: [dark_sea_green]{previous_block}[/dark_sea_green] "
582
+ f"Current: [dark_sea_green]{current_block}[/dark_sea_green] "
583
+ f"Diff: [dark_sea_green]{new_blocks}[/dark_sea_green]"
584
+ )
585
+
586
+ message = f"\nLive stake view - Press [bold red]Ctrl+C[/bold red] to exit\n{block_info}"
587
+ live_render = Group(message, progress, table)
588
+ live.update(live_render)
589
+
590
+ while not progress.finished:
591
+ await asyncio.sleep(0.1)
592
+ elapsed = asyncio.get_event_loop().time() - start_time
593
+ progress.update(
594
+ progress_task, completed=min(elapsed, refresh_interval)
595
+ )
596
+
597
+ except KeyboardInterrupt:
598
+ console.print("\n[bold]Stopped live updates[/bold]")
599
+ return
600
+
601
+ else:
602
+ # Iterate over each hotkey and make a table
603
+ counter = 0
604
+ num_hotkeys = len(hotkeys_to_substakes)
605
+ all_hks_swapped_tao_value = Balance(0)
606
+ all_hks_tao_value = Balance(0)
607
+ dict_output = {
608
+ "stake_info": {},
609
+ "coldkey_address": coldkey_address,
610
+ "network": meshtensor.network,
611
+ "free_balance": 0.0,
612
+ "total_tao_value": 0.0,
613
+ "total_swapped_tao_value": 0.0,
614
+ }
615
+ for hotkey, substakes in hotkeys_to_substakes.items():
616
+ counter += 1
617
+ tao_value, swapped_tao_value, substake_values_ = create_table(
618
+ hotkey, substakes, claimable_amounts
619
+ )
620
+ dict_output["stake_info"][hotkey] = substake_values_
621
+ all_hks_tao_value += tao_value
622
+ all_hks_swapped_tao_value += swapped_tao_value
623
+
624
+ if num_hotkeys > 1 and counter < num_hotkeys and prompt and not json_output:
625
+ console.print("\nPress Enter to continue to the next hotkey...")
626
+ input()
627
+
628
+ total_tao_value = (
629
+ f"τ {millify_tao(all_hks_tao_value.tao + balance.tao)}"
630
+ if not verbose
631
+ else all_hks_tao_value + balance
632
+ )
633
+ total_swapped_tao_value = (
634
+ f"τ {millify_tao(all_hks_swapped_tao_value.tao)}"
635
+ if not verbose
636
+ else all_hks_swapped_tao_value
637
+ )
638
+ console.print("\n\n")
639
+ console.print(
640
+ f"Wallet:\n"
641
+ f" Coldkey SS58: "
642
+ f"[{COLOR_PALETTE.G.CK}]{coldkey_address}[/{COLOR_PALETTE.G.CK}]\n"
643
+ f" Free Balance: "
644
+ f"[{COLOR_PALETTE.G.BALANCE}]{balance}[/{COLOR_PALETTE.G.BALANCE}]\n"
645
+ f" Total MESH Swapped Value ({Balance.unit}): "
646
+ f"[{COLOR_PALETTE.G.BALANCE}]{total_swapped_tao_value}[/{COLOR_PALETTE.G.BALANCE}]\n"
647
+ f" Total MESH Value (including free balance) ({Balance.unit}): "
648
+ f"[{COLOR_PALETTE.G.BALANCE}]{total_tao_value}[/{COLOR_PALETTE.G.BALANCE}]\n"
649
+ )
650
+ dict_output["free_balance"] = balance.tao
651
+ dict_output["total_tao_value"] = all_hks_tao_value.tao + balance.tao
652
+ dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao
653
+ if json_output:
654
+ json_console.print(json.dumps(dict_output))
655
+ if not sub_stakes:
656
+ console.print(
657
+ f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})"
658
+ )
659
+ else:
660
+ # TODO: Temporarily returning till we update docs
661
+ return
662
+ display_table = Prompt.ask(
663
+ "\nPress Enter to view column descriptions or type 'q' to skip:",
664
+ choices=["", "q"],
665
+ default="",
666
+ show_choices=True,
667
+ ).lower()
668
+
669
+ if display_table == "q":
670
+ console.print(
671
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped."
672
+ )
673
+ else:
674
+ header = """
675
+ [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows:
676
+ """
677
+ console.print(header)
678
+ description_table = Table(
679
+ show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True
680
+ )
681
+
682
+ fields = [
683
+ ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."),
684
+ (
685
+ "[bold tan]Symbol[/bold tan]",
686
+ "The symbol for the subnet's dynamic MESH token.",
687
+ ),
688
+ (
689
+ "[bold tan]Stake (α)[/bold tan]",
690
+ "The stake amount this hotkey holds in the subnet, expressed in subnet's alpha token currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#staking[/blue].",
691
+ ),
692
+ (
693
+ "[bold tan]MESH Reserves (τ_in)[/bold tan]",
694
+ '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].',
695
+ ),
696
+ (
697
+ "[bold tan]Alpha Reserves (α_in)[/bold tan]",
698
+ "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].",
699
+ ),
700
+ (
701
+ "[bold tan]RATE (τ_in/α_in)[/bold tan]",
702
+ "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].",
703
+ ),
704
+ (
705
+ "[bold tan]Alpha out (α_out)[/bold tan]",
706
+ "Total stake in the subnet, expressed in 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",
707
+ ),
708
+ (
709
+ "[bold tan]MESH Equiv (τ_in x α/α_out)[/bold tan]",
710
+ 'MESH-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (MESH Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "meshcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#local-weight-or-tao-equiv-%CF%84_in-x-%CE%B1%CE%B1_out[/blue].',
711
+ ),
712
+ (
713
+ "[bold tan]Exchange Value (α x τ/α)[/bold tan]",
714
+ "This is the potential τ you will receive if you unstake from this hotkey now on this subnet. Note: The MESH Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].",
715
+ ),
716
+ # (
717
+ # "[bold tan]Swap (α → τ)[/bold tan]",
718
+ # "This is the τ you will receive if you unstake from this hotkey now on this subnet. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].",
719
+ # ),
720
+ (
721
+ "[bold tan]Registered[/bold tan]",
722
+ "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.meshtensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].",
723
+ ),
724
+ (
725
+ "[bold tan]Emission (α/block)[/bold tan]",
726
+ "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.meshtensor.com/dynamic-tao/dtao-guide#emissions[/blue].",
727
+ ),
728
+ ]
729
+
730
+ description_table.add_column(
731
+ "Field",
732
+ no_wrap=True,
733
+ style="bold tan",
734
+ )
735
+ description_table.add_column("Description", overflow="fold")
736
+ for field_name, description in fields:
737
+ description_table.add_row(field_name, description)
738
+ console.print(description_table)