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.
- meshtensor_cli/__init__.py +22 -0
- meshtensor_cli/cli.py +10742 -0
- meshtensor_cli/doc_generation_helper.py +4 -0
- meshtensor_cli/src/__init__.py +1085 -0
- meshtensor_cli/src/commands/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/axon.py +132 -0
- meshtensor_cli/src/commands/crowd/__init__.py +0 -0
- meshtensor_cli/src/commands/crowd/contribute.py +621 -0
- meshtensor_cli/src/commands/crowd/contributors.py +200 -0
- meshtensor_cli/src/commands/crowd/create.py +783 -0
- meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
- meshtensor_cli/src/commands/crowd/refund.py +233 -0
- meshtensor_cli/src/commands/crowd/update.py +418 -0
- meshtensor_cli/src/commands/crowd/utils.py +124 -0
- meshtensor_cli/src/commands/crowd/view.py +991 -0
- meshtensor_cli/src/commands/governance/__init__.py +0 -0
- meshtensor_cli/src/commands/governance/governance.py +794 -0
- meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
- meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
- meshtensor_cli/src/commands/liquidity/utils.py +202 -0
- meshtensor_cli/src/commands/proxy.py +700 -0
- meshtensor_cli/src/commands/stake/__init__.py +0 -0
- meshtensor_cli/src/commands/stake/add.py +799 -0
- meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
- meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
- meshtensor_cli/src/commands/stake/claim.py +770 -0
- meshtensor_cli/src/commands/stake/list.py +738 -0
- meshtensor_cli/src/commands/stake/move.py +1211 -0
- meshtensor_cli/src/commands/stake/remove.py +1466 -0
- meshtensor_cli/src/commands/stake/wizard.py +323 -0
- meshtensor_cli/src/commands/subnets/__init__.py +0 -0
- meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
- meshtensor_cli/src/commands/subnets/price.py +733 -0
- meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
- meshtensor_cli/src/commands/sudo.py +1294 -0
- meshtensor_cli/src/commands/tc/__init__.py +0 -0
- meshtensor_cli/src/commands/tc/tc.py +190 -0
- meshtensor_cli/src/commands/treasury/__init__.py +0 -0
- meshtensor_cli/src/commands/treasury/treasury.py +194 -0
- meshtensor_cli/src/commands/view.py +354 -0
- meshtensor_cli/src/commands/wallets.py +2311 -0
- meshtensor_cli/src/commands/weights.py +467 -0
- meshtensor_cli/src/meshtensor/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/balances.py +313 -0
- meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
- meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
- meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
- meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
- meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
- meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
- meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
- meshtensor_cli/src/meshtensor/minigraph.py +254 -0
- meshtensor_cli/src/meshtensor/networking.py +12 -0
- meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
- meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
- meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
- meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
- meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
- meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
- meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
- meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
- meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
- meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
- meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
- meshtensor_cli/src/meshtensor/utils.py +2007 -0
- meshtensor_cli/version.py +23 -0
- meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
- meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
- meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
- 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}")
|