nexaroa 0.0.111__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.
- neuroshard/__init__.py +93 -0
- neuroshard/__main__.py +4 -0
- neuroshard/cli.py +466 -0
- neuroshard/core/__init__.py +92 -0
- neuroshard/core/consensus/verifier.py +252 -0
- neuroshard/core/crypto/__init__.py +20 -0
- neuroshard/core/crypto/ecdsa.py +392 -0
- neuroshard/core/economics/__init__.py +52 -0
- neuroshard/core/economics/constants.py +387 -0
- neuroshard/core/economics/ledger.py +2111 -0
- neuroshard/core/economics/market.py +975 -0
- neuroshard/core/economics/wallet.py +168 -0
- neuroshard/core/governance/__init__.py +74 -0
- neuroshard/core/governance/proposal.py +561 -0
- neuroshard/core/governance/registry.py +545 -0
- neuroshard/core/governance/versioning.py +332 -0
- neuroshard/core/governance/voting.py +453 -0
- neuroshard/core/model/__init__.py +30 -0
- neuroshard/core/model/dynamic.py +4186 -0
- neuroshard/core/model/llm.py +905 -0
- neuroshard/core/model/registry.py +164 -0
- neuroshard/core/model/scaler.py +387 -0
- neuroshard/core/model/tokenizer.py +568 -0
- neuroshard/core/network/__init__.py +56 -0
- neuroshard/core/network/connection_pool.py +72 -0
- neuroshard/core/network/dht.py +130 -0
- neuroshard/core/network/dht_plan.py +55 -0
- neuroshard/core/network/dht_proof_store.py +516 -0
- neuroshard/core/network/dht_protocol.py +261 -0
- neuroshard/core/network/dht_service.py +506 -0
- neuroshard/core/network/encrypted_channel.py +141 -0
- neuroshard/core/network/nat.py +201 -0
- neuroshard/core/network/nat_traversal.py +695 -0
- neuroshard/core/network/p2p.py +929 -0
- neuroshard/core/network/p2p_data.py +150 -0
- neuroshard/core/swarm/__init__.py +106 -0
- neuroshard/core/swarm/aggregation.py +729 -0
- neuroshard/core/swarm/buffers.py +643 -0
- neuroshard/core/swarm/checkpoint.py +709 -0
- neuroshard/core/swarm/compute.py +624 -0
- neuroshard/core/swarm/diloco.py +844 -0
- neuroshard/core/swarm/factory.py +1288 -0
- neuroshard/core/swarm/heartbeat.py +669 -0
- neuroshard/core/swarm/logger.py +487 -0
- neuroshard/core/swarm/router.py +658 -0
- neuroshard/core/swarm/service.py +640 -0
- neuroshard/core/training/__init__.py +29 -0
- neuroshard/core/training/checkpoint.py +600 -0
- neuroshard/core/training/distributed.py +1602 -0
- neuroshard/core/training/global_tracker.py +617 -0
- neuroshard/core/training/production.py +276 -0
- neuroshard/governance_cli.py +729 -0
- neuroshard/grpc_server.py +895 -0
- neuroshard/runner.py +3223 -0
- neuroshard/sdk/__init__.py +92 -0
- neuroshard/sdk/client.py +990 -0
- neuroshard/sdk/errors.py +101 -0
- neuroshard/sdk/types.py +282 -0
- neuroshard/tracker/__init__.py +0 -0
- neuroshard/tracker/server.py +864 -0
- neuroshard/ui/__init__.py +0 -0
- neuroshard/ui/app.py +102 -0
- neuroshard/ui/templates/index.html +1052 -0
- neuroshard/utils/__init__.py +0 -0
- neuroshard/utils/autostart.py +81 -0
- neuroshard/utils/hardware.py +121 -0
- neuroshard/utils/serialization.py +90 -0
- neuroshard/version.py +1 -0
- nexaroa-0.0.111.dist-info/METADATA +283 -0
- nexaroa-0.0.111.dist-info/RECORD +78 -0
- nexaroa-0.0.111.dist-info/WHEEL +5 -0
- nexaroa-0.0.111.dist-info/entry_points.txt +4 -0
- nexaroa-0.0.111.dist-info/licenses/LICENSE +190 -0
- nexaroa-0.0.111.dist-info/top_level.txt +2 -0
- protos/__init__.py +0 -0
- protos/neuroshard.proto +651 -0
- protos/neuroshard_pb2.py +160 -0
- protos/neuroshard_pb2_grpc.py +1298 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
NeuroShard Governance CLI
|
|
4
|
+
|
|
5
|
+
Commands for participating in decentralized governance:
|
|
6
|
+
- List proposals
|
|
7
|
+
- View proposal details
|
|
8
|
+
- Create new proposals
|
|
9
|
+
- Vote on proposals
|
|
10
|
+
- Check voting results
|
|
11
|
+
|
|
12
|
+
Authentication:
|
|
13
|
+
- Uses wallet token (same as neuroshard-node)
|
|
14
|
+
- Token can be 64-char hex or 12-word mnemonic
|
|
15
|
+
- Derives ECDSA keypair for signing proposals/votes
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
# Add parent to path for imports
|
|
26
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
27
|
+
|
|
28
|
+
from neuroshard.version import __version__
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Lazy imports to speed up CLI startup
|
|
32
|
+
def get_governance_modules():
|
|
33
|
+
"""Import governance modules lazily."""
|
|
34
|
+
from neuroshard.core.governance import (
|
|
35
|
+
NEP, NEPType, NEPStatus, create_proposal, EconomicImpact,
|
|
36
|
+
NEPRegistry, get_active_neps, get_pending_neps,
|
|
37
|
+
cast_vote, get_vote_tally, VoteResult,
|
|
38
|
+
)
|
|
39
|
+
from neuroshard.core.governance.voting import VoteChoice, VotingSystem
|
|
40
|
+
return {
|
|
41
|
+
'NEP': NEP,
|
|
42
|
+
'NEPType': NEPType,
|
|
43
|
+
'NEPStatus': NEPStatus,
|
|
44
|
+
'create_proposal': create_proposal,
|
|
45
|
+
'EconomicImpact': EconomicImpact,
|
|
46
|
+
'NEPRegistry': NEPRegistry,
|
|
47
|
+
'get_active_neps': get_active_neps,
|
|
48
|
+
'get_pending_neps': get_pending_neps,
|
|
49
|
+
'cast_vote': cast_vote,
|
|
50
|
+
'get_vote_tally': get_vote_tally,
|
|
51
|
+
'VoteResult': VoteResult,
|
|
52
|
+
'VoteChoice': VoteChoice,
|
|
53
|
+
'VotingSystem': VotingSystem,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_crypto_module():
|
|
58
|
+
"""Import crypto module lazily."""
|
|
59
|
+
from neuroshard.core.crypto.ecdsa import NodeCrypto, derive_keypair_from_token
|
|
60
|
+
return {
|
|
61
|
+
'NodeCrypto': NodeCrypto,
|
|
62
|
+
'derive_keypair_from_token': derive_keypair_from_token,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
NEUROSHARD_DIR = os.path.expanduser("~/.neuroshard")
|
|
67
|
+
TOKEN_FILE = os.path.join(NEUROSHARD_DIR, "wallet_token")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resolve_token(token_arg: str = None) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Resolve wallet token from various sources (in priority order):
|
|
73
|
+
1. --token argument
|
|
74
|
+
2. NEUROSHARD_TOKEN environment variable
|
|
75
|
+
3. ~/.neuroshard/wallet_token file
|
|
76
|
+
|
|
77
|
+
Handles both 64-char hex and 12-word mnemonic formats.
|
|
78
|
+
|
|
79
|
+
Returns the resolved token or None.
|
|
80
|
+
"""
|
|
81
|
+
token = None
|
|
82
|
+
source = None
|
|
83
|
+
|
|
84
|
+
# Priority 1: Command line argument
|
|
85
|
+
if token_arg:
|
|
86
|
+
token = token_arg
|
|
87
|
+
source = "command line"
|
|
88
|
+
|
|
89
|
+
# Priority 2: Environment variable
|
|
90
|
+
elif os.getenv("NEUROSHARD_TOKEN"):
|
|
91
|
+
token = os.getenv("NEUROSHARD_TOKEN")
|
|
92
|
+
source = "NEUROSHARD_TOKEN env"
|
|
93
|
+
|
|
94
|
+
# Priority 3: Token file
|
|
95
|
+
elif os.path.exists(TOKEN_FILE):
|
|
96
|
+
try:
|
|
97
|
+
with open(TOKEN_FILE, 'r') as f:
|
|
98
|
+
token = f.read().strip()
|
|
99
|
+
source = TOKEN_FILE
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
if not token:
|
|
104
|
+
return None, None
|
|
105
|
+
|
|
106
|
+
# Handle mnemonic (12 words)
|
|
107
|
+
words = token.strip().split()
|
|
108
|
+
if len(words) == 12:
|
|
109
|
+
try:
|
|
110
|
+
from mnemonic import Mnemonic
|
|
111
|
+
mnemo = Mnemonic("english")
|
|
112
|
+
if mnemo.check(token):
|
|
113
|
+
seed = mnemo.to_seed(token, passphrase="")
|
|
114
|
+
token = seed[:32].hex()
|
|
115
|
+
source = f"{source} (mnemonic)"
|
|
116
|
+
except ImportError:
|
|
117
|
+
print("[WARNING] 'mnemonic' package not installed, treating as raw token")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"[WARNING] Mnemonic error: {e}")
|
|
120
|
+
|
|
121
|
+
return token, source
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_wallet(token: str):
|
|
125
|
+
"""
|
|
126
|
+
Initialize wallet from token.
|
|
127
|
+
|
|
128
|
+
Returns (node_id, crypto) tuple where:
|
|
129
|
+
- node_id: Your unique identifier (derived from public key)
|
|
130
|
+
- crypto: NodeCrypto instance for signing
|
|
131
|
+
"""
|
|
132
|
+
crypto_mod = get_crypto_module()
|
|
133
|
+
crypto = crypto_mod['NodeCrypto'](token)
|
|
134
|
+
return crypto.node_id, crypto
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def save_token(token: str):
|
|
138
|
+
"""Save token to file for future use."""
|
|
139
|
+
os.makedirs(NEUROSHARD_DIR, exist_ok=True)
|
|
140
|
+
with open(TOKEN_FILE, 'w') as f:
|
|
141
|
+
f.write(token)
|
|
142
|
+
os.chmod(TOKEN_FILE, 0o600) # Secure permissions
|
|
143
|
+
print(f"[INFO] Token saved to {TOKEN_FILE}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_registry(ledger=None):
|
|
147
|
+
"""Get or create the NEP registry."""
|
|
148
|
+
gov = get_governance_modules()
|
|
149
|
+
db_path = os.path.join(NEUROSHARD_DIR, "nep_registry.db")
|
|
150
|
+
os.makedirs(NEUROSHARD_DIR, exist_ok=True)
|
|
151
|
+
return gov['NEPRegistry'](db_path=db_path, ledger=ledger)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_voting_system(ledger=None):
|
|
155
|
+
"""Get or create the voting system."""
|
|
156
|
+
gov = get_governance_modules()
|
|
157
|
+
db_path = os.path.join(NEUROSHARD_DIR, "nep_votes.db")
|
|
158
|
+
os.makedirs(NEUROSHARD_DIR, exist_ok=True)
|
|
159
|
+
return gov['VotingSystem'](db_path=db_path, ledger=ledger)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def require_wallet(args) -> tuple:
|
|
163
|
+
"""
|
|
164
|
+
Ensure wallet is available for commands that need it.
|
|
165
|
+
|
|
166
|
+
Returns (node_id, crypto) or exits with error.
|
|
167
|
+
"""
|
|
168
|
+
token, source = resolve_token(getattr(args, 'token', None))
|
|
169
|
+
|
|
170
|
+
if not token:
|
|
171
|
+
print("\nā Wallet token required for this command!")
|
|
172
|
+
print()
|
|
173
|
+
print("Provide your token using one of:")
|
|
174
|
+
print(" 1. --token YOUR_TOKEN")
|
|
175
|
+
print(" 2. export NEUROSHARD_TOKEN=YOUR_TOKEN")
|
|
176
|
+
print(f" 3. echo YOUR_TOKEN > {TOKEN_FILE}")
|
|
177
|
+
print()
|
|
178
|
+
print("Get your token at: https://neuroshard.com/wallet")
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
node_id, crypto = get_wallet(token)
|
|
183
|
+
print(f"[WALLET] Authenticated as {node_id[:16]}... (from {source})")
|
|
184
|
+
return node_id, crypto
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f"\nā Failed to initialize wallet: {e}")
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def format_timestamp(ts):
|
|
191
|
+
"""Format unix timestamp to readable string."""
|
|
192
|
+
if not ts:
|
|
193
|
+
return "N/A"
|
|
194
|
+
return time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def format_status(status):
|
|
198
|
+
"""Format status with color codes."""
|
|
199
|
+
colors = {
|
|
200
|
+
'draft': '\033[90m', # Gray
|
|
201
|
+
'review': '\033[94m', # Blue
|
|
202
|
+
'voting': '\033[93m', # Yellow
|
|
203
|
+
'approved': '\033[92m', # Green
|
|
204
|
+
'rejected': '\033[91m', # Red
|
|
205
|
+
'scheduled': '\033[95m', # Purple
|
|
206
|
+
'active': '\033[92m', # Green
|
|
207
|
+
}
|
|
208
|
+
reset = '\033[0m'
|
|
209
|
+
color = colors.get(status, '')
|
|
210
|
+
return f"{color}{status.upper()}{reset}"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def cmd_list(args):
|
|
214
|
+
"""List all proposals."""
|
|
215
|
+
gov = get_governance_modules()
|
|
216
|
+
registry = get_registry()
|
|
217
|
+
|
|
218
|
+
# Filter by status if specified
|
|
219
|
+
status_filter = None
|
|
220
|
+
if args.status:
|
|
221
|
+
try:
|
|
222
|
+
status_filter = gov['NEPStatus'](args.status)
|
|
223
|
+
except ValueError:
|
|
224
|
+
print(f"Invalid status: {args.status}")
|
|
225
|
+
print(f"Valid statuses: {[s.value for s in gov['NEPStatus']]}")
|
|
226
|
+
return 1
|
|
227
|
+
|
|
228
|
+
proposals = registry.list_proposals(status=status_filter, limit=args.limit)
|
|
229
|
+
|
|
230
|
+
if not proposals:
|
|
231
|
+
print("No proposals found.")
|
|
232
|
+
return 0
|
|
233
|
+
|
|
234
|
+
print(f"\n{'ID':<10} {'Status':<12} {'Type':<8} {'Title':<40} {'Created':<16}")
|
|
235
|
+
print("=" * 90)
|
|
236
|
+
|
|
237
|
+
for nep in proposals:
|
|
238
|
+
print(
|
|
239
|
+
f"{nep.nep_id:<10} "
|
|
240
|
+
f"{format_status(nep.status.value):<22} " # Extra space for color codes
|
|
241
|
+
f"{nep.nep_type.value:<8} "
|
|
242
|
+
f"{nep.title[:38]:<40} "
|
|
243
|
+
f"{format_timestamp(nep.created_at):<16}"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
print(f"\nTotal: {len(proposals)} proposals")
|
|
247
|
+
return 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def cmd_show(args):
|
|
251
|
+
"""Show details of a specific proposal."""
|
|
252
|
+
gov = get_governance_modules()
|
|
253
|
+
registry = get_registry()
|
|
254
|
+
voting = get_voting_system()
|
|
255
|
+
|
|
256
|
+
nep = registry.get_proposal(args.nep_id)
|
|
257
|
+
if not nep:
|
|
258
|
+
print(f"Proposal not found: {args.nep_id}")
|
|
259
|
+
return 1
|
|
260
|
+
|
|
261
|
+
print(f"\n{'='*70}")
|
|
262
|
+
print(f" {nep.nep_id}: {nep.title}")
|
|
263
|
+
print(f"{'='*70}")
|
|
264
|
+
print()
|
|
265
|
+
print(f" Status: {format_status(nep.status.value)}")
|
|
266
|
+
print(f" Type: {nep.nep_type.value}")
|
|
267
|
+
print(f" Author: {nep.author_node_id[:20]}...")
|
|
268
|
+
print(f" Created: {format_timestamp(nep.created_at)}")
|
|
269
|
+
print(f" Updated: {format_timestamp(nep.updated_at)}")
|
|
270
|
+
print()
|
|
271
|
+
print(f" ABSTRACT")
|
|
272
|
+
print(f" {'-'*60}")
|
|
273
|
+
print(f" {nep.abstract}")
|
|
274
|
+
print()
|
|
275
|
+
|
|
276
|
+
# Economic impact
|
|
277
|
+
impact = nep.economic_impact
|
|
278
|
+
print(f" ECONOMIC IMPACT")
|
|
279
|
+
print(f" {'-'*60}")
|
|
280
|
+
print(f" Training efficiency: {impact.training_efficiency_multiplier:.1f}x")
|
|
281
|
+
print(f" Training reward: {impact.training_reward_multiplier:.1f}x")
|
|
282
|
+
print(f" Inference reward: {impact.inference_reward_multiplier:.1f}x")
|
|
283
|
+
print(f" Memory change: {impact.min_memory_change_mb:+d} MB")
|
|
284
|
+
print(f" Net earnings: {impact.net_earnings_change_percent:+.1f}%")
|
|
285
|
+
print()
|
|
286
|
+
|
|
287
|
+
# Voting info
|
|
288
|
+
if nep.status == gov['NEPStatus'].VOTING:
|
|
289
|
+
print(f" VOTING (Active)")
|
|
290
|
+
print(f" {'-'*60}")
|
|
291
|
+
print(f" Started: {format_timestamp(nep.voting_start)}")
|
|
292
|
+
print(f" Ends: {format_timestamp(nep.voting_end)}")
|
|
293
|
+
|
|
294
|
+
# Get tally
|
|
295
|
+
result = voting.get_vote_tally(nep.nep_id, registry)
|
|
296
|
+
print(f" Yes votes: {result.yes_count} ({result.yes_stake:.2f} NEURO)")
|
|
297
|
+
print(f" No votes: {result.no_count} ({result.no_stake:.2f} NEURO)")
|
|
298
|
+
print(f" Abstain: {result.abstain_count} ({result.abstain_stake:.2f} NEURO)")
|
|
299
|
+
print(f" Quorum: {result.participation_rate*100:.1f}% of {result.total_network_stake:.2f} NEURO")
|
|
300
|
+
if result.quorum_reached:
|
|
301
|
+
print(f" Approval: {result.approval_rate*100:.1f}% (threshold: 66%)")
|
|
302
|
+
print()
|
|
303
|
+
|
|
304
|
+
if args.full:
|
|
305
|
+
print(f" MOTIVATION")
|
|
306
|
+
print(f" {'-'*60}")
|
|
307
|
+
print(f" {nep.motivation}")
|
|
308
|
+
print()
|
|
309
|
+
print(f" SPECIFICATION")
|
|
310
|
+
print(f" {'-'*60}")
|
|
311
|
+
print(f" {nep.specification}")
|
|
312
|
+
print()
|
|
313
|
+
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def cmd_propose(args):
|
|
318
|
+
"""Create a new proposal."""
|
|
319
|
+
gov = get_governance_modules()
|
|
320
|
+
|
|
321
|
+
# Require wallet authentication
|
|
322
|
+
node_id, crypto = require_wallet(args)
|
|
323
|
+
|
|
324
|
+
registry = get_registry()
|
|
325
|
+
|
|
326
|
+
# Parse type
|
|
327
|
+
try:
|
|
328
|
+
nep_type = gov['NEPType'](args.type)
|
|
329
|
+
except ValueError:
|
|
330
|
+
print(f"Invalid type: {args.type}")
|
|
331
|
+
print(f"Valid types: {[t.value for t in gov['NEPType']]}")
|
|
332
|
+
return 1
|
|
333
|
+
|
|
334
|
+
# If using a file for content
|
|
335
|
+
if args.file:
|
|
336
|
+
with open(args.file) as f:
|
|
337
|
+
content = json.load(f)
|
|
338
|
+
title = content.get('title', args.title)
|
|
339
|
+
abstract = content.get('abstract', '')
|
|
340
|
+
motivation = content.get('motivation', '')
|
|
341
|
+
specification = content.get('specification', '')
|
|
342
|
+
else:
|
|
343
|
+
title = args.title
|
|
344
|
+
abstract = args.abstract or input("Enter abstract (1-2 sentences): ")
|
|
345
|
+
motivation = args.motivation or input("Enter motivation (why is this needed?): ")
|
|
346
|
+
specification = args.specification or "See discussion thread."
|
|
347
|
+
|
|
348
|
+
# Create proposal
|
|
349
|
+
nep = gov['create_proposal'](
|
|
350
|
+
title=title,
|
|
351
|
+
nep_type=nep_type,
|
|
352
|
+
abstract=abstract,
|
|
353
|
+
motivation=motivation,
|
|
354
|
+
specification=specification,
|
|
355
|
+
author_node_id=node_id,
|
|
356
|
+
economic_impact=gov['EconomicImpact'](
|
|
357
|
+
net_earnings_change_percent=args.earnings_impact or 0.0
|
|
358
|
+
),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Confirm submission
|
|
362
|
+
print(f"\n{'='*60}")
|
|
363
|
+
print(f" PROPOSAL PREVIEW")
|
|
364
|
+
print(f"{'='*60}")
|
|
365
|
+
print(f" Title: {title}")
|
|
366
|
+
print(f" Type: {nep_type.value}")
|
|
367
|
+
print(f" Author: {node_id[:16]}...")
|
|
368
|
+
print(f" Abstract: {abstract[:100]}...")
|
|
369
|
+
print()
|
|
370
|
+
print(f" ā ļø COSTS:")
|
|
371
|
+
print(f" - 10 NEURO proposal fee (held in escrow)")
|
|
372
|
+
print(f" - Requires 100 NEURO staked")
|
|
373
|
+
print()
|
|
374
|
+
print(f" š° REWARDS (stake-proportional):")
|
|
375
|
+
print(f" - If approved: 0.1% of total stake voted + fee refund")
|
|
376
|
+
print(f" - If rejected w/ quorum: 0.01% of total stake voted")
|
|
377
|
+
print(f" - Example: 100K stake votes ā ~110 NEURO if approved")
|
|
378
|
+
print()
|
|
379
|
+
|
|
380
|
+
if not args.yes:
|
|
381
|
+
confirm = input("Submit this proposal? [y/N]: ")
|
|
382
|
+
if confirm.lower() != 'y':
|
|
383
|
+
print("Cancelled.")
|
|
384
|
+
return 0
|
|
385
|
+
|
|
386
|
+
# Sign the proposal with ECDSA
|
|
387
|
+
signature = crypto.sign(nep.content_hash)
|
|
388
|
+
|
|
389
|
+
# Submit to registry
|
|
390
|
+
success, message, nep_id = registry.submit_proposal(
|
|
391
|
+
nep=nep,
|
|
392
|
+
node_id=node_id,
|
|
393
|
+
signature=signature,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
if success:
|
|
397
|
+
print(f"\nā
{message}")
|
|
398
|
+
print(f" Your proposal ID: {nep_id}")
|
|
399
|
+
print(f" Your wallet: {node_id}")
|
|
400
|
+
print(f" Track status: neuroshard-governance show {nep_id}")
|
|
401
|
+
else:
|
|
402
|
+
print(f"\nā Failed: {message}")
|
|
403
|
+
return 1
|
|
404
|
+
|
|
405
|
+
return 0
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def cmd_vote(args):
|
|
409
|
+
"""Vote on a proposal."""
|
|
410
|
+
gov = get_governance_modules()
|
|
411
|
+
|
|
412
|
+
# Require wallet authentication
|
|
413
|
+
node_id, crypto = require_wallet(args)
|
|
414
|
+
|
|
415
|
+
registry = get_registry()
|
|
416
|
+
voting = get_voting_system()
|
|
417
|
+
|
|
418
|
+
# Check proposal exists and is in voting
|
|
419
|
+
nep = registry.get_proposal(args.nep_id)
|
|
420
|
+
if not nep:
|
|
421
|
+
print(f"Proposal not found: {args.nep_id}")
|
|
422
|
+
return 1
|
|
423
|
+
|
|
424
|
+
if nep.status != gov['NEPStatus'].VOTING:
|
|
425
|
+
print(f"Proposal is not in voting phase (status: {nep.status.value})")
|
|
426
|
+
return 1
|
|
427
|
+
|
|
428
|
+
# Parse vote choice
|
|
429
|
+
choice_map = {
|
|
430
|
+
'yes': gov['VoteChoice'].YES,
|
|
431
|
+
'no': gov['VoteChoice'].NO,
|
|
432
|
+
'abstain': gov['VoteChoice'].ABSTAIN,
|
|
433
|
+
}
|
|
434
|
+
choice = choice_map.get(args.choice.lower())
|
|
435
|
+
if not choice:
|
|
436
|
+
print(f"Invalid vote: {args.choice}")
|
|
437
|
+
print("Valid choices: yes, no, abstain")
|
|
438
|
+
return 1
|
|
439
|
+
|
|
440
|
+
# Show proposal summary
|
|
441
|
+
print(f"\n{'='*60}")
|
|
442
|
+
print(f" VOTING: {nep.nep_id}")
|
|
443
|
+
print(f"{'='*60}")
|
|
444
|
+
print(f" Title: {nep.title}")
|
|
445
|
+
print(f" Impact: {nep.economic_impact.describe()}")
|
|
446
|
+
print(f" Ends: {format_timestamp(nep.voting_end)}")
|
|
447
|
+
print()
|
|
448
|
+
print(f" Your wallet: {node_id[:16]}...")
|
|
449
|
+
print(f" Your vote: {args.choice.upper()}")
|
|
450
|
+
if args.reason:
|
|
451
|
+
print(f" Reason: {args.reason}")
|
|
452
|
+
print()
|
|
453
|
+
print(f" š° You will earn: 0.01% of your staked NEURO for voting")
|
|
454
|
+
print()
|
|
455
|
+
|
|
456
|
+
if not args.yes:
|
|
457
|
+
confirm = input("Confirm vote? [y/N]: ")
|
|
458
|
+
if confirm.lower() != 'y':
|
|
459
|
+
print("Cancelled.")
|
|
460
|
+
return 0
|
|
461
|
+
|
|
462
|
+
# Sign the vote with ECDSA
|
|
463
|
+
vote_payload = f"{args.nep_id}:{node_id}:{choice.value}:{int(time.time())}"
|
|
464
|
+
signature = crypto.sign(vote_payload)
|
|
465
|
+
|
|
466
|
+
# Cast vote
|
|
467
|
+
success, message = voting.cast_vote(
|
|
468
|
+
nep_id=args.nep_id,
|
|
469
|
+
voter_node_id=node_id,
|
|
470
|
+
choice=choice,
|
|
471
|
+
signature=signature,
|
|
472
|
+
reason=args.reason or "",
|
|
473
|
+
nep_registry=registry,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
if success:
|
|
477
|
+
print(f"\nā
{message}")
|
|
478
|
+
print(f" Your vote has been recorded and signed.")
|
|
479
|
+
else:
|
|
480
|
+
print(f"\nā Failed: {message}")
|
|
481
|
+
return 1
|
|
482
|
+
|
|
483
|
+
return 0
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def cmd_results(args):
|
|
487
|
+
"""Show voting results for a proposal."""
|
|
488
|
+
gov = get_governance_modules()
|
|
489
|
+
registry = get_registry()
|
|
490
|
+
voting = get_voting_system()
|
|
491
|
+
|
|
492
|
+
nep = registry.get_proposal(args.nep_id)
|
|
493
|
+
if not nep:
|
|
494
|
+
print(f"Proposal not found: {args.nep_id}")
|
|
495
|
+
return 1
|
|
496
|
+
|
|
497
|
+
result = voting.get_vote_tally(args.nep_id, registry)
|
|
498
|
+
|
|
499
|
+
print(f"\n{'='*60}")
|
|
500
|
+
print(f" VOTING RESULTS: {nep.nep_id}")
|
|
501
|
+
print(f"{'='*60}")
|
|
502
|
+
print(f" {nep.title}")
|
|
503
|
+
print(f" Status: {format_status(nep.status.value)}")
|
|
504
|
+
print()
|
|
505
|
+
|
|
506
|
+
# Vote counts
|
|
507
|
+
total_votes = result.yes_count + result.no_count + result.abstain_count
|
|
508
|
+
print(f" VOTES ({total_votes} total)")
|
|
509
|
+
print(f" {'-'*50}")
|
|
510
|
+
|
|
511
|
+
# Bar chart
|
|
512
|
+
if total_votes > 0:
|
|
513
|
+
yes_pct = result.yes_count / total_votes * 100
|
|
514
|
+
no_pct = result.no_count / total_votes * 100
|
|
515
|
+
abs_pct = result.abstain_count / total_votes * 100
|
|
516
|
+
|
|
517
|
+
bar_width = 30
|
|
518
|
+
yes_bar = 'ā' * int(yes_pct / 100 * bar_width)
|
|
519
|
+
no_bar = 'ā' * int(no_pct / 100 * bar_width)
|
|
520
|
+
|
|
521
|
+
print(f" YES: {result.yes_count:4} ({yes_pct:5.1f}%) \033[92m{yes_bar}\033[0m")
|
|
522
|
+
print(f" NO: {result.no_count:4} ({no_pct:5.1f}%) \033[91m{no_bar}\033[0m")
|
|
523
|
+
print(f" ABSTAIN: {result.abstain_count:4} ({abs_pct:5.1f}%)")
|
|
524
|
+
else:
|
|
525
|
+
print(f" No votes cast yet")
|
|
526
|
+
print()
|
|
527
|
+
|
|
528
|
+
# Stake-weighted results
|
|
529
|
+
print(f" STAKE-WEIGHTED")
|
|
530
|
+
print(f" {'-'*50}")
|
|
531
|
+
print(f" Yes stake: {result.yes_stake:12.2f} NEURO")
|
|
532
|
+
print(f" No stake: {result.no_stake:12.2f} NEURO")
|
|
533
|
+
print(f" Abstain stake: {result.abstain_stake:12.2f} NEURO")
|
|
534
|
+
print(f" Total voted: {result.total_stake_voted:12.2f} NEURO")
|
|
535
|
+
print(f" Network stake: {result.total_network_stake:12.2f} NEURO")
|
|
536
|
+
print()
|
|
537
|
+
|
|
538
|
+
# Thresholds
|
|
539
|
+
print(f" THRESHOLDS")
|
|
540
|
+
print(f" {'-'*50}")
|
|
541
|
+
quorum_status = "ā
" if result.quorum_reached else "ā"
|
|
542
|
+
print(f" Quorum (20%): {result.participation_rate*100:5.1f}% {quorum_status}")
|
|
543
|
+
|
|
544
|
+
approval_status = "ā
" if result.approval_rate >= 0.66 else "ā"
|
|
545
|
+
print(f" Approval (66%): {result.approval_rate*100:5.1f}% {approval_status}")
|
|
546
|
+
print()
|
|
547
|
+
|
|
548
|
+
if result.quorum_reached and result.approval_rate >= 0.66:
|
|
549
|
+
print(f" \033[92mā Proposal PASSING\033[0m")
|
|
550
|
+
elif result.quorum_reached:
|
|
551
|
+
print(f" \033[91mā Proposal FAILING (below approval threshold)\033[0m")
|
|
552
|
+
else:
|
|
553
|
+
print(f" \033[93mā³ Waiting for quorum\033[0m")
|
|
554
|
+
|
|
555
|
+
return 0
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def main():
|
|
559
|
+
"""Main CLI entry point."""
|
|
560
|
+
parser = argparse.ArgumentParser(
|
|
561
|
+
description="NeuroShard Governance CLI",
|
|
562
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
563
|
+
epilog="""
|
|
564
|
+
WALLET SETUP:
|
|
565
|
+
Commands that modify state (propose, vote) require your wallet token.
|
|
566
|
+
This is the same token used for neuroshard-node.
|
|
567
|
+
|
|
568
|
+
Option 1: Pass directly
|
|
569
|
+
neuroshard-governance --token YOUR_TOKEN propose ...
|
|
570
|
+
|
|
571
|
+
Option 2: Environment variable
|
|
572
|
+
export NEUROSHARD_TOKEN=YOUR_TOKEN
|
|
573
|
+
neuroshard-governance propose ...
|
|
574
|
+
|
|
575
|
+
Option 3: Save to file (recommended)
|
|
576
|
+
neuroshard-governance --token YOUR_TOKEN wallet --save
|
|
577
|
+
# Future commands auto-use ~/.neuroshard/wallet_token
|
|
578
|
+
|
|
579
|
+
EXAMPLES:
|
|
580
|
+
# Check your wallet
|
|
581
|
+
neuroshard-governance --token YOUR_TOKEN wallet
|
|
582
|
+
|
|
583
|
+
# List all proposals (no auth needed)
|
|
584
|
+
neuroshard-governance list
|
|
585
|
+
|
|
586
|
+
# List proposals in voting
|
|
587
|
+
neuroshard-governance list --status voting
|
|
588
|
+
|
|
589
|
+
# View a proposal
|
|
590
|
+
neuroshard-governance show NEP-001
|
|
591
|
+
|
|
592
|
+
# Create a proposal (requires wallet)
|
|
593
|
+
neuroshard-governance propose --title "Add MTP Training" --type train
|
|
594
|
+
|
|
595
|
+
# Vote on a proposal (requires wallet)
|
|
596
|
+
neuroshard-governance vote NEP-001 yes --reason "Improves efficiency"
|
|
597
|
+
|
|
598
|
+
# Check voting results
|
|
599
|
+
neuroshard-governance results NEP-001
|
|
600
|
+
|
|
601
|
+
Get your wallet token at: https://neuroshard.com/wallet
|
|
602
|
+
"""
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
parser.add_argument(
|
|
606
|
+
"--version", action="version",
|
|
607
|
+
version=f"NeuroShard Governance CLI {__version__}"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Global token option (for commands that need authentication)
|
|
611
|
+
parser.add_argument(
|
|
612
|
+
"--token", type=str, default=None,
|
|
613
|
+
help="Wallet token (64-char hex or 12-word mnemonic). "
|
|
614
|
+
"Can also be set via NEUROSHARD_TOKEN env or ~/.neuroshard/wallet_token"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
618
|
+
|
|
619
|
+
# List command (no auth required)
|
|
620
|
+
list_parser = subparsers.add_parser("list", help="List proposals")
|
|
621
|
+
list_parser.add_argument("--status", type=str, help="Filter by status")
|
|
622
|
+
list_parser.add_argument("--limit", type=int, default=50, help="Max results")
|
|
623
|
+
|
|
624
|
+
# Show command (no auth required)
|
|
625
|
+
show_parser = subparsers.add_parser("show", help="Show proposal details")
|
|
626
|
+
show_parser.add_argument("nep_id", type=str, help="Proposal ID (e.g., NEP-001)")
|
|
627
|
+
show_parser.add_argument("--full", action="store_true", help="Show full specification")
|
|
628
|
+
|
|
629
|
+
# Propose command (requires wallet)
|
|
630
|
+
propose_parser = subparsers.add_parser("propose", help="Create a proposal (requires wallet)")
|
|
631
|
+
propose_parser.add_argument("--title", type=str, required=True, help="Proposal title")
|
|
632
|
+
propose_parser.add_argument("--type", type=str, required=True,
|
|
633
|
+
help="Type: arch, econ, train, net, gov, emergency")
|
|
634
|
+
propose_parser.add_argument("--abstract", type=str, help="Brief description")
|
|
635
|
+
propose_parser.add_argument("--motivation", type=str, help="Why is this needed")
|
|
636
|
+
propose_parser.add_argument("--specification", type=str, help="Technical details")
|
|
637
|
+
propose_parser.add_argument("--file", type=str, help="JSON file with proposal content")
|
|
638
|
+
propose_parser.add_argument("--earnings-impact", type=float, help="Net earnings change %")
|
|
639
|
+
propose_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
640
|
+
|
|
641
|
+
# Vote command (requires wallet)
|
|
642
|
+
vote_parser = subparsers.add_parser("vote", help="Vote on a proposal (requires wallet)")
|
|
643
|
+
vote_parser.add_argument("nep_id", type=str, help="Proposal ID")
|
|
644
|
+
vote_parser.add_argument("choice", type=str, help="Vote: yes, no, abstain")
|
|
645
|
+
vote_parser.add_argument("--reason", type=str, help="Reason for your vote")
|
|
646
|
+
vote_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
647
|
+
|
|
648
|
+
# Results command (no auth required)
|
|
649
|
+
results_parser = subparsers.add_parser("results", help="Show voting results")
|
|
650
|
+
results_parser.add_argument("nep_id", type=str, help="Proposal ID")
|
|
651
|
+
|
|
652
|
+
# Wallet command (utility)
|
|
653
|
+
wallet_parser = subparsers.add_parser("wallet", help="Show wallet info")
|
|
654
|
+
wallet_parser.add_argument("--save", action="store_true",
|
|
655
|
+
help="Save token to ~/.neuroshard/wallet_token")
|
|
656
|
+
|
|
657
|
+
args = parser.parse_args()
|
|
658
|
+
|
|
659
|
+
if not args.command:
|
|
660
|
+
parser.print_help()
|
|
661
|
+
return 0
|
|
662
|
+
|
|
663
|
+
# Dispatch to command handler
|
|
664
|
+
commands = {
|
|
665
|
+
'list': cmd_list,
|
|
666
|
+
'show': cmd_show,
|
|
667
|
+
'propose': cmd_propose,
|
|
668
|
+
'vote': cmd_vote,
|
|
669
|
+
'results': cmd_results,
|
|
670
|
+
'wallet': cmd_wallet,
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
handler = commands.get(args.command)
|
|
674
|
+
if handler:
|
|
675
|
+
return handler(args)
|
|
676
|
+
else:
|
|
677
|
+
parser.print_help()
|
|
678
|
+
return 1
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def cmd_wallet(args):
|
|
682
|
+
"""Show wallet info and optionally save token."""
|
|
683
|
+
token, source = resolve_token(args.token)
|
|
684
|
+
|
|
685
|
+
if not token:
|
|
686
|
+
print("\nā No wallet token found!")
|
|
687
|
+
print()
|
|
688
|
+
print("Provide your token using one of:")
|
|
689
|
+
print(" 1. --token YOUR_TOKEN")
|
|
690
|
+
print(" 2. export NEUROSHARD_TOKEN=YOUR_TOKEN")
|
|
691
|
+
print(f" 3. echo YOUR_TOKEN > {TOKEN_FILE}")
|
|
692
|
+
print()
|
|
693
|
+
print("Get your token at: https://neuroshard.com/wallet")
|
|
694
|
+
return 1
|
|
695
|
+
|
|
696
|
+
try:
|
|
697
|
+
node_id, crypto = get_wallet(token)
|
|
698
|
+
|
|
699
|
+
print(f"\n{'='*60}")
|
|
700
|
+
print(f" WALLET INFO")
|
|
701
|
+
print(f"{'='*60}")
|
|
702
|
+
print(f" Node ID: {node_id}")
|
|
703
|
+
print(f" Public Key: {crypto.get_public_key_hex()[:32]}...")
|
|
704
|
+
print(f" Source: {source}")
|
|
705
|
+
print()
|
|
706
|
+
|
|
707
|
+
# Test signing
|
|
708
|
+
test_msg = "test_message"
|
|
709
|
+
sig = crypto.sign(test_msg)
|
|
710
|
+
verified = crypto.verify(test_msg, sig)
|
|
711
|
+
print(f" Signature test: {'ā
OK' if verified else 'ā FAILED'}")
|
|
712
|
+
print()
|
|
713
|
+
|
|
714
|
+
if args.save:
|
|
715
|
+
save_token(token)
|
|
716
|
+
print(f" ā
Token saved to {TOKEN_FILE}")
|
|
717
|
+
print(f" Future commands will use this automatically.")
|
|
718
|
+
else:
|
|
719
|
+
print(f" š” Run with --save to remember this wallet")
|
|
720
|
+
|
|
721
|
+
return 0
|
|
722
|
+
|
|
723
|
+
except Exception as e:
|
|
724
|
+
print(f"\nā Failed to initialize wallet: {e}")
|
|
725
|
+
return 1
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
if __name__ == "__main__":
|
|
729
|
+
sys.exit(main())
|