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 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 – Lightning-Fast Solana Development Server
2
-
3
- A blazing-fast, drop-in replacement for `solana-test-validator` built on LiteSVM. Get a full Solana development environment running in under 1 second with comprehensive RPC support and zero configuration.
4
-
5
- ## 🚀 Why SolForge?
6
-
7
- | Feature | solana-test-validator | SolForge |
8
- | ----------------- | --------------------- | ---------------- |
9
- | **Startup Time** | 10-30 seconds | < 1 second |
10
- | **Memory Usage** | 500MB+ | ~50MB |
11
- | **Configuration** | Complex setup | Zero config |
12
- | **Airdrops** | Rate limited | Unlimited |
13
- | **Database** | Full ledger | Ephemeral SQLite |
14
-
15
- ## ✨ Features
16
-
17
- - **Sub-second startup** with LiteSVM in-memory execution
18
- - 🔄 **Drop-in replacement** for solana-test-validator
19
- - 💧 **Unlimited airdrops** via real faucet transfers
20
- - 🗃️ **Smart persistence** with ephemeral SQLite + Drizzle
21
- - 🔌 **WebSocket support** for signature subscriptions
22
- - 🧰 **Universal compatibility** with Solana CLI, Anchor, @solana/kit, web3.js
23
- - 📊 **Rich RPC coverage** (90+ methods implemented)
24
- - 🖥️ **Built-in GUI dashboard** for airdrops, mints, and asset management
25
- - 🎯 **CLI tools** for tokens, programs, and accounts
26
-
27
- ## 📦 Installation & Quick Start
28
-
29
- ### Option 1: From Source (Recommended)
30
-
31
- ```bash
32
- # Clone and install
33
- git clone https://github.com/nitishxyz/solforge
34
- cd solforge
35
- bun install
36
-
37
- # Start the server
38
- bun start
39
- # or with debug logging
40
- DEBUG_RPC_LOG=1 bun start
41
- ```
42
-
43
- ### Option 2: Compiled Binary (Coming Soon)
44
-
45
- ```bash
46
- # Download and run
47
- curl -L https://github.com/nitishxyz/solforge/releases/latest/download/solforge-$(uname -s)-$(uname -m) -o solforge
48
- chmod +x solforge
49
- ./solforge # first run guides you through setup
50
- ```
51
-
52
- ### Option 3: CLI Development
53
-
54
- ```bash
55
- # Use the CLI directly
56
- bun src/cli/main.ts start
57
- bun src/cli/main.ts config init # Create sf.config.json
58
- ```
59
-
60
- ## 🎯 Usage Examples
61
-
62
- ### With Solana CLI
63
-
64
- ```bash
65
- # Connect to SolForge
66
- solana config set -u http://localhost:8899
67
-
68
- # Get unlimited airdrops
69
- solana airdrop 1000
70
-
71
- # Deploy programs normally
72
- solana program deploy ./program.so
73
- ```
74
-
75
- ### With @solana/kit
76
-
77
- ```typescript
78
- import { createSolanaRpc, generateKeyPairSigner, lamports } from "@solana/kit";
79
-
80
- const rpc = createSolanaRpc("http://localhost:8899");
81
-
82
- // Get account balance
83
- const { value: balance } = await rpc.getBalance(address).send();
84
-
85
- // Request airdrops (no limits!)
86
- const signature = await rpc
87
- .requestAirdrop(
88
- address,
89
- lamports(1_000_000_000n), // 1 SOL
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 dropin, 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.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": "bin/solforge"
16
+ "solforge": "cli.cjs"
17
17
  },
18
18
  "files": [
19
- "bin",
20
- "dist",
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: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",
41
- "prepack": "chmod +x bin/solforge || true"
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
- if (!cmd) {
25
- const { runSolforge } = await import("./run-solforge");
26
- await runSolforge();
27
- return;
28
- }
30
+ if (!cmd) {
31
+ const { runSolforge } = await import("./run-solforge");
32
+ await runSolforge();
33
+ return;
34
+ }
29
35
 
30
- if (cmd === "help" || cmd === "-h" || cmd === "--help") {
31
- printHelp();
32
- return;
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
- console.log(`
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
- p.log.error(`Unknown command: ${parts.filter(Boolean).join(" ")}`);
127
- printHelp();
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();