sol-mcp 0.2.0__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.
@@ -0,0 +1,304 @@
1
+ """Download Solana source repositories for indexing.
2
+
3
+ Clones:
4
+ - anza-xyz/agave (reference validator runtime)
5
+ - solana-foundation/solana-improvement-documents (SIMDs)
6
+ - anza-xyz/alpenglow (new consensus protocol, not yet live)
7
+ - firedancer-io/firedancer (Jump's C implementation)
8
+ - jito-foundation/jito-solana (MEV-enabled Agave fork, ~70% of stake)
9
+ - jito-foundation/jito-programs (on-chain tip distribution programs)
10
+ """
11
+
12
+ import subprocess
13
+ from pathlib import Path
14
+ from typing import Callable
15
+
16
+ # Default data directory
17
+ DEFAULT_DATA_DIR = Path.home() / ".solana-mcp"
18
+
19
+ # Repositories to clone
20
+ REPOS = {
21
+ # Reference implementation (Anza)
22
+ "agave": {
23
+ "url": "https://github.com/anza-xyz/agave.git",
24
+ "branch": "master",
25
+ "client": True,
26
+ "stake_pct": 8.0, # ~8% stake on vanilla Agave
27
+ "sparse_paths": [
28
+ "programs/stake",
29
+ "programs/vote",
30
+ "programs/bpf_loader",
31
+ "programs/system",
32
+ "runtime",
33
+ "svm",
34
+ "poh",
35
+ "turbine",
36
+ "gossip",
37
+ "ledger",
38
+ "validator",
39
+ "sdk",
40
+ "core", # TowerBFT consensus, replay_stage, fork choice
41
+ "docs",
42
+ ],
43
+ },
44
+ # Jito-Agave: MEV-enabled fork, dominant client (~70% stake)
45
+ "jito-solana": {
46
+ "url": "https://github.com/jito-foundation/jito-solana.git",
47
+ "branch": "master",
48
+ "client": True,
49
+ "stake_pct": 70.0, # ~70% of mainnet stake
50
+ "sparse_paths": [
51
+ "core", # Block engine integration
52
+ "runtime",
53
+ "programs/stake",
54
+ "programs/vote",
55
+ "poh",
56
+ "turbine",
57
+ "gossip",
58
+ "validator",
59
+ "bundle", # Jito-specific: bundle processing
60
+ "tip-distributor", # Jito-specific: tip distribution
61
+ "block-engine", # Jito-specific: MEV infrastructure
62
+ ],
63
+ },
64
+ # Firedancer: Jump's C implementation (independent failure domain)
65
+ "firedancer": {
66
+ "url": "https://github.com/firedancer-io/firedancer.git",
67
+ "branch": "main",
68
+ "client": True,
69
+ "stake_pct": 22.0, # ~21% Frankendancer + <1% full Firedancer
70
+ "sparse_paths": [
71
+ "src/app", # Validator application
72
+ "src/ballet", # Cryptography, hashing
73
+ "src/disco", # Distributed consensus
74
+ "src/flamenco", # Runtime, accounts
75
+ "src/tango", # Messaging infrastructure
76
+ "src/waltz", # Networking (QUIC, UDP)
77
+ "src/choreo", # Fork choice, TowerBFT
78
+ "contrib", # Build scripts, tests
79
+ ],
80
+ },
81
+ # SIMDs (small repo, full clone)
82
+ "solana-improvement-documents": {
83
+ "url": "https://github.com/solana-foundation/solana-improvement-documents.git",
84
+ "branch": "main",
85
+ "client": False,
86
+ "sparse_paths": None, # Clone full repo (it's small)
87
+ },
88
+ # Alpenglow: Future consensus (not yet live)
89
+ "alpenglow": {
90
+ "url": "https://github.com/anza-xyz/alpenglow.git",
91
+ "branch": "master",
92
+ "client": False,
93
+ "sparse_paths": None, # Clone full repo
94
+ },
95
+ # ===================
96
+ # MEV Infrastructure
97
+ # ===================
98
+ # Jito on-chain programs (tip distribution, etc.)
99
+ "jito-programs": {
100
+ "url": "https://github.com/jito-foundation/jito-programs.git",
101
+ "branch": "master",
102
+ "client": False,
103
+ "sparse_paths": [
104
+ "mev-programs", # Core MEV programs
105
+ "tip-distribution", # Tip distribution program
106
+ "tip-payment", # Tip payment handling
107
+ ],
108
+ },
109
+ }
110
+
111
+
112
+ def run_git(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess:
113
+ """Run a git command."""
114
+ result = subprocess.run(
115
+ ["git"] + args,
116
+ cwd=cwd,
117
+ capture_output=True,
118
+ text=True,
119
+ )
120
+ return result
121
+
122
+
123
+ def clone_repo(
124
+ name: str,
125
+ url: str,
126
+ dest: Path,
127
+ branch: str = "main",
128
+ sparse_paths: list[str] | None = None,
129
+ progress_callback: Callable[[str], None] | None = None,
130
+ ) -> bool:
131
+ """
132
+ Clone a repository, optionally with sparse checkout.
133
+
134
+ Args:
135
+ name: Repository name for logging
136
+ url: Git URL to clone
137
+ dest: Destination path
138
+ branch: Branch to checkout
139
+ sparse_paths: If provided, only checkout these paths (sparse checkout)
140
+ progress_callback: Optional callback for progress updates
141
+
142
+ Returns:
143
+ True if successful, False otherwise
144
+ """
145
+ def log(msg: str):
146
+ if progress_callback:
147
+ progress_callback(msg)
148
+ else:
149
+ print(msg)
150
+
151
+ if dest.exists():
152
+ log(f" {name}: Already exists, pulling latest...")
153
+ result = run_git(["pull", "--ff-only"], cwd=dest)
154
+ if result.returncode != 0:
155
+ log(f" {name}: Pull failed, trying fetch + reset...")
156
+ run_git(["fetch", "origin"], cwd=dest)
157
+ run_git(["reset", "--hard", f"origin/{branch}"], cwd=dest)
158
+ return True
159
+
160
+ log(f" {name}: Cloning from {url}...")
161
+
162
+ if sparse_paths:
163
+ # Sparse checkout for large repos
164
+ dest.mkdir(parents=True, exist_ok=True)
165
+
166
+ # Initialize repo
167
+ run_git(["init"], cwd=dest)
168
+ run_git(["remote", "add", "origin", url], cwd=dest)
169
+
170
+ # Configure sparse checkout
171
+ run_git(["config", "core.sparseCheckout", "true"], cwd=dest)
172
+
173
+ # Write sparse-checkout file
174
+ sparse_file = dest / ".git" / "info" / "sparse-checkout"
175
+ sparse_file.parent.mkdir(parents=True, exist_ok=True)
176
+ sparse_file.write_text("\n".join(sparse_paths) + "\n")
177
+
178
+ # Fetch and checkout
179
+ log(f" {name}: Fetching (sparse checkout: {len(sparse_paths)} paths)...")
180
+ result = run_git(["fetch", "--depth=1", "origin", branch], cwd=dest)
181
+ if result.returncode != 0:
182
+ log(f" {name}: Fetch failed: {result.stderr}")
183
+ return False
184
+
185
+ result = run_git(["checkout", branch], cwd=dest)
186
+ if result.returncode != 0:
187
+ log(f" {name}: Checkout failed: {result.stderr}")
188
+ return False
189
+ else:
190
+ # Full clone for small repos
191
+ result = run_git(["clone", "--depth=1", "--branch", branch, url, str(dest)])
192
+ if result.returncode != 0:
193
+ log(f" {name}: Clone failed: {result.stderr}")
194
+ return False
195
+
196
+ log(f" {name}: Done")
197
+ return True
198
+
199
+
200
+ def download_repos(
201
+ data_dir: Path | None = None,
202
+ repos: list[str] | None = None,
203
+ progress_callback: Callable[[str], None] | None = None,
204
+ ) -> dict[str, bool]:
205
+ """
206
+ Download all configured repositories.
207
+
208
+ Args:
209
+ data_dir: Base directory for downloads (default: ~/.solana-mcp)
210
+ repos: List of repo names to download (default: all)
211
+ progress_callback: Optional callback for progress updates
212
+
213
+ Returns:
214
+ Dict mapping repo name to success status
215
+ """
216
+ if data_dir is None:
217
+ data_dir = DEFAULT_DATA_DIR
218
+
219
+ data_dir.mkdir(parents=True, exist_ok=True)
220
+
221
+ if repos is None:
222
+ repos = list(REPOS.keys())
223
+
224
+ results = {}
225
+
226
+ for name in repos:
227
+ if name not in REPOS:
228
+ if progress_callback:
229
+ progress_callback(f" {name}: Unknown repository, skipping")
230
+ results[name] = False
231
+ continue
232
+
233
+ config = REPOS[name]
234
+ dest = data_dir / name
235
+
236
+ success = clone_repo(
237
+ name=name,
238
+ url=config["url"],
239
+ dest=dest,
240
+ branch=config["branch"],
241
+ sparse_paths=config.get("sparse_paths"),
242
+ progress_callback=progress_callback,
243
+ )
244
+ results[name] = success
245
+
246
+ return results
247
+
248
+
249
+ def get_repo_path(name: str, data_dir: Path | None = None) -> Path | None:
250
+ """Get the path to a downloaded repository."""
251
+ if data_dir is None:
252
+ data_dir = DEFAULT_DATA_DIR
253
+
254
+ path = data_dir / name
255
+ if path.exists():
256
+ return path
257
+ return None
258
+
259
+
260
+ def get_repo_version(name: str, data_dir: Path | None = None) -> str | None:
261
+ """Get the current commit hash of a repository."""
262
+ path = get_repo_path(name, data_dir)
263
+ if path is None:
264
+ return None
265
+
266
+ result = run_git(["rev-parse", "HEAD"], cwd=path)
267
+ if result.returncode == 0:
268
+ return result.stdout.strip()[:12]
269
+ return None
270
+
271
+
272
+ def list_downloaded_repos(data_dir: Path | None = None) -> dict[str, dict]:
273
+ """List all downloaded repositories with their status."""
274
+ if data_dir is None:
275
+ data_dir = DEFAULT_DATA_DIR
276
+
277
+ status = {}
278
+ for name in REPOS:
279
+ path = data_dir / name
280
+ if path.exists():
281
+ version = get_repo_version(name, data_dir)
282
+ status[name] = {
283
+ "path": str(path),
284
+ "version": version,
285
+ "exists": True,
286
+ }
287
+ else:
288
+ status[name] = {
289
+ "path": str(path),
290
+ "version": None,
291
+ "exists": False,
292
+ }
293
+
294
+ return status
295
+
296
+
297
+ if __name__ == "__main__":
298
+ # Test download
299
+ print("Downloading Solana repositories...")
300
+ results = download_repos(progress_callback=print)
301
+ print("\nResults:")
302
+ for name, success in results.items():
303
+ status = "✓" if success else "✗"
304
+ print(f" {status} {name}")