solforge 0.2.2 → 0.2.4
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.
- package/LICENSE +2 -2
- package/README.md +90 -523
- package/cli.cjs +131 -0
- package/package.json +15 -13
- package/scripts/install.sh +112 -0
- package/scripts/postinstall.cjs +110 -0
- package/src/cli/main.ts +33 -12
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024 nitishxyz
|
|
3
|
+
Copyright (c) 2024-2025 nitishxyz
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,523 +1,90 @@
|
|
|
1
|
-
# SolForge –
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.send();
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### With web3.js
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
98
|
-
|
|
99
|
-
const connection = new Connection("http://localhost:8899");
|
|
100
|
-
|
|
101
|
-
// Get account info
|
|
102
|
-
const accountInfo = await connection.getAccountInfo(publicKey);
|
|
103
|
-
|
|
104
|
-
// Send transaction
|
|
105
|
-
const signature = await connection.sendTransaction(transaction, [keypair]);
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### With Anchor
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
// anchor.toml - use default settings
|
|
112
|
-
[provider]
|
|
113
|
-
cluster = \"http://127.0.0.1:8899\"
|
|
114
|
-
wallet = \"~/.config/solana/id.json\"
|
|
115
|
-
|
|
116
|
-
// Deploy and test normally
|
|
117
|
-
anchor build
|
|
118
|
-
anchor deploy
|
|
119
|
-
anchor test --skip-local-validator # SolForge is already running
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## 🖥️ GUI Dashboard
|
|
123
|
-
|
|
124
|
-
Solforge ships a dark-mode dashboard that starts alongside the RPC server. By default it listens on `http://127.0.0.1:42069` and provides:
|
|
125
|
-
|
|
126
|
-
- Quick airdrops and SPL mints via a faucet-aware form.
|
|
127
|
-
- Live RPC metrics (slot, block height, transaction count, blockhash, faucet balance).
|
|
128
|
-
- Tables of cloned programs and token mints with one-click modals to import additional assets.
|
|
129
|
-
|
|
130
|
-
### Launching the Dashboard
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
# Run the interactive CLI (starts RPC + GUI)
|
|
134
|
-
bun src/cli/main.ts
|
|
135
|
-
|
|
136
|
-
# Or start directly
|
|
137
|
-
bun src/cli/main.ts start
|
|
138
|
-
|
|
139
|
-
# Open the dashboard
|
|
140
|
-
open http://127.0.0.1:42069
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### GUI API Endpoints
|
|
144
|
-
|
|
145
|
-
The GUI server exposes REST endpoints backed by the same JSON-RPC methods:
|
|
146
|
-
|
|
147
|
-
| Method & Path | Description |
|
|
148
|
-
| ------------------------- | ------------------------------------ |
|
|
149
|
-
| `GET /api/status` | Aggregated RPC stats + faucet info |
|
|
150
|
-
| `GET /api/programs` | List the registered programs |
|
|
151
|
-
| `GET /api/tokens` | Detailed SPL mint metadata |
|
|
152
|
-
| `POST /api/airdrop` | Proxy to `requestAirdrop` |
|
|
153
|
-
| `POST /api/mint` | Proxy to `solforgeMintTo` |
|
|
154
|
-
| `POST /api/clone/program` | Proxy to program clone helpers |
|
|
155
|
-
| `POST /api/clone/token` | Proxy to token clone helpers |
|
|
156
|
-
|
|
157
|
-
Override the GUI port via `sf.config.json` (`gui.port`) or `SOLFORGE_GUI_PORT`.
|
|
158
|
-
|
|
159
|
-
Run `bun run build:css` before `bun run build:bin` to embed the latest Tailwind styles in the standalone binary.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
## 🔧 Configuration
|
|
163
|
-
|
|
164
|
-
SolForge works with zero configuration, but can be customized via environment variables or config file.
|
|
165
|
-
|
|
166
|
-
### Environment Variables
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
# Server settings
|
|
170
|
-
export RPC_PORT=8899 # HTTP port (WS uses port+1)
|
|
171
|
-
export DEBUG_RPC_LOG=1 # Log all RPC calls
|
|
172
|
-
|
|
173
|
-
# Database
|
|
174
|
-
export SOLFORGE_DB_MODE=ephemeral # or 'persistent'
|
|
175
|
-
export SOLFORGE_DB_PATH=.solforge/db.db
|
|
176
|
-
|
|
177
|
-
# Faucet
|
|
178
|
-
export SOLFORGE_FAUCET_LAMPORTS=1000000000000000 # 1M SOL
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Config File (sf.config.json)
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
# Generate default config
|
|
185
|
-
bun src/cli/main.ts config init
|
|
186
|
-
|
|
187
|
-
# Edit configuration
|
|
188
|
-
bun src/cli/main.ts config set server.rpcPort 9000
|
|
189
|
-
bun src/cli/main.ts config get server.db.mode
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
```json
|
|
193
|
-
{
|
|
194
|
-
\"server\": {
|
|
195
|
-
\"rpcPort\": 8899,
|
|
196
|
-
\"wsPort\": 8900,
|
|
197
|
-
\"db\": {
|
|
198
|
-
\"mode\": \"ephemeral\",
|
|
199
|
-
\"path\": \".solforge/db.db\"
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
\"svm\": {
|
|
203
|
-
\"initialLamports\": \"1000000000000000\",
|
|
204
|
-
\"faucetSOL\": 1000
|
|
205
|
-
},
|
|
206
|
-
\"clone\": {
|
|
207
|
-
\"endpoint\": \"https://api.mainnet-beta.solana.com\",
|
|
208
|
-
\"programs\": [],
|
|
209
|
-
\"tokens\": [],
|
|
210
|
-
\"programAccounts\": []
|
|
211
|
-
},
|
|
212
|
-
\"gui\": {
|
|
213
|
-
\"enabled\": true,
|
|
214
|
-
\"port\": 42069
|
|
215
|
-
},
|
|
216
|
-
\"bootstrap\": {
|
|
217
|
-
\"airdrops\": []
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## 🛠️ CLI Tools
|
|
223
|
-
|
|
224
|
-
SolForge includes powerful CLI tools for development:
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
# Airdrop SOL to any address
|
|
228
|
-
bun src/cli/main.ts airdrop --to <pubkey> --sol 100
|
|
229
|
-
|
|
230
|
-
# Interactive token minting
|
|
231
|
-
bun src/cli/main.ts mint
|
|
232
|
-
|
|
233
|
-
# Clone mainnet programs and data
|
|
234
|
-
bun src/cli/main.ts program clone <program-id>
|
|
235
|
-
bun src/cli/main.ts token clone <mint-address>
|
|
236
|
-
|
|
237
|
-
# Manage configuration
|
|
238
|
-
bun src/cli/main.ts config init
|
|
239
|
-
bun src/cli/main.ts config set server.rpcPort 9000
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## 📡 RPC Method Coverage
|
|
243
|
-
|
|
244
|
-
### ✅ Fully Implemented (90+ methods)
|
|
245
|
-
|
|
246
|
-
**Account Operations**
|
|
247
|
-
|
|
248
|
-
- `getAccountInfo`, `getMultipleAccounts`, `getBalance`
|
|
249
|
-
- `getParsedAccountInfo`, `getProgramAccounts`
|
|
250
|
-
|
|
251
|
-
**Transaction Operations**
|
|
252
|
-
|
|
253
|
-
- `sendTransaction`, `simulateTransaction`, `getTransaction`
|
|
254
|
-
- `getSignatureStatuses`, `getSignaturesForAddress`
|
|
255
|
-
|
|
256
|
-
**Block & Slot Operations**
|
|
257
|
-
|
|
258
|
-
- `getLatestBlockhash`, `getBlock`, `getBlocks`, `getBlockHeight`
|
|
259
|
-
- `getSlot`, `getSlotLeader`, `getSlotLeaders`
|
|
260
|
-
|
|
261
|
-
**System & Network**
|
|
262
|
-
|
|
263
|
-
- `getHealth`, `getVersion`, `getGenesisHash`, `getEpochInfo`
|
|
264
|
-
- `getSupply`, `getInflationRate`, `getVoteAccounts`
|
|
265
|
-
|
|
266
|
-
**Fee Operations**
|
|
267
|
-
|
|
268
|
-
- `getFeeForMessage`, `getFees`, `getRecentPrioritizationFees`
|
|
269
|
-
|
|
270
|
-
**WebSocket Subscriptions**
|
|
271
|
-
|
|
272
|
-
- `signatureSubscribe/Unsubscribe` ✅ (real-time notifications)
|
|
273
|
-
- Other subscriptions (stubbed but functional)
|
|
274
|
-
|
|
275
|
-
### ⚠️ Minimal/Stubbed
|
|
276
|
-
|
|
277
|
-
- Token-specific RPCs (returns defaults unless indexed)
|
|
278
|
-
- Some advanced cluster RPCs (simplified for local dev)
|
|
279
|
-
|
|
280
|
-
## 💾 Data & Persistence
|
|
281
|
-
|
|
282
|
-
### Ephemeral Mode (Default)
|
|
283
|
-
|
|
284
|
-
- SQLite database recreated on each restart
|
|
285
|
-
- Perfect for testing and development
|
|
286
|
-
- Stores full transaction history during session
|
|
287
|
-
|
|
288
|
-
### Persistent Mode
|
|
289
|
-
|
|
290
|
-
```bash
|
|
291
|
-
# Enable persistent storage
|
|
292
|
-
export SOLFORGE_DB_MODE=persistent
|
|
293
|
-
bun start
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Database Schema
|
|
297
|
-
|
|
298
|
-
```sql
|
|
299
|
-
-- Transactions with full metadata
|
|
300
|
-
CREATE TABLE transactions (
|
|
301
|
-
signature TEXT PRIMARY KEY,
|
|
302
|
-
slot INTEGER,
|
|
303
|
-
raw_transaction BLOB, -- Full transaction data
|
|
304
|
-
logs TEXT, -- JSON array of logs
|
|
305
|
-
status TEXT, -- success/error
|
|
306
|
-
fee INTEGER,
|
|
307
|
-
timestamp INTEGER
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
-- Account snapshots
|
|
311
|
-
CREATE TABLE accounts (
|
|
312
|
-
address TEXT PRIMARY KEY,
|
|
313
|
-
lamports INTEGER,
|
|
314
|
-
owner TEXT,
|
|
315
|
-
data_len INTEGER,
|
|
316
|
-
last_slot INTEGER
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
-- Address to signature mapping
|
|
320
|
-
CREATE TABLE address_signatures (
|
|
321
|
-
address TEXT,
|
|
322
|
-
signature TEXT,
|
|
323
|
-
slot INTEGER
|
|
324
|
-
);
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
## 🔌 WebSocket Support
|
|
328
|
-
|
|
329
|
-
```javascript
|
|
330
|
-
const ws = new WebSocket("ws://localhost:8900");
|
|
331
|
-
|
|
332
|
-
// Subscribe to signature updates
|
|
333
|
-
ws.send(
|
|
334
|
-
JSON.stringify({
|
|
335
|
-
jsonrpc: "2.0",
|
|
336
|
-
id: 1,
|
|
337
|
-
method: "signatureSubscribe",
|
|
338
|
-
params: ["<signature>", { commitment: "confirmed" }],
|
|
339
|
-
}),
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
// Receive real-time notifications
|
|
343
|
-
ws.onmessage = (event) => {
|
|
344
|
-
const response = JSON.parse(event.data);
|
|
345
|
-
console.log("Signature update:", response);
|
|
346
|
-
};
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
## 🧪 Testing & Validation
|
|
350
|
-
|
|
351
|
-
```bash
|
|
352
|
-
# Run comprehensive test suite
|
|
353
|
-
bun run test-client.ts
|
|
354
|
-
|
|
355
|
-
# Test specific functionality
|
|
356
|
-
bun test
|
|
357
|
-
|
|
358
|
-
# Validate against real programs
|
|
359
|
-
anchor test --skip-local-validator
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
## 🏗️ Architecture Overview
|
|
363
|
-
|
|
364
|
-
```
|
|
365
|
-
SolForge
|
|
366
|
-
├── 🧠 LiteSVM Core # In-memory execution engine
|
|
367
|
-
├── 🌐 HTTP Server # JSON-RPC over HTTP
|
|
368
|
-
├── 🔌 WebSocket Server # Real-time subscriptions
|
|
369
|
-
├── 🗃️ SQLite + Drizzle # Ephemeral data indexing
|
|
370
|
-
├── 💧 Smart Faucet # Unlimited SOL distribution
|
|
371
|
-
└── 🎯 CLI Tools # Developer utilities
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Key Components
|
|
375
|
-
|
|
376
|
-
- **`index.ts`**: Main server entry point
|
|
377
|
-
- **`server/`**: HTTP and WebSocket servers
|
|
378
|
-
- **`server/methods/`**: Modular RPC implementations
|
|
379
|
-
- **`src/cli/`**: Command-line interface
|
|
380
|
-
- **`src/config/`**: Configuration management
|
|
381
|
-
- **`src/db/`**: Database schema and operations
|
|
382
|
-
|
|
383
|
-
## 🤝 Development
|
|
384
|
-
|
|
385
|
-
### Adding New RPC Methods
|
|
386
|
-
|
|
387
|
-
1. Create method file: `server/methods/your-method.ts`
|
|
388
|
-
2. Implement the `RpcMethodHandler` interface
|
|
389
|
-
3. Export from `server/methods/index.ts`
|
|
390
|
-
4. Add to `rpcMethods` object
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
// server/methods/your-method.ts
|
|
394
|
-
import type { RpcMethodHandler } from \"../types\";
|
|
395
|
-
|
|
396
|
-
export const yourMethod: RpcMethodHandler = (id, params, context) => {
|
|
397
|
-
try {
|
|
398
|
-
const result = context.svm.someOperation();
|
|
399
|
-
return context.createSuccessResponse(id, result);
|
|
400
|
-
} catch (error: any) {
|
|
401
|
-
return context.createErrorResponse(id, -32603, \"Internal error\");
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Project Structure
|
|
407
|
-
|
|
408
|
-
```
|
|
409
|
-
solforge/
|
|
410
|
-
├── index.ts # Main entry point
|
|
411
|
-
├── server/ # Core server
|
|
412
|
-
│ ├── rpc-server.ts # HTTP server
|
|
413
|
-
│ ├── ws-server.ts # WebSocket server
|
|
414
|
-
│ ├── methods/ # RPC method implementations
|
|
415
|
-
│ │ ├── account/ # Account methods
|
|
416
|
-
│ │ ├── transaction/ # Transaction methods
|
|
417
|
-
│ │ └── index.ts # Method registry
|
|
418
|
-
│ └── lib/ # Utilities
|
|
419
|
-
├── src/ # CLI and config
|
|
420
|
-
│ ├── cli/ # Command-line interface
|
|
421
|
-
│ ├── config/ # Configuration management
|
|
422
|
-
│ └── db/ # Database operations
|
|
423
|
-
├── test-client.ts # Integration tests
|
|
424
|
-
└── docs/ # Documentation
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Development Guidelines
|
|
428
|
-
|
|
429
|
-
- Use **Bun exclusively** (no npm/yarn/node)
|
|
430
|
-
- Keep files **under 200 lines** (split when larger)
|
|
431
|
-
- Follow **kebab-case** for filenames
|
|
432
|
-
- Write **comprehensive tests**
|
|
433
|
-
- Use **TypeScript strictly**
|
|
434
|
-
|
|
435
|
-
## 🐛 Troubleshooting
|
|
436
|
-
|
|
437
|
-
### Common Issues
|
|
438
|
-
|
|
439
|
-
**Server won't start**
|
|
440
|
-
|
|
441
|
-
```bash
|
|
442
|
-
# Check if port is in use
|
|
443
|
-
lsof -i :8899
|
|
444
|
-
|
|
445
|
-
# Use different port
|
|
446
|
-
RPC_PORT=9000 bun start
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
**Anchor deploy fails**
|
|
450
|
-
|
|
451
|
-
```bash
|
|
452
|
-
# Ensure relaxed validation (default)
|
|
453
|
-
# Check logs with debug mode
|
|
454
|
-
DEBUG_RPC_LOG=1 bun start
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
**WebSocket connection issues**
|
|
458
|
-
|
|
459
|
-
```bash
|
|
460
|
-
# WebSocket runs on RPC_PORT + 1
|
|
461
|
-
# Default: ws://localhost:8900
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
**Airdrop not working**
|
|
465
|
-
|
|
466
|
-
```bash
|
|
467
|
-
# Check faucet was created
|
|
468
|
-
ls .solforge/faucet.json
|
|
469
|
-
|
|
470
|
-
# Manual airdrop via CLI
|
|
471
|
-
bun src/cli/main.ts airdrop --to <address> --sol 10
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
## 🔮 Roadmap
|
|
475
|
-
|
|
476
|
-
### v0.3.0 - Enhanced RPC Coverage
|
|
477
|
-
|
|
478
|
-
- [ ] Complete token RPC implementations
|
|
479
|
-
- [ ] Advanced subscription support
|
|
480
|
-
- [ ] Improved error handling
|
|
481
|
-
|
|
482
|
-
### v0.4.0 - Developer Experience
|
|
483
|
-
|
|
484
|
-
- [ ] Web-based dashboard
|
|
485
|
-
- [ ] Time-travel debugging
|
|
486
|
-
- [ ] Snapshot/restore functionality
|
|
487
|
-
|
|
488
|
-
### v0.5.0 - Production Features
|
|
489
|
-
|
|
490
|
-
- [ ] Clustering support
|
|
491
|
-
- [ ] Metrics and monitoring
|
|
492
|
-
- [ ] Plugin architecture
|
|
493
|
-
|
|
494
|
-
## 📄 License
|
|
495
|
-
|
|
496
|
-
MIT License - see LICENSE file for details.
|
|
497
|
-
|
|
498
|
-
## 🤝 Contributing
|
|
499
|
-
|
|
500
|
-
We welcome contributions! Please see:
|
|
501
|
-
|
|
502
|
-
- [AGENTS.md](./AGENTS.md) - Development guidelines
|
|
503
|
-
- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - Architecture details
|
|
504
|
-
- [SOLFORGE.md](./SOLFORGE.md) - Vision and roadmap
|
|
505
|
-
|
|
506
|
-
## 🙏 Acknowledgments
|
|
507
|
-
|
|
508
|
-
Built with ❤️ using:
|
|
509
|
-
|
|
510
|
-
- [LiteSVM](https://github.com/litesvm/litesvm) - Fast Solana VM
|
|
511
|
-
- [Bun](https://bun.sh) - Lightning-fast JavaScript runtime
|
|
512
|
-
- [Drizzle](https://drizzle.team) - TypeScript SQL toolkit
|
|
513
|
-
|
|
514
|
-
---
|
|
515
|
-
|
|
516
|
-
**⚡ Ready to build on Solana at lightning speed?**
|
|
517
|
-
|
|
518
|
-
```bash
|
|
519
|
-
git clone https://github.com/nitishxyz/solforge
|
|
520
|
-
cd solforge && bun install && bun start
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
_Happy coding! 🦀_
|
|
1
|
+
# SolForge – Fast, Friendly Solana Localnet
|
|
2
|
+
|
|
3
|
+
SolForge is a drop‑in, sub‑second Solana localnet powered by LiteSVM. It starts fast, works with the standard Solana toolchain, and includes a tiny GUI for everyday tasks.
|
|
4
|
+
|
|
5
|
+
Highlights
|
|
6
|
+
- Sub‑second startup and low memory usage
|
|
7
|
+
- Works with Solana CLI, Anchor, web3.js, and @solana/kit
|
|
8
|
+
- Unlimited faucet airdrops, simple persistence, WS subscriptions
|
|
9
|
+
- 90+ JSON‑RPC methods implemented (common dev flows covered)
|
|
10
|
+
- Built‑in GUI: airdrops, SPL mints, quick status
|
|
11
|
+
|
|
12
|
+
Install
|
|
13
|
+
- One‑liner: curl -fsSL https://install.solforge.sh | sh
|
|
14
|
+
- Manual: download a release binary from GitHub and put it on PATH
|
|
15
|
+
|
|
16
|
+
Quick Start
|
|
17
|
+
- Start localnet: `solforge start`
|
|
18
|
+
- RPC: `http://127.0.0.1:8899`, WS: `ws://127.0.0.1:8900`
|
|
19
|
+
- GUI: `http://127.0.0.1:42069`
|
|
20
|
+
|
|
21
|
+
CLI Basics
|
|
22
|
+
- Help: `solforge --help`
|
|
23
|
+
- Version: `solforge --version`
|
|
24
|
+
|
|
25
|
+
Commands
|
|
26
|
+
- `start` / `rpc start`: Start RPC (and WS, GUI)
|
|
27
|
+
- `config init|get|set`: Manage `sf.config.json`
|
|
28
|
+
- `airdrop --to <pubkey> --sol <amount>`: Faucet airdrop
|
|
29
|
+
- `mint`: Interactive SPL minting helper
|
|
30
|
+
- `token clone|create|adopt-authority`: SPL token tools
|
|
31
|
+
- `program clone|load|accounts clone`: Program + account helpers
|
|
32
|
+
|
|
33
|
+
Solana CLI
|
|
34
|
+
- Point to SolForge: `solana config set -u http://127.0.0.1:8899`
|
|
35
|
+
- Airdrop freely: `solana airdrop 1000`
|
|
36
|
+
- Deploy as usual: `solana program deploy ./program.so`
|
|
37
|
+
|
|
38
|
+
Anchor
|
|
39
|
+
- `anchor.toml` provider.cluster = `"http://127.0.0.1:8899"`
|
|
40
|
+
- `anchor test --skip-local-validator`
|
|
41
|
+
|
|
42
|
+
@solana/kit
|
|
43
|
+
```
|
|
44
|
+
const rpc = createSolanaRpc("http://127.0.0.1:8899");
|
|
45
|
+
await rpc.getBalance(pubkey).send();
|
|
46
|
+
await rpc.requestAirdrop(pubkey, lamports(1_000_000_000n)).send();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Configuration
|
|
50
|
+
- File: `sf.config.json` (generate with `solforge config init`)
|
|
51
|
+
- Server: ports, DB mode (ephemeral/persistent), DB path
|
|
52
|
+
- SVM: initial lamports, faucet SOL
|
|
53
|
+
- Clone: mainnet endpoint, programs/tokens/accounts to import
|
|
54
|
+
- GUI: enable and port
|
|
55
|
+
|
|
56
|
+
Environment Variables
|
|
57
|
+
- `RPC_PORT`: HTTP port (default 8899); WS is port+1
|
|
58
|
+
- `DEBUG_RPC_LOG=1`: Log all RPC calls
|
|
59
|
+
- `SOLFORGE_DB_MODE=ephemeral|persistent`
|
|
60
|
+
- `SOLFORGE_DB_PATH=.solforge/db.db`
|
|
61
|
+
- `SOLFORGE_GUI_PORT`: GUI port (default 42069)
|
|
62
|
+
|
|
63
|
+
GUI
|
|
64
|
+
- Runs with the RPC by default at `http://127.0.0.1:42069`
|
|
65
|
+
- Shows status (slot, block height, txs, blockhash, faucet)
|
|
66
|
+
- Quick forms: airdrop SOL, mint SPL tokens
|
|
67
|
+
|
|
68
|
+
From Source
|
|
69
|
+
- Requires Bun
|
|
70
|
+
- `bun install`
|
|
71
|
+
- `bun src/cli/main.ts start`
|
|
72
|
+
- `bun run build:bin` builds standalone binaries
|
|
73
|
+
|
|
74
|
+
Repo Map
|
|
75
|
+
- CLI entry (Node shim): `cli.cjs`:1
|
|
76
|
+
- CLI router: `src/cli/main.ts`:1
|
|
77
|
+
- RPC bootstrap: `src/rpc/start.ts`:1
|
|
78
|
+
- Config schema: `src/config/index.ts`:1
|
|
79
|
+
- JSON‑RPC methods (server): `server/methods/index.ts`:1
|
|
80
|
+
|
|
81
|
+
Uninstall
|
|
82
|
+
- Remove the `solforge` binary from your PATH (eg. `~/.local/bin` or `/usr/local/bin`).
|
|
83
|
+
|
|
84
|
+
Troubleshooting
|
|
85
|
+
- Port in use: set `RPC_PORT` or change `server.rpcPort` in `sf.config.json`
|
|
86
|
+
- GUI not loading: set `SOLFORGE_GUI_PORT` or disable via config
|
|
87
|
+
- Slow startup: delete `.solforge/db.db` (if using persistent mode)
|
|
88
|
+
|
|
89
|
+
License
|
|
90
|
+
- MIT — see `LICENSE` for details.
|
package/cli.cjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SolForge CLI bootstrapper
|
|
3
|
+
// - Prefers a prebuilt vendor binary (downloaded via postinstall or on first run)
|
|
4
|
+
// - Falls back to Bun-based TS entry if available
|
|
5
|
+
|
|
6
|
+
const fs = require("node:fs");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const https = require("node:https");
|
|
9
|
+
const { spawn } = require("node:child_process");
|
|
10
|
+
|
|
11
|
+
function pkg() {
|
|
12
|
+
// Resolve package.json next to this file regardless of install location
|
|
13
|
+
const p = path.join(__dirname, "package.json");
|
|
14
|
+
try { return require(p); } catch { return { version: "" }; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function assetName() {
|
|
18
|
+
const p = process.platform;
|
|
19
|
+
const a = process.arch;
|
|
20
|
+
if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
|
|
21
|
+
if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
|
|
22
|
+
if (p === "linux" && a === "x64") return "solforge-linux-x64";
|
|
23
|
+
if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
|
|
24
|
+
if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function vendorPath() {
|
|
29
|
+
const name = assetName();
|
|
30
|
+
if (!name) return null;
|
|
31
|
+
return path.join(__dirname, "vendor", name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function download(url, outPath) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const req = https.get(url, (res) => {
|
|
37
|
+
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
38
|
+
return resolve(download(res.headers.location, outPath));
|
|
39
|
+
}
|
|
40
|
+
if (res.statusCode !== 200) {
|
|
41
|
+
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
42
|
+
}
|
|
43
|
+
const file = fs.createWriteStream(outPath, { mode: process.platform === "win32" ? undefined : 0o755 });
|
|
44
|
+
res.pipe(file);
|
|
45
|
+
file.on("finish", () => file.close(() => resolve()));
|
|
46
|
+
file.on("error", reject);
|
|
47
|
+
});
|
|
48
|
+
req.on("error", reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function ensureBinary() {
|
|
53
|
+
const vp = vendorPath();
|
|
54
|
+
if (!vp) return null;
|
|
55
|
+
if (fs.existsSync(vp)) return vp;
|
|
56
|
+
|
|
57
|
+
// Respect opt-out
|
|
58
|
+
if (String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true") {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { version, repository } = pkg();
|
|
63
|
+
const repo = process.env.SOLFORGE_REPO || (repository && (typeof repository === "string" ? repository.replace(/^github:/, "") : (repository.url && (repository.url.match(/github\.com[:/](.+?)\.git$/) || [])[1]))) || "nitishxyz/solforge";
|
|
64
|
+
if (!version) return null;
|
|
65
|
+
const asset = path.basename(vp);
|
|
66
|
+
const url = `https://github.com/${repo}/releases/download/v${version}/${asset}`;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
fs.mkdirSync(path.dirname(vp), { recursive: true });
|
|
70
|
+
await download(url, vp);
|
|
71
|
+
if (process.platform !== "win32") {
|
|
72
|
+
try { fs.chmodSync(vp, 0o755); } catch {}
|
|
73
|
+
}
|
|
74
|
+
return fs.existsSync(vp) ? vp : null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function run(cmd, args) {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const child = spawn(cmd, args, { stdio: "inherit" });
|
|
83
|
+
child.on("exit", (code) => resolve(typeof code === "number" ? code : 0));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
(async () => {
|
|
88
|
+
// Fast path for --version/--help without booting the app
|
|
89
|
+
const args = process.argv.slice(2);
|
|
90
|
+
if (args.includes("-v") || args.includes("--version") || args[0] === "version") {
|
|
91
|
+
console.log(pkg().version || "");
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
if (args.includes("-h") || args.includes("--help") || args[0] === "help") {
|
|
95
|
+
console.log(`
|
|
96
|
+
solforge <command>
|
|
97
|
+
|
|
98
|
+
Commands:
|
|
99
|
+
(no command) Run setup then start RPC & WS servers
|
|
100
|
+
rpc start Start RPC & WS servers
|
|
101
|
+
start Alias for 'rpc start'
|
|
102
|
+
config init Create sf.config.json in CWD
|
|
103
|
+
config get <key> Read a config value (dot path)
|
|
104
|
+
config set <k> <v> Set a config value
|
|
105
|
+
airdrop --to <pubkey> --sol <amount> Airdrop SOL via RPC faucet
|
|
106
|
+
mint Interactive: pick mint, receiver, amount
|
|
107
|
+
token clone <mint> Clone SPL token mint + accounts
|
|
108
|
+
program clone <programId> Clone program code (and optionally accounts)
|
|
109
|
+
program accounts clone <programId> Clone accounts owned by program
|
|
110
|
+
|
|
111
|
+
Options:
|
|
112
|
+
-h, --help Show help
|
|
113
|
+
-v, --version Show version
|
|
114
|
+
`);
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const vp = await ensureBinary();
|
|
119
|
+
if (vp) {
|
|
120
|
+
const code = await run(vp, process.argv.slice(2));
|
|
121
|
+
process.exit(code);
|
|
122
|
+
}
|
|
123
|
+
// Fallback: try to run TS entry via Bun
|
|
124
|
+
const bun = process.env.SOLFORGE_BUN || "bun";
|
|
125
|
+
const entry = path.join(__dirname, "src", "cli", "main.ts");
|
|
126
|
+
const code = await run(bun, [entry, ...process.argv.slice(2)]);
|
|
127
|
+
if (code !== 0) {
|
|
128
|
+
console.error("solforge: failed to run binary and Bun fallback. Install Bun or ensure a release asset exists.");
|
|
129
|
+
}
|
|
130
|
+
process.exit(code);
|
|
131
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solforge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"url": "https://github.com/nitishxyz/solforge/issues"
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
|
-
"solforge": "
|
|
16
|
+
"solforge": "cli.cjs"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"cli.cjs",
|
|
20
|
+
"scripts",
|
|
21
21
|
"src",
|
|
22
22
|
"server",
|
|
23
23
|
"docs",
|
|
@@ -25,23 +25,24 @@
|
|
|
25
25
|
"LICENSE"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"lint": "biome check",
|
|
29
|
-
"cli": "bun src/cli/main.ts",
|
|
30
|
-
"postinstall": "node scripts/postinstall.cjs || true",
|
|
31
|
-
"build:gui": "bun build src/gui/src/main.tsx --outdir src/gui/public/build --target=browser --minify",
|
|
32
|
-
"build:css": "bunx tailwindcss -i src/gui/src/index.css -o src/gui/public/app.css --minify",
|
|
33
28
|
"build": "bun run build:bin",
|
|
34
29
|
"build:bin": "bun run build:css && bun run build:gui && bun build --compile src/cli/main.ts --outfile dist/solforge",
|
|
30
|
+
"build:bin:all": "bun run build:bin:darwin-arm64 && bun run build:bin:darwin-x64 && bun run build:bin:linux-x64 && bun run build:bin:linux-arm64 && bun run build:bin:windows-x64",
|
|
35
31
|
"build:bin:darwin-arm64": "bun run build:css && bun run build:gui && bun build --compile --target=bun-darwin-arm64 src/cli/main.ts --outfile dist/solforge-darwin-arm64",
|
|
36
32
|
"build:bin:darwin-x64": "bun run build:css && bun run build:gui && bun build --compile --target=bun-darwin-x64 src/cli/main.ts --outfile dist/solforge-darwin-x64",
|
|
37
|
-
"build:bin:linux-x64": "bun run build:css && bun run build:gui && bun build --compile --target=bun-linux-x64 src/cli/main.ts --outfile dist/solforge-linux-x64",
|
|
38
33
|
"build:bin:linux-arm64": "bun run build:css && bun run build:gui && bun build --compile --target=bun-linux-arm64 src/cli/main.ts --outfile dist/solforge-linux-arm64",
|
|
34
|
+
"build:bin:linux-x64": "bun run build:css && bun run build:gui && bun build --compile --target=bun-linux-x64 src/cli/main.ts --outfile dist/solforge-linux-x64",
|
|
39
35
|
"build:bin:windows-x64": "bun run build:css && bun run build:gui && bun build --compile --target=bun-windows-x64 src/cli/main.ts --outfile dist/solforge-windows-x64.exe",
|
|
40
|
-
"build:
|
|
41
|
-
"
|
|
36
|
+
"build:css": "bunx tailwindcss -i src/gui/src/index.css -o src/gui/public/app.css --minify",
|
|
37
|
+
"build:gui": "bun build src/gui/src/main.tsx --outdir src/gui/public/build --target=browser --minify",
|
|
38
|
+
"cli": "bun src/cli/main.ts",
|
|
39
|
+
"lint": "biome check",
|
|
40
|
+
"postinstall": "node scripts/postinstall.cjs || true",
|
|
41
|
+
"prepack": "chmod +x cli.cjs || true"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@biomejs/biome": "2.2.4",
|
|
45
|
+
"@types/aws-lambda": "8.10.152",
|
|
45
46
|
"@types/bun": "latest",
|
|
46
47
|
"@types/react": "^19.1.13",
|
|
47
48
|
"@types/react-dom": "^19.1.9",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"drizzle-orm": "^0.44.5",
|
|
65
66
|
"litesvm": "^0.3.3",
|
|
66
67
|
"react": "^19.1.1",
|
|
67
|
-
"react-dom": "^19.1.1"
|
|
68
|
+
"react-dom": "^19.1.1",
|
|
69
|
+
"sst": "3.17.13"
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# SolForge installer (downloads GitHub release binary)
|
|
5
|
+
# Usage: curl -fsSL https://sh.solforge.sh | sh
|
|
6
|
+
# Optional: SOLFORGE_VERSION=v0.2.3 curl -fsSL https://sh.solforge.sh | sh
|
|
7
|
+
|
|
8
|
+
REPO="nitishxyz/solforge"
|
|
9
|
+
BIN_NAME="solforge"
|
|
10
|
+
VERSION="${SOLFORGE_VERSION:-latest}"
|
|
11
|
+
|
|
12
|
+
info() { printf "\033[1;34m[i]\033[0m %s\n" "$*"; }
|
|
13
|
+
warn() { printf "\033[1;33m[!]\033[0m %s\n" "$*"; }
|
|
14
|
+
err() { printf "\033[1;31m[x]\033[0m %s\n" "$*" 1>&2; }
|
|
15
|
+
|
|
16
|
+
# Detect downloader
|
|
17
|
+
http_get() {
|
|
18
|
+
if command -v curl >/dev/null 2>&1; then
|
|
19
|
+
curl -fsSL "$1"
|
|
20
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
21
|
+
wget -qO- "$1"
|
|
22
|
+
else
|
|
23
|
+
err "Need 'curl' or 'wget' to download binaries"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
http_down() {
|
|
29
|
+
dest="$2"
|
|
30
|
+
if command -v curl >/dev/null 2>&1; then
|
|
31
|
+
curl -fL --progress-bar -o "$dest" "$1"
|
|
32
|
+
else
|
|
33
|
+
wget -qO "$dest" "$1"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# OS/arch detection
|
|
38
|
+
uname_s=$(uname -s 2>/dev/null || echo unknown)
|
|
39
|
+
uname_m=$(uname -m 2>/dev/null || echo unknown)
|
|
40
|
+
|
|
41
|
+
case "$uname_s" in
|
|
42
|
+
Linux) os="linux" ;;
|
|
43
|
+
Darwin) os="darwin" ;;
|
|
44
|
+
*) err "Unsupported OS: $uname_s"; exit 1 ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
case "$uname_m" in
|
|
48
|
+
x86_64|amd64) arch="x64" ;;
|
|
49
|
+
arm64|aarch64) arch="arm64" ;;
|
|
50
|
+
*) err "Unsupported architecture: $uname_m"; exit 1 ;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
asset="${BIN_NAME}-${os}-${arch}"
|
|
54
|
+
ext=""
|
|
55
|
+
filename="$asset$ext"
|
|
56
|
+
|
|
57
|
+
# Build URL
|
|
58
|
+
if [ "$VERSION" = "latest" ]; then
|
|
59
|
+
base="https://github.com/$REPO/releases/latest/download"
|
|
60
|
+
else
|
|
61
|
+
base="https://github.com/$REPO/releases/download/$VERSION"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
url="$base/$filename"
|
|
65
|
+
|
|
66
|
+
info "Installing $BIN_NAME ($os/$arch) from: $url"
|
|
67
|
+
|
|
68
|
+
# Download
|
|
69
|
+
tmpdir=${TMPDIR:-/tmp}
|
|
70
|
+
tmpfile="$tmpdir/$filename"
|
|
71
|
+
http_down "$url" "$tmpfile"
|
|
72
|
+
|
|
73
|
+
# Make executable
|
|
74
|
+
chmod +x "$tmpfile"
|
|
75
|
+
|
|
76
|
+
# Choose install dir
|
|
77
|
+
install_dir="/usr/local/bin"
|
|
78
|
+
if [ ! -w "$install_dir" ]; then
|
|
79
|
+
if command -v sudo >/dev/null 2>&1; then
|
|
80
|
+
sudo_cmd="sudo"
|
|
81
|
+
else
|
|
82
|
+
sudo_cmd=""
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if [ -n "$sudo_cmd" ] && [ -d "$install_dir" ] && [ ! -w "$install_dir" ]; then
|
|
87
|
+
info "Moving binary to $install_dir (requires sudo)"
|
|
88
|
+
$sudo_cmd mv "$tmpfile" "$install_dir/$BIN_NAME"
|
|
89
|
+
else
|
|
90
|
+
# Fallback to user bin
|
|
91
|
+
user_bin="$HOME/.local/bin"
|
|
92
|
+
mkdir -p "$user_bin"
|
|
93
|
+
info "Moving binary to $user_bin"
|
|
94
|
+
mv "$tmpfile" "$user_bin/$BIN_NAME"
|
|
95
|
+
install_dir="$user_bin"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Verify
|
|
99
|
+
if "$install_dir/$BIN_NAME" --version >/dev/null 2>&1; then
|
|
100
|
+
ver=$("$install_dir/$BIN_NAME" --version 2>/dev/null || true)
|
|
101
|
+
info "$BIN_NAME installed: $ver"
|
|
102
|
+
else
|
|
103
|
+
warn "Installed, but failed to run --version"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# PATH hint
|
|
107
|
+
case ":$PATH:" in
|
|
108
|
+
*":$install_dir:"*) :;;
|
|
109
|
+
*) warn "Add $install_dir to your PATH to use '$BIN_NAME'" ;;
|
|
110
|
+
esac
|
|
111
|
+
|
|
112
|
+
info "Done. Run: $BIN_NAME --help"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
SolForge postinstall: fetch the platform-specific prebuilt binary from GitHub Releases.
|
|
4
|
+
- Skips if SOLFORGE_SKIP_DOWNLOAD=true
|
|
5
|
+
- Falls back silently on errors (CLI will still work via Bun if installed)
|
|
6
|
+
*/
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const https = require("https");
|
|
10
|
+
|
|
11
|
+
function log(msg) {
|
|
12
|
+
console.log(`[solforge] ${msg}`);
|
|
13
|
+
}
|
|
14
|
+
function warn(msg) {
|
|
15
|
+
console.warn(`[solforge] ${msg}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true") {
|
|
19
|
+
log("Skipping binary download due to SOLFORGE_SKIP_DOWNLOAD=true");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assetName() {
|
|
24
|
+
const p = process.platform;
|
|
25
|
+
const a = process.arch;
|
|
26
|
+
if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
|
|
27
|
+
if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
|
|
28
|
+
if (p === "linux" && a === "x64") return "solforge-linux-x64";
|
|
29
|
+
if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
|
|
30
|
+
if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getRepo() {
|
|
35
|
+
try {
|
|
36
|
+
const pkg = require(path.join(__dirname, "..", "package.json"));
|
|
37
|
+
if (pkg.repository) {
|
|
38
|
+
if (typeof pkg.repository === "string") return pkg.repository.replace(/^github:/, "");
|
|
39
|
+
if (pkg.repository.url) {
|
|
40
|
+
const m = pkg.repository.url.match(/github\.com[:/](.+?)\.git$/);
|
|
41
|
+
if (m) return m[1];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
return process.env.SOLFORGE_REPO || "nitishxyz/solforge";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getVersion() {
|
|
49
|
+
try {
|
|
50
|
+
const pkg = require(path.join(__dirname, "..", "package.json"));
|
|
51
|
+
return pkg.version;
|
|
52
|
+
} catch {
|
|
53
|
+
return process.env.npm_package_version || "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const name = assetName();
|
|
58
|
+
if (!name) {
|
|
59
|
+
warn(`No prebuilt binary for ${process.platform}/${process.arch}; skipping`);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const version = getVersion();
|
|
64
|
+
if (!version) {
|
|
65
|
+
warn("Unable to determine package version; skipping binary download");
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const repo = getRepo();
|
|
70
|
+
const url = `https://github.com/${repo}/releases/download/v${version}/${name}`;
|
|
71
|
+
|
|
72
|
+
const vendorDir = path.join(__dirname, "..", "vendor");
|
|
73
|
+
const outPath = path.join(vendorDir, name);
|
|
74
|
+
|
|
75
|
+
if (fs.existsSync(outPath)) {
|
|
76
|
+
log(`Binary already present at vendor/${name}`);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
81
|
+
|
|
82
|
+
function download(to, from, cb, redirects = 0) {
|
|
83
|
+
const req = https.get(from, (res) => {
|
|
84
|
+
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location && redirects < 5) {
|
|
85
|
+
return download(to, res.headers.location, cb, redirects + 1);
|
|
86
|
+
}
|
|
87
|
+
if (res.statusCode !== 200) {
|
|
88
|
+
return cb(new Error(`HTTP ${res.statusCode} for ${from}`));
|
|
89
|
+
}
|
|
90
|
+
const file = fs.createWriteStream(to, { mode: 0o755 });
|
|
91
|
+
res.pipe(file);
|
|
92
|
+
file.on("finish", () => file.close(cb));
|
|
93
|
+
});
|
|
94
|
+
req.on("error", (err) => cb(err));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
log(`Fetching ${name} for v${version}...`);
|
|
98
|
+
download(outPath, url, (err) => {
|
|
99
|
+
if (err) {
|
|
100
|
+
warn(`Could not download prebuilt binary: ${err.message}`);
|
|
101
|
+
warn("CLI will fall back to running via Bun if available.");
|
|
102
|
+
try { fs.unlinkSync(outPath); } catch {}
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
if (process.platform !== "win32") {
|
|
106
|
+
try { fs.chmodSync(outPath, 0o755); } catch {}
|
|
107
|
+
}
|
|
108
|
+
log(`Installed vendor/${name}`);
|
|
109
|
+
});
|
|
110
|
+
|
package/src/cli/main.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
// Minimal, fast CLI router with @clack/prompts for UX
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
|
+
// Load version for --version in both bun script and compiled binary
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
5
|
+
import pkg from "../../package.json" assert { type: "json" };
|
|
3
6
|
|
|
4
7
|
// Robust arg parsing for both bun script and compiled binary
|
|
5
8
|
const known = new Set([
|
|
6
9
|
"help",
|
|
7
10
|
"-h",
|
|
8
11
|
"--help",
|
|
12
|
+
"version",
|
|
13
|
+
"-v",
|
|
14
|
+
"--version",
|
|
9
15
|
"rpc",
|
|
10
16
|
"start",
|
|
11
17
|
"config",
|
|
@@ -21,16 +27,21 @@ const argv = firstIdx >= 0 ? raw.slice(firstIdx) : [];
|
|
|
21
27
|
async function main() {
|
|
22
28
|
const [cmd, sub, ...rest] = argv;
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (!cmd) {
|
|
31
|
+
const { runSolforge } = await import("./run-solforge");
|
|
32
|
+
await runSolforge();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
if (cmd === "help" || cmd === "-h" || cmd === "--help") {
|
|
37
|
+
printHelp();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (cmd === "version" || cmd === "-v" || cmd === "--version") {
|
|
42
|
+
printVersion();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
34
45
|
|
|
35
46
|
// Alias: solforge start -> solforge rpc start
|
|
36
47
|
if (cmd === "start") {
|
|
@@ -104,7 +115,7 @@ async function main() {
|
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
function printHelp() {
|
|
107
|
-
|
|
118
|
+
console.log(`
|
|
108
119
|
solforge <command>
|
|
109
120
|
|
|
110
121
|
Commands:
|
|
@@ -119,12 +130,22 @@ Commands:
|
|
|
119
130
|
token clone <mint> Clone SPL token mint + accounts
|
|
120
131
|
program clone <programId> Clone program code (and optionally accounts)
|
|
121
132
|
program accounts clone <programId> Clone accounts owned by program
|
|
133
|
+
|
|
134
|
+
Options:
|
|
135
|
+
-h, --help Show help
|
|
136
|
+
-v, --version Show version
|
|
122
137
|
`);
|
|
123
138
|
}
|
|
124
139
|
|
|
125
140
|
async function unknownCommand(parts: (string | undefined)[]) {
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
p.log.error(`Unknown command: ${parts.filter(Boolean).join(" ")}`);
|
|
142
|
+
printHelp();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function printVersion() {
|
|
146
|
+
// Prefer package.json version if available
|
|
147
|
+
const v = (pkg as any)?.version ?? "";
|
|
148
|
+
console.log(String(v));
|
|
128
149
|
}
|
|
129
150
|
|
|
130
151
|
main();
|