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,794 @@
1
+ """
2
+ Governance commands for meshcli.
3
+
4
+ Provides commands for interacting with the Meshtensor on-chain governance system:
5
+ - Submit referenda
6
+ - Vote on active proposals
7
+ - Delegate voting power
8
+ - View governance status
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import sys
14
+ from typing import TYPE_CHECKING, Optional
15
+
16
+ from meshtensor_wallet import Wallet
17
+ from rich import box
18
+ from rich.table import Column, Table
19
+
20
+ from meshtensor_cli.src.meshtensor.utils import (
21
+ confirm_action,
22
+ console,
23
+ print_error,
24
+ print_success,
25
+ print_verbose,
26
+ unlock_key,
27
+ blocks_to_duration,
28
+ json_console,
29
+ print_extrinsic_id,
30
+ )
31
+
32
+ if TYPE_CHECKING:
33
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
34
+
35
+
36
+ # ============================================================================
37
+ # Constants
38
+ # ============================================================================
39
+
40
+ GOVERNANCE_TRACKS = {
41
+ 0: {"name": "Root", "description": "Runtime upgrades & consensus changes"},
42
+ 1: {"name": "Treasury", "description": "Fund allocation & grants"},
43
+ 2: {"name": "Param-Critical", "description": "Critical network parameters"},
44
+ 3: {"name": "Param-Standard", "description": "Standard network parameters"},
45
+ 4: {"name": "Subnet", "description": "Subnet-scoped governance"},
46
+ 5: {"name": "Emergency", "description": "Emergency actions (TC co-signed)"},
47
+ }
48
+
49
+ CONVICTION_LEVELS = {
50
+ 0: {"multiplier": "0.5x", "lock": "No lock"},
51
+ 1: {"multiplier": "1.0x", "lock": "1x enactment period"},
52
+ 2: {"multiplier": "1.5x", "lock": "2x enactment period"},
53
+ 3: {"multiplier": "2.0x", "lock": "4x enactment period"},
54
+ 4: {"multiplier": "2.5x", "lock": "8x enactment period"},
55
+ 5: {"multiplier": "3.0x", "lock": "16x enactment period"},
56
+ 6: {"multiplier": "3.5x", "lock": "32x enactment period"},
57
+ }
58
+
59
+
60
+ # ============================================================================
61
+ # meshcli governance propose
62
+ # ============================================================================
63
+
64
+ async def governance_propose(
65
+ subtensor: "MeshtensorInterface",
66
+ wallet: Wallet,
67
+ track: int,
68
+ call_data: str,
69
+ deposit: float,
70
+ enactment_delay: int,
71
+ description: str = "",
72
+ quiet: bool = False,
73
+ verbose: bool = False,
74
+ ):
75
+ """Submit a new referendum proposal."""
76
+
77
+ if track not in GOVERNANCE_TRACKS:
78
+ print_error(f"Invalid track: {track}. Must be 0-5.")
79
+ return
80
+
81
+ track_info = GOVERNANCE_TRACKS[track]
82
+ if not quiet:
83
+ console.print(
84
+ f"\n[bold]Submitting referendum on {track_info['name']} Track[/bold]"
85
+ )
86
+ console.print(f" Track: {track} ({track_info['description']})")
87
+ console.print(f" Deposit: {deposit} MESH")
88
+ console.print(f" Enactment delay: {enactment_delay} blocks")
89
+ if description:
90
+ console.print(f" Description: {description}")
91
+
92
+ if not confirm_action("Submit this referendum?"):
93
+ return
94
+
95
+ if not unlock_key(wallet):
96
+ return
97
+
98
+ try:
99
+ call = await subtensor.substrate.compose_call(
100
+ call_module="Referenda",
101
+ call_function="submit",
102
+ call_params={
103
+ "proposal_origin": {"system": "Root"} if track == 0 else {"Origins": f"Track{track}"},
104
+ "proposal": {"Lookup": {"hash": call_data, "len": 0}},
105
+ "enactment_moment": {"After": enactment_delay},
106
+ },
107
+ )
108
+
109
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
110
+ call=call, keypair=wallet.coldkey
111
+ )
112
+ response = await subtensor.substrate.submit_extrinsic(
113
+ extrinsic, wait_for_inclusion=True
114
+ )
115
+
116
+ if response.is_success:
117
+ print_success(f"Referendum submitted successfully on {track_info['name']} Track")
118
+ print_extrinsic_id(response)
119
+ else:
120
+ print_error(f"Failed to submit referendum: {response.error_message}")
121
+ except Exception as e:
122
+ print_error(f"Error submitting referendum: {e}")
123
+
124
+
125
+ # ============================================================================
126
+ # meshcli governance vote
127
+ # ============================================================================
128
+
129
+ async def governance_vote(
130
+ subtensor: "MeshtensorInterface",
131
+ wallet: Wallet,
132
+ ref_index: int,
133
+ vote: str,
134
+ conviction: int = 1,
135
+ quiet: bool = False,
136
+ verbose: bool = False,
137
+ ):
138
+ """Vote on an active referendum."""
139
+
140
+ if vote.lower() not in ("aye", "nay", "abstain"):
141
+ print_error("Vote must be 'aye', 'nay', or 'abstain'.")
142
+ return
143
+
144
+ if conviction not in CONVICTION_LEVELS:
145
+ print_error(f"Invalid conviction level: {conviction}. Must be 0-6.")
146
+ return
147
+
148
+ conv_info = CONVICTION_LEVELS[conviction]
149
+ if not quiet:
150
+ console.print(f"\n[bold]Voting on Referendum #{ref_index}[/bold]")
151
+ console.print(f" Vote: {vote.upper()}")
152
+ console.print(f" Conviction: {conviction} ({conv_info['multiplier']}, {conv_info['lock']})")
153
+
154
+ if not confirm_action("Confirm vote?"):
155
+ return
156
+
157
+ if not unlock_key(wallet):
158
+ return
159
+
160
+ try:
161
+ is_aye = vote.lower() == "aye"
162
+ conviction_value = f"Locked{conviction}x" if conviction > 0 else "None"
163
+
164
+ if vote.lower() == "abstain":
165
+ call = await subtensor.substrate.compose_call(
166
+ call_module="ConvictionVoting",
167
+ call_function="vote",
168
+ call_params={
169
+ "poll_index": ref_index,
170
+ "vote": {
171
+ "SplitAbstain": {
172
+ "aye": 0,
173
+ "nay": 0,
174
+ "abstain": 1,
175
+ }
176
+ },
177
+ },
178
+ )
179
+ else:
180
+ call = await subtensor.substrate.compose_call(
181
+ call_module="ConvictionVoting",
182
+ call_function="vote",
183
+ call_params={
184
+ "poll_index": ref_index,
185
+ "vote": {
186
+ "Standard": {
187
+ "vote": {"aye": is_aye, "conviction": conviction_value},
188
+ "balance": 0, # Use full available balance
189
+ }
190
+ },
191
+ },
192
+ )
193
+
194
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
195
+ call=call, keypair=wallet.coldkey
196
+ )
197
+ response = await subtensor.substrate.submit_extrinsic(
198
+ extrinsic, wait_for_inclusion=True
199
+ )
200
+
201
+ if response.is_success:
202
+ print_success(f"Vote cast successfully: {vote.upper()} on referendum #{ref_index}")
203
+ print_extrinsic_id(response)
204
+ else:
205
+ print_error(f"Failed to vote: {response.error_message}")
206
+ except Exception as e:
207
+ print_error(f"Error voting: {e}")
208
+
209
+
210
+ # ============================================================================
211
+ # meshcli governance delegate
212
+ # ============================================================================
213
+
214
+ async def governance_delegate(
215
+ subtensor: "MeshtensorInterface",
216
+ wallet: Wallet,
217
+ track: int,
218
+ delegatee: str,
219
+ conviction: int = 1,
220
+ quiet: bool = False,
221
+ verbose: bool = False,
222
+ ):
223
+ """Delegate voting power to another account for a specific track."""
224
+
225
+ if track not in GOVERNANCE_TRACKS:
226
+ print_error(f"Invalid track: {track}. Must be 0-5.")
227
+ return
228
+
229
+ if conviction not in CONVICTION_LEVELS:
230
+ print_error(f"Invalid conviction: {conviction}. Must be 0-6.")
231
+ return
232
+
233
+ track_info = GOVERNANCE_TRACKS[track]
234
+ conv_info = CONVICTION_LEVELS[conviction]
235
+
236
+ if not quiet:
237
+ console.print(f"\n[bold]Delegating Voting Power[/bold]")
238
+ console.print(f" Track: {track} ({track_info['name']})")
239
+ console.print(f" Delegatee: {delegatee}")
240
+ console.print(f" Conviction: {conviction} ({conv_info['multiplier']})")
241
+
242
+ if not confirm_action("Confirm delegation?"):
243
+ return
244
+
245
+ if not unlock_key(wallet):
246
+ return
247
+
248
+ try:
249
+ call = await subtensor.substrate.compose_call(
250
+ call_module="MeshtensorGovernance",
251
+ call_function="delegate",
252
+ call_params={
253
+ "track": track,
254
+ "delegatee": delegatee,
255
+ "conviction": conviction,
256
+ },
257
+ )
258
+
259
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
260
+ call=call, keypair=wallet.coldkey
261
+ )
262
+ response = await subtensor.substrate.submit_extrinsic(
263
+ extrinsic, wait_for_inclusion=True
264
+ )
265
+
266
+ if response.is_success:
267
+ print_success(
268
+ f"Delegated to {delegatee[:8]}...{delegatee[-8:]} on {track_info['name']} Track"
269
+ )
270
+ print_extrinsic_id(response)
271
+ else:
272
+ print_error(f"Failed to delegate: {response.error_message}")
273
+ except Exception as e:
274
+ print_error(f"Error delegating: {e}")
275
+
276
+
277
+ # ============================================================================
278
+ # meshcli governance undelegate
279
+ # ============================================================================
280
+
281
+ async def governance_undelegate(
282
+ subtensor: "MeshtensorInterface",
283
+ wallet: Wallet,
284
+ track: int,
285
+ quiet: bool = False,
286
+ verbose: bool = False,
287
+ ):
288
+ """Revoke a delegation for a specific track."""
289
+
290
+ if track not in GOVERNANCE_TRACKS:
291
+ print_error(f"Invalid track: {track}. Must be 0-5.")
292
+ return
293
+
294
+ track_info = GOVERNANCE_TRACKS[track]
295
+
296
+ if not quiet:
297
+ console.print(f"\n[bold]Revoking Delegation[/bold]")
298
+ console.print(f" Track: {track} ({track_info['name']})")
299
+
300
+ if not confirm_action("Revoke delegation?"):
301
+ return
302
+
303
+ if not unlock_key(wallet):
304
+ return
305
+
306
+ try:
307
+ call = await subtensor.substrate.compose_call(
308
+ call_module="MeshtensorGovernance",
309
+ call_function="undelegate",
310
+ call_params={"track": track},
311
+ )
312
+
313
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
314
+ call=call, keypair=wallet.coldkey
315
+ )
316
+ response = await subtensor.substrate.submit_extrinsic(
317
+ extrinsic, wait_for_inclusion=True
318
+ )
319
+
320
+ if response.is_success:
321
+ print_success(f"Delegation revoked on {track_info['name']} Track")
322
+ print_extrinsic_id(response)
323
+ else:
324
+ print_error(f"Failed to undelegate: {response.error_message}")
325
+ except Exception as e:
326
+ print_error(f"Error undelegating: {e}")
327
+
328
+
329
+ # ============================================================================
330
+ # meshcli governance list
331
+ # ============================================================================
332
+
333
+ async def governance_list(
334
+ subtensor: "MeshtensorInterface",
335
+ track: Optional[int] = None,
336
+ quiet: bool = False,
337
+ verbose: bool = False,
338
+ output_json: bool = False,
339
+ ):
340
+ """List active referenda, optionally filtered by track."""
341
+
342
+ try:
343
+ # Query all active referenda from pallet_referenda
344
+ referenda = await subtensor.substrate.query_map(
345
+ module="Referenda",
346
+ storage_function="ReferendumInfoFor",
347
+ )
348
+
349
+ results = []
350
+ async for ref_index, ref_info in referenda:
351
+ ref_data = ref_info.value if hasattr(ref_info, "value") else ref_info
352
+ if isinstance(ref_data, dict) and "Ongoing" in ref_data:
353
+ ongoing = ref_data["Ongoing"]
354
+ ref_track = ongoing.get("track", -1)
355
+ if track is not None and ref_track != track:
356
+ continue
357
+ results.append({
358
+ "index": ref_index.value if hasattr(ref_index, "value") else ref_index,
359
+ "track": ref_track,
360
+ "track_name": GOVERNANCE_TRACKS.get(ref_track, {}).get("name", "Unknown"),
361
+ "tally_ayes": ongoing.get("tally", {}).get("ayes", 0),
362
+ "tally_nays": ongoing.get("tally", {}).get("nays", 0),
363
+ "tally_support": ongoing.get("tally", {}).get("support", 0),
364
+ "submitted": ongoing.get("submitted", 0),
365
+ })
366
+
367
+ if output_json:
368
+ json_console.print_json(json.dumps(results))
369
+ return
370
+
371
+ if not results:
372
+ console.print("[dim]No active referenda found.[/dim]")
373
+ return
374
+
375
+ table = Table(
376
+ Column("Index", style="bold cyan"),
377
+ Column("Track", style="bold"),
378
+ Column("Ayes", style="green"),
379
+ Column("Nays", style="red"),
380
+ Column("Support", style="yellow"),
381
+ Column("Submitted", style="dim"),
382
+ title="Active Referenda",
383
+ box=box.ROUNDED,
384
+ )
385
+
386
+ for ref in sorted(results, key=lambda r: r["index"]):
387
+ table.add_row(
388
+ str(ref["index"]),
389
+ f"{ref['track_name']} ({ref['track']})",
390
+ str(ref["tally_ayes"]),
391
+ str(ref["tally_nays"]),
392
+ str(ref["tally_support"]),
393
+ str(ref["submitted"]),
394
+ )
395
+
396
+ console.print(table)
397
+ console.print(f"\n[dim]Total: {len(results)} active referenda[/dim]")
398
+
399
+ except Exception as e:
400
+ print_error(f"Error listing referenda: {e}")
401
+
402
+
403
+ # ============================================================================
404
+ # meshcli governance info
405
+ # ============================================================================
406
+
407
+ async def governance_info(
408
+ subtensor: "MeshtensorInterface",
409
+ ref_index: int,
410
+ quiet: bool = False,
411
+ verbose: bool = False,
412
+ output_json: bool = False,
413
+ ):
414
+ """Show detailed information about a specific referendum."""
415
+
416
+ try:
417
+ ref_info = await subtensor.substrate.query(
418
+ module="Referenda",
419
+ storage_function="ReferendumInfoFor",
420
+ params=[ref_index],
421
+ )
422
+
423
+ if ref_info is None:
424
+ print_error(f"Referendum #{ref_index} not found.")
425
+ return
426
+
427
+ ref_data = ref_info.value if hasattr(ref_info, "value") else ref_info
428
+
429
+ if output_json:
430
+ json_console.print_json(json.dumps(ref_data, default=str))
431
+ return
432
+
433
+ console.print(f"\n[bold]Referendum #{ref_index}[/bold]")
434
+ console.print(f" Status: {list(ref_data.keys())[0] if isinstance(ref_data, dict) else 'Unknown'}")
435
+
436
+ if isinstance(ref_data, dict) and "Ongoing" in ref_data:
437
+ ongoing = ref_data["Ongoing"]
438
+ track_id = ongoing.get("track", -1)
439
+ track_info = GOVERNANCE_TRACKS.get(track_id, {"name": "Unknown"})
440
+
441
+ console.print(f" Track: {track_info['name']} ({track_id})")
442
+ console.print(f" Submitted at block: {ongoing.get('submitted', 'N/A')}")
443
+
444
+ tally = ongoing.get("tally", {})
445
+ console.print(f" Ayes: {tally.get('ayes', 0)}")
446
+ console.print(f" Nays: {tally.get('nays', 0)}")
447
+ console.print(f" Support: {tally.get('support', 0)}")
448
+
449
+ except Exception as e:
450
+ print_error(f"Error fetching referendum info: {e}")
451
+
452
+
453
+ # ============================================================================
454
+ # meshcli governance power
455
+ # ============================================================================
456
+
457
+ async def governance_power(
458
+ subtensor: "MeshtensorInterface",
459
+ account: str,
460
+ quiet: bool = False,
461
+ verbose: bool = False,
462
+ output_json: bool = False,
463
+ ):
464
+ """Show voting power breakdown for an account."""
465
+
466
+ try:
467
+ # Query contribution score from MeshtensorGovernance pallet
468
+ contribution_score = await subtensor.substrate.query(
469
+ module="MeshtensorGovernance",
470
+ storage_function="ContributionScore",
471
+ params=[account],
472
+ )
473
+
474
+ # Query stake deposit block for duration bonus
475
+ stake_deposit = await subtensor.substrate.query(
476
+ module="MeshtensorGovernance",
477
+ storage_function="StakeDepositBlock",
478
+ params=[account],
479
+ )
480
+
481
+ # Query total staked balance
482
+ # This would need to read from the staking storage
483
+ stake_info = await subtensor.substrate.query(
484
+ module="MeshtensorModule",
485
+ storage_function="TotalColdkeyStake",
486
+ params=[account],
487
+ )
488
+
489
+ raw_stake = stake_info.value if stake_info else 0
490
+ cw = contribution_score.value if contribution_score else 0
491
+
492
+ if output_json:
493
+ data = {
494
+ "account": account,
495
+ "raw_stake": raw_stake,
496
+ "contribution_score": cw,
497
+ "stake_deposit_block": stake_deposit.value if stake_deposit else None,
498
+ }
499
+ json_console.print_json(json.dumps(data, default=str))
500
+ return
501
+
502
+ console.print(f"\n[bold]Voting Power Breakdown[/bold]")
503
+ console.print(f" Account: {account[:8]}...{account[-8:]}")
504
+ console.print(f" Raw Stake: {raw_stake / 1e9:.4f} MESH")
505
+ console.print(f" Contribution Score: {cw}/1000")
506
+ console.print(
507
+ f" Stake Since Block: {stake_deposit.value if stake_deposit else 'N/A'}"
508
+ )
509
+ console.print()
510
+
511
+ # Show per-track VP estimates
512
+ table = Table(
513
+ Column("Track", style="bold"),
514
+ Column("α (Stake)", style="cyan"),
515
+ Column("β (Contrib)", style="green"),
516
+ Column("γ (Activity)", style="yellow"),
517
+ title="Track Coefficients",
518
+ box=box.SIMPLE,
519
+ )
520
+ track_coefficients = [
521
+ (0, "Root", 0.35, 0.35, 0.30),
522
+ (1, "Treasury", 0.35, 0.40, 0.25),
523
+ (2, "Param-Crit", 0.30, 0.45, 0.25),
524
+ (3, "Param-Std", 0.30, 0.45, 0.25),
525
+ (4, "Subnet", 0.25, 0.50, 0.25),
526
+ (5, "Emergency", 0.25, 0.30, 0.45),
527
+ ]
528
+ for tid, name, a, b, g in track_coefficients:
529
+ table.add_row(f"{name} ({tid})", f"{a:.2f}", f"{b:.2f}", f"{g:.2f}")
530
+
531
+ console.print(table)
532
+
533
+ except Exception as e:
534
+ print_error(f"Error fetching voting power: {e}")
535
+
536
+
537
+ # ============================================================================
538
+ # meshcli governance tracks
539
+ # ============================================================================
540
+
541
+ async def governance_tracks(
542
+ subtensor: "MeshtensorInterface",
543
+ quiet: bool = False,
544
+ verbose: bool = False,
545
+ output_json: bool = False,
546
+ ):
547
+ """Show governance track configuration."""
548
+
549
+ tracks_data = [
550
+ {"id": 0, "name": "Root", "approval": "66%", "support": "20%",
551
+ "decision": "28 days", "enactment": "14 days", "deposit": "10,000 MESH"},
552
+ {"id": 1, "name": "Treasury", "approval": "55%", "support": "15%",
553
+ "decision": "14 days", "enactment": "7 days", "deposit": "1,000 MESH"},
554
+ {"id": 2, "name": "Param-Critical", "approval": "60%", "support": "15%",
555
+ "decision": "14 days", "enactment": "7 days", "deposit": "5,000 MESH"},
556
+ {"id": 3, "name": "Param-Standard", "approval": "55%", "support": "15%",
557
+ "decision": "7 days", "enactment": "3 days", "deposit": "1,000 MESH"},
558
+ {"id": 4, "name": "Subnet", "approval": "50%", "support": "10%",
559
+ "decision": "7 days", "enactment": "2 days", "deposit": "100 MESH"},
560
+ {"id": 5, "name": "Emergency", "approval": "75%", "support": "25%",
561
+ "decision": "3 days", "enactment": "6 hours", "deposit": "TC-waived"},
562
+ ]
563
+
564
+ if output_json:
565
+ json_console.print_json(json.dumps(tracks_data))
566
+ return
567
+
568
+ table = Table(
569
+ Column("ID", style="bold"),
570
+ Column("Track", style="bold cyan"),
571
+ Column("Approval", style="green"),
572
+ Column("Support", style="yellow"),
573
+ Column("Decision", style="dim"),
574
+ Column("Enactment", style="dim"),
575
+ Column("Deposit", style="magenta"),
576
+ title="Governance Tracks",
577
+ box=box.ROUNDED,
578
+ )
579
+
580
+ for t in tracks_data:
581
+ table.add_row(
582
+ str(t["id"]),
583
+ t["name"],
584
+ t["approval"],
585
+ t["support"],
586
+ t["decision"],
587
+ t["enactment"],
588
+ t["deposit"],
589
+ )
590
+
591
+ console.print(table)
592
+ console.print("\n[dim]All deposits include a 10% non-refundable burn component.[/dim]")
593
+
594
+
595
+ # ============================================================================
596
+ # meshcli governance history
597
+ # ============================================================================
598
+
599
+ async def governance_history(
600
+ subtensor: "MeshtensorInterface",
601
+ limit: int = 20,
602
+ quiet: bool = False,
603
+ verbose: bool = False,
604
+ output_json: bool = False,
605
+ ):
606
+ """Show recent referendum history."""
607
+
608
+ try:
609
+ referenda = await subtensor.substrate.query_map(
610
+ module="Referenda",
611
+ storage_function="ReferendumInfoFor",
612
+ )
613
+
614
+ results = []
615
+ async for ref_index, ref_info in referenda:
616
+ ref_data = ref_info.value if hasattr(ref_info, "value") else ref_info
617
+ idx = ref_index.value if hasattr(ref_index, "value") else ref_index
618
+ if isinstance(ref_data, dict):
619
+ status = list(ref_data.keys())[0]
620
+ results.append({"index": idx, "status": status, "data": ref_data})
621
+
622
+ results.sort(key=lambda r: r["index"], reverse=True)
623
+ results = results[:limit]
624
+
625
+ if output_json:
626
+ json_console.print_json(json.dumps(results, default=str))
627
+ return
628
+
629
+ if not results:
630
+ console.print("[dim]No referendum history found.[/dim]")
631
+ return
632
+
633
+ table = Table(
634
+ Column("Index", style="bold"),
635
+ Column("Status", style="bold"),
636
+ title=f"Referendum History (last {limit})",
637
+ box=box.ROUNDED,
638
+ )
639
+
640
+ for ref in results:
641
+ status = ref["status"]
642
+ style = {
643
+ "Ongoing": "yellow",
644
+ "Approved": "green",
645
+ "Rejected": "red",
646
+ "Cancelled": "dim",
647
+ "TimedOut": "dim red",
648
+ "Killed": "bold red",
649
+ }.get(status, "white")
650
+ table.add_row(str(ref["index"]), f"[{style}]{status}[/{style}]")
651
+
652
+ console.print(table)
653
+
654
+ except Exception as e:
655
+ print_error(f"Error fetching referendum history: {e}")
656
+
657
+
658
+ # ============================================================================
659
+ # meshcli governance signal-golr
660
+ # ============================================================================
661
+
662
+ async def governance_signal_golr(
663
+ subtensor: "MeshtensorInterface",
664
+ wallet: Wallet,
665
+ quiet: bool = False,
666
+ verbose: bool = False,
667
+ ):
668
+ """Signal support for Governance of Last Resort (GoLR) activation.
669
+
670
+ Only callable by validators when governance has been paralyzed for 180+ days.
671
+ When 80% of validators signal, reduced threshold mode activates, halving
672
+ approval thresholds on all tracks.
673
+ """
674
+
675
+ if not quiet:
676
+ console.print(f"\n[bold]Signaling GoLR Activation[/bold]")
677
+ console.print(f" [dim]GoLR activates when 80% of validators signal[/dim]")
678
+ console.print(f" [dim]Requires 180+ days of governance paralysis[/dim]")
679
+
680
+ if not confirm_action("Signal GoLR activation?"):
681
+ return
682
+
683
+ if not unlock_key(wallet):
684
+ return
685
+
686
+ try:
687
+ call = await subtensor.substrate.compose_call(
688
+ call_module="MeshtensorGovernance",
689
+ call_function="signal_golr",
690
+ call_params={},
691
+ )
692
+
693
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
694
+ call=call, keypair=wallet.coldkey
695
+ )
696
+ response = await subtensor.substrate.submit_extrinsic(
697
+ extrinsic, wait_for_inclusion=True
698
+ )
699
+
700
+ if response.is_success:
701
+ print_success("GoLR signal submitted successfully")
702
+ print_extrinsic_id(response)
703
+ else:
704
+ print_error(f"Failed to signal GoLR: {response.error_message}")
705
+ except Exception as e:
706
+ print_error(f"Error signaling GoLR: {e}")
707
+
708
+
709
+ # ============================================================================
710
+ # meshcli governance update-scores
711
+ # ============================================================================
712
+
713
+ async def governance_update_scores(
714
+ subtensor: "MeshtensorInterface",
715
+ wallet: Wallet,
716
+ quiet: bool = False,
717
+ verbose: bool = False,
718
+ ):
719
+ """Trigger contribution score recalculation for the current epoch.
720
+
721
+ This is a permissionless call — anyone can trigger the computation.
722
+ """
723
+
724
+ if not quiet:
725
+ console.print(f"\n[bold]Updating Contribution Scores[/bold]")
726
+
727
+ if not unlock_key(wallet):
728
+ return
729
+
730
+ try:
731
+ call = await subtensor.substrate.compose_call(
732
+ call_module="MeshtensorGovernance",
733
+ call_function="update_contribution_scores",
734
+ call_params={},
735
+ )
736
+
737
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
738
+ call=call, keypair=wallet.coldkey
739
+ )
740
+ response = await subtensor.substrate.submit_extrinsic(
741
+ extrinsic, wait_for_inclusion=True
742
+ )
743
+
744
+ if response.is_success:
745
+ print_success("Contribution scores updated")
746
+ print_extrinsic_id(response)
747
+ else:
748
+ print_error(f"Failed to update scores: {response.error_message}")
749
+ except Exception as e:
750
+ print_error(f"Error updating scores: {e}")
751
+
752
+
753
+ # ============================================================================
754
+ # meshcli governance distribute-rewards
755
+ # ============================================================================
756
+
757
+ async def governance_distribute_rewards(
758
+ subtensor: "MeshtensorInterface",
759
+ wallet: Wallet,
760
+ quiet: bool = False,
761
+ verbose: bool = False,
762
+ ):
763
+ """Distribute governance rewards from the reward pool.
764
+
765
+ This is a permissionless call — anyone can trigger the distribution.
766
+ """
767
+
768
+ if not quiet:
769
+ console.print(f"\n[bold]Distributing Governance Rewards[/bold]")
770
+
771
+ if not unlock_key(wallet):
772
+ return
773
+
774
+ try:
775
+ call = await subtensor.substrate.compose_call(
776
+ call_module="MeshtensorGovernance",
777
+ call_function="distribute_governance_rewards",
778
+ call_params={},
779
+ )
780
+
781
+ extrinsic = await subtensor.substrate.create_signed_extrinsic(
782
+ call=call, keypair=wallet.coldkey
783
+ )
784
+ response = await subtensor.substrate.submit_extrinsic(
785
+ extrinsic, wait_for_inclusion=True
786
+ )
787
+
788
+ if response.is_success:
789
+ print_success("Governance rewards distributed")
790
+ print_extrinsic_id(response)
791
+ else:
792
+ print_error(f"Failed to distribute rewards: {response.error_message}")
793
+ except Exception as e:
794
+ print_error(f"Error distributing rewards: {e}")