solforge 0.2.4 → 0.2.6

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.
Files changed (82) hide show
  1. package/README.md +471 -79
  2. package/cli.cjs +106 -78
  3. package/package.json +1 -1
  4. package/scripts/install.sh +1 -1
  5. package/scripts/postinstall.cjs +69 -61
  6. package/server/lib/base58.ts +1 -1
  7. package/server/methods/account/get-account-info.ts +3 -7
  8. package/server/methods/account/get-balance.ts +3 -7
  9. package/server/methods/account/get-multiple-accounts.ts +2 -1
  10. package/server/methods/account/get-parsed-account-info.ts +3 -7
  11. package/server/methods/account/parsers/index.ts +2 -2
  12. package/server/methods/account/parsers/loader-upgradeable.ts +14 -1
  13. package/server/methods/account/parsers/spl-token.ts +29 -10
  14. package/server/methods/account/request-airdrop.ts +44 -31
  15. package/server/methods/block/get-block.ts +3 -7
  16. package/server/methods/block/get-blocks-with-limit.ts +3 -7
  17. package/server/methods/block/is-blockhash-valid.ts +3 -7
  18. package/server/methods/get-address-lookup-table.ts +3 -7
  19. package/server/methods/program/get-program-accounts.ts +9 -9
  20. package/server/methods/program/get-token-account-balance.ts +3 -7
  21. package/server/methods/program/get-token-accounts-by-delegate.ts +4 -3
  22. package/server/methods/program/get-token-accounts-by-owner.ts +61 -35
  23. package/server/methods/program/get-token-largest-accounts.ts +3 -2
  24. package/server/methods/program/get-token-supply.ts +3 -2
  25. package/server/methods/solforge/index.ts +9 -6
  26. package/server/methods/transaction/get-parsed-transaction.ts +3 -7
  27. package/server/methods/transaction/get-signature-statuses.ts +14 -7
  28. package/server/methods/transaction/get-signatures-for-address.ts +3 -7
  29. package/server/methods/transaction/get-transaction.ts +167 -81
  30. package/server/methods/transaction/send-transaction.ts +29 -16
  31. package/server/methods/transaction/simulate-transaction.ts +3 -2
  32. package/server/rpc-server.ts +47 -34
  33. package/server/types.ts +9 -6
  34. package/server/ws-server.ts +15 -8
  35. package/src/api-server-entry.ts +91 -91
  36. package/src/cli/commands/airdrop.ts +2 -2
  37. package/src/cli/commands/config.ts +2 -2
  38. package/src/cli/commands/mint.ts +3 -3
  39. package/src/cli/commands/program-clone.ts +9 -11
  40. package/src/cli/commands/program-load.ts +3 -3
  41. package/src/cli/commands/rpc-start.ts +8 -5
  42. package/src/cli/commands/token-adopt-authority.ts +1 -1
  43. package/src/cli/commands/token-clone.ts +5 -6
  44. package/src/cli/commands/token-create.ts +5 -5
  45. package/src/cli/main.ts +38 -37
  46. package/src/cli/run-solforge.ts +20 -6
  47. package/src/cli/setup-wizard.ts +8 -6
  48. package/src/commands/add-program.ts +324 -328
  49. package/src/commands/init.ts +106 -106
  50. package/src/commands/list.ts +125 -125
  51. package/src/commands/mint.ts +247 -248
  52. package/src/commands/start.ts +837 -833
  53. package/src/commands/status.ts +80 -80
  54. package/src/commands/stop.ts +381 -382
  55. package/src/config/index.ts +33 -17
  56. package/src/config/manager.ts +150 -150
  57. package/src/db/index.ts +2 -2
  58. package/src/db/tx-store.ts +12 -8
  59. package/src/gui/public/app.css +1556 -1
  60. package/src/gui/public/build/main.css +1569 -1
  61. package/src/gui/server.ts +21 -22
  62. package/src/gui/src/api.ts +1 -1
  63. package/src/gui/src/app.tsx +96 -45
  64. package/src/gui/src/components/airdrop-mint-form.tsx +49 -19
  65. package/src/gui/src/components/clone-program-modal.tsx +31 -12
  66. package/src/gui/src/components/clone-token-modal.tsx +32 -13
  67. package/src/gui/src/components/modal.tsx +18 -11
  68. package/src/gui/src/components/programs-panel.tsx +27 -15
  69. package/src/gui/src/components/status-panel.tsx +32 -18
  70. package/src/gui/src/components/tokens-panel.tsx +25 -19
  71. package/src/gui/src/index.css +491 -463
  72. package/src/index.ts +177 -149
  73. package/src/rpc/start.ts +1 -1
  74. package/src/services/api-server.ts +494 -475
  75. package/src/services/port-manager.ts +164 -167
  76. package/src/services/process-registry.ts +144 -145
  77. package/src/services/program-cloner.ts +312 -312
  78. package/src/services/token-cloner.ts +799 -797
  79. package/src/services/validator.ts +288 -290
  80. package/src/types/config.ts +72 -72
  81. package/src/utils/shell.ts +75 -75
  82. package/src/utils/token-loader.ts +78 -78
@@ -1,295 +1,293 @@
1
- import { spawn, ChildProcess } from "child_process";
2
- import { existsSync } from "fs";
3
- import { join } from "path";
1
+ import { type ChildProcess, spawn } from "node:child_process";
4
2
  import type {
5
- ValidatorState,
6
- ValidatorStatus,
7
- LocalnetConfig,
8
- OperationResult,
3
+ LocalnetConfig,
4
+ OperationResult,
5
+ ValidatorState,
6
+ ValidatorStatus,
9
7
  } from "../types/config.js";
10
8
 
11
9
  export class ValidatorService {
12
- private process: ChildProcess | null = null;
13
- private state: ValidatorState;
14
- private config: LocalnetConfig;
15
-
16
- constructor(config: LocalnetConfig) {
17
- this.config = config;
18
- this.state = {
19
- status: "stopped",
20
- port: config.port,
21
- faucetPort: config.faucetPort,
22
- rpcUrl: `http://${config.bindAddress}:${config.port}`,
23
- wsUrl: `ws://${config.bindAddress}:${config.port}`,
24
- logs: [],
25
- };
26
- }
27
-
28
- /**
29
- * Start the validator with the given configuration
30
- */
31
- async start(
32
- programs: string[] = [],
33
- tokens: string[] = []
34
- ): Promise<OperationResult<ValidatorState>> {
35
- if (this.state.status === "running") {
36
- return {
37
- success: false,
38
- error: "Validator is already running",
39
- data: this.state,
40
- };
41
- }
42
-
43
- try {
44
- this.updateStatus("starting");
45
-
46
- const args = this.buildValidatorArgs(programs, tokens);
47
-
48
- this.process = spawn("solana-test-validator", args, {
49
- stdio: ["pipe", "pipe", "pipe"],
50
- detached: false,
51
- });
52
-
53
- this.state.pid = this.process.pid;
54
- this.state.startTime = new Date();
55
-
56
- // Handle process events
57
- this.setupProcessHandlers();
58
-
59
- // Wait for validator to be ready
60
- await this.waitForReady();
61
-
62
- this.updateStatus("running");
63
-
64
- return {
65
- success: true,
66
- data: this.state,
67
- };
68
- } catch (error) {
69
- this.updateStatus("error");
70
- this.state.error = error instanceof Error ? error.message : String(error);
71
-
72
- return {
73
- success: false,
74
- error: this.state.error,
75
- data: this.state,
76
- };
77
- }
78
- }
79
-
80
- /**
81
- * Stop the validator
82
- */
83
- async stop(): Promise<OperationResult<ValidatorState>> {
84
- if (this.state.status === "stopped") {
85
- return {
86
- success: true,
87
- data: this.state,
88
- };
89
- }
90
-
91
- try {
92
- this.updateStatus("stopping");
93
-
94
- if (this.process) {
95
- this.process.kill("SIGTERM");
96
-
97
- // Wait for graceful shutdown
98
- await new Promise<void>((resolve) => {
99
- const timeout = setTimeout(() => {
100
- if (this.process) {
101
- this.process.kill("SIGKILL");
102
- }
103
- resolve();
104
- }, 5000);
105
-
106
- this.process?.on("exit", () => {
107
- clearTimeout(timeout);
108
- resolve();
109
- });
110
- });
111
- }
112
-
113
- this.cleanup();
114
- this.updateStatus("stopped");
115
-
116
- return {
117
- success: true,
118
- data: this.state,
119
- };
120
- } catch (error) {
121
- return {
122
- success: false,
123
- error: error instanceof Error ? error.message : String(error),
124
- data: this.state,
125
- };
126
- }
127
- }
128
-
129
- /**
130
- * Get current validator state
131
- */
132
- getState(): ValidatorState {
133
- return { ...this.state };
134
- }
135
-
136
- /**
137
- * Check if validator is running
138
- */
139
- isRunning(): boolean {
140
- return this.state.status === "running";
141
- }
142
-
143
- /**
144
- * Get recent logs
145
- */
146
- getLogs(count = 100): string[] {
147
- return this.state.logs.slice(-count);
148
- }
149
-
150
- /**
151
- * Build validator arguments based on configuration
152
- */
153
- private buildValidatorArgs(
154
- programs: string[] = [],
155
- tokens: string[] = []
156
- ): string[] {
157
- const args: string[] = [];
158
-
159
- // Basic configuration
160
- args.push("--rpc-port", this.config.port.toString());
161
- args.push("--faucet-port", this.config.faucetPort.toString());
162
- args.push("--bind-address", this.config.bindAddress);
163
-
164
- if (this.config.reset) {
165
- args.push("--reset");
166
- }
167
-
168
- if (this.config.quiet) {
169
- args.push("--quiet");
170
- }
171
-
172
- if (this.config.ledgerPath) {
173
- args.push("--ledger", this.config.ledgerPath);
174
- }
175
-
176
- // Set log level
177
- args.push("--log");
178
-
179
- // Clone programs
180
- for (const programId of programs) {
181
- args.push("--clone", programId);
182
- }
183
-
184
- // Clone tokens (these would be mint addresses)
185
- for (const tokenMint of tokens) {
186
- args.push("--clone", tokenMint);
187
- }
188
-
189
- // Always specify mainnet as the source for cloning
190
- if (programs.length > 0 || tokens.length > 0) {
191
- args.push("--url", "https://api.mainnet-beta.solana.com");
192
- }
193
-
194
- return args;
195
- }
196
-
197
- /**
198
- * Setup process event handlers
199
- */
200
- private setupProcessHandlers(): void {
201
- if (!this.process) return;
202
-
203
- this.process.stdout?.on("data", (data: Buffer) => {
204
- const log = data.toString().trim();
205
- this.addLog(`[STDOUT] ${log}`);
206
- });
207
-
208
- this.process.stderr?.on("data", (data: Buffer) => {
209
- const log = data.toString().trim();
210
- this.addLog(`[STDERR] ${log}`);
211
- });
212
-
213
- this.process.on("error", (error) => {
214
- this.addLog(`[ERROR] ${error.message}`);
215
- this.updateStatus("error");
216
- this.state.error = error.message;
217
- });
218
-
219
- this.process.on("exit", (code, signal) => {
220
- this.addLog(`[EXIT] Process exited with code ${code}, signal ${signal}`);
221
- this.cleanup();
222
-
223
- if (this.state.status !== "stopping") {
224
- this.updateStatus(code === 0 ? "stopped" : "error");
225
- if (code !== 0) {
226
- this.state.error = `Process exited with code ${code}`;
227
- }
228
- }
229
- });
230
- }
231
-
232
- /**
233
- * Wait for validator to be ready
234
- */
235
- private async waitForReady(timeout = 30000): Promise<void> {
236
- const startTime = Date.now();
237
-
238
- while (Date.now() - startTime < timeout) {
239
- try {
240
- // Try to connect to the RPC endpoint
241
- const response = await fetch(this.state.rpcUrl, {
242
- method: "POST",
243
- headers: { "Content-Type": "application/json" },
244
- body: JSON.stringify({
245
- jsonrpc: "2.0",
246
- id: 1,
247
- method: "getHealth",
248
- }),
249
- });
250
-
251
- if (response.ok) {
252
- return; // Validator is ready
253
- }
254
- } catch (error) {
255
- // Continue waiting
256
- }
257
-
258
- await new Promise((resolve) => setTimeout(resolve, 1000));
259
- }
260
-
261
- throw new Error("Validator failed to start within timeout period");
262
- }
263
-
264
- /**
265
- * Update validator status
266
- */
267
- private updateStatus(status: ValidatorStatus): void {
268
- this.state.status = status;
269
- this.addLog(`[STATUS] Validator status changed to: ${status}`);
270
- }
271
-
272
- /**
273
- * Add log entry
274
- */
275
- private addLog(message: string): void {
276
- const timestamp = new Date().toISOString();
277
- const logEntry = `[${timestamp}] ${message}`;
278
- this.state.logs.push(logEntry);
279
-
280
- // Keep only last 1000 log entries
281
- if (this.state.logs.length > 1000) {
282
- this.state.logs = this.state.logs.slice(-1000);
283
- }
284
- }
285
-
286
- /**
287
- * Clean up process references
288
- */
289
- private cleanup(): void {
290
- this.process = null;
291
- this.state.pid = undefined;
292
- this.state.startTime = undefined;
293
- this.state.error = undefined;
294
- }
10
+ private process: ChildProcess | null = null;
11
+ private state: ValidatorState;
12
+ private config: LocalnetConfig;
13
+
14
+ constructor(config: LocalnetConfig) {
15
+ this.config = config;
16
+ this.state = {
17
+ status: "stopped",
18
+ port: config.port,
19
+ faucetPort: config.faucetPort,
20
+ rpcUrl: `http://${config.bindAddress}:${config.port}`,
21
+ wsUrl: `ws://${config.bindAddress}:${config.port}`,
22
+ logs: [],
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Start the validator with the given configuration
28
+ */
29
+ async start(
30
+ programs: string[] = [],
31
+ tokens: string[] = [],
32
+ ): Promise<OperationResult<ValidatorState>> {
33
+ if (this.state.status === "running") {
34
+ return {
35
+ success: false,
36
+ error: "Validator is already running",
37
+ data: this.state,
38
+ };
39
+ }
40
+
41
+ try {
42
+ this.updateStatus("starting");
43
+
44
+ const args = this.buildValidatorArgs(programs, tokens);
45
+
46
+ this.process = spawn("solana-test-validator", args, {
47
+ stdio: ["pipe", "pipe", "pipe"],
48
+ detached: false,
49
+ });
50
+
51
+ this.state.pid = this.process.pid;
52
+ this.state.startTime = new Date();
53
+
54
+ // Handle process events
55
+ this.setupProcessHandlers();
56
+
57
+ // Wait for validator to be ready
58
+ await this.waitForReady();
59
+
60
+ this.updateStatus("running");
61
+
62
+ return {
63
+ success: true,
64
+ data: this.state,
65
+ };
66
+ } catch (error) {
67
+ this.updateStatus("error");
68
+ this.state.error = error instanceof Error ? error.message : String(error);
69
+
70
+ return {
71
+ success: false,
72
+ error: this.state.error,
73
+ data: this.state,
74
+ };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Stop the validator
80
+ */
81
+ async stop(): Promise<OperationResult<ValidatorState>> {
82
+ if (this.state.status === "stopped") {
83
+ return {
84
+ success: true,
85
+ data: this.state,
86
+ };
87
+ }
88
+
89
+ try {
90
+ this.updateStatus("stopping");
91
+
92
+ if (this.process) {
93
+ this.process.kill("SIGTERM");
94
+
95
+ // Wait for graceful shutdown
96
+ await new Promise<void>((resolve) => {
97
+ const timeout = setTimeout(() => {
98
+ if (this.process) {
99
+ this.process.kill("SIGKILL");
100
+ }
101
+ resolve();
102
+ }, 5000);
103
+
104
+ this.process?.on("exit", () => {
105
+ clearTimeout(timeout);
106
+ resolve();
107
+ });
108
+ });
109
+ }
110
+
111
+ this.cleanup();
112
+ this.updateStatus("stopped");
113
+
114
+ return {
115
+ success: true,
116
+ data: this.state,
117
+ };
118
+ } catch (error) {
119
+ return {
120
+ success: false,
121
+ error: error instanceof Error ? error.message : String(error),
122
+ data: this.state,
123
+ };
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get current validator state
129
+ */
130
+ getState(): ValidatorState {
131
+ return { ...this.state };
132
+ }
133
+
134
+ /**
135
+ * Check if validator is running
136
+ */
137
+ isRunning(): boolean {
138
+ return this.state.status === "running";
139
+ }
140
+
141
+ /**
142
+ * Get recent logs
143
+ */
144
+ getLogs(count = 100): string[] {
145
+ return this.state.logs.slice(-count);
146
+ }
147
+
148
+ /**
149
+ * Build validator arguments based on configuration
150
+ */
151
+ private buildValidatorArgs(
152
+ programs: string[] = [],
153
+ tokens: string[] = [],
154
+ ): string[] {
155
+ const args: string[] = [];
156
+
157
+ // Basic configuration
158
+ args.push("--rpc-port", this.config.port.toString());
159
+ args.push("--faucet-port", this.config.faucetPort.toString());
160
+ args.push("--bind-address", this.config.bindAddress);
161
+
162
+ if (this.config.reset) {
163
+ args.push("--reset");
164
+ }
165
+
166
+ if (this.config.quiet) {
167
+ args.push("--quiet");
168
+ }
169
+
170
+ if (this.config.ledgerPath) {
171
+ args.push("--ledger", this.config.ledgerPath);
172
+ }
173
+
174
+ // Set log level
175
+ args.push("--log");
176
+
177
+ // Clone programs
178
+ for (const programId of programs) {
179
+ args.push("--clone", programId);
180
+ }
181
+
182
+ // Clone tokens (these would be mint addresses)
183
+ for (const tokenMint of tokens) {
184
+ args.push("--clone", tokenMint);
185
+ }
186
+
187
+ // Always specify mainnet as the source for cloning
188
+ if (programs.length > 0 || tokens.length > 0) {
189
+ args.push("--url", "https://api.mainnet-beta.solana.com");
190
+ }
191
+
192
+ return args;
193
+ }
194
+
195
+ /**
196
+ * Setup process event handlers
197
+ */
198
+ private setupProcessHandlers(): void {
199
+ if (!this.process) return;
200
+
201
+ this.process.stdout?.on("data", (data: Buffer) => {
202
+ const log = data.toString().trim();
203
+ this.addLog(`[STDOUT] ${log}`);
204
+ });
205
+
206
+ this.process.stderr?.on("data", (data: Buffer) => {
207
+ const log = data.toString().trim();
208
+ this.addLog(`[STDERR] ${log}`);
209
+ });
210
+
211
+ this.process.on("error", (error) => {
212
+ this.addLog(`[ERROR] ${error.message}`);
213
+ this.updateStatus("error");
214
+ this.state.error = error.message;
215
+ });
216
+
217
+ this.process.on("exit", (code, signal) => {
218
+ this.addLog(`[EXIT] Process exited with code ${code}, signal ${signal}`);
219
+ this.cleanup();
220
+
221
+ if (this.state.status !== "stopping") {
222
+ this.updateStatus(code === 0 ? "stopped" : "error");
223
+ if (code !== 0) {
224
+ this.state.error = `Process exited with code ${code}`;
225
+ }
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Wait for validator to be ready
232
+ */
233
+ private async waitForReady(timeout = 30000): Promise<void> {
234
+ const startTime = Date.now();
235
+
236
+ while (Date.now() - startTime < timeout) {
237
+ try {
238
+ // Try to connect to the RPC endpoint
239
+ const response = await fetch(this.state.rpcUrl, {
240
+ method: "POST",
241
+ headers: { "Content-Type": "application/json" },
242
+ body: JSON.stringify({
243
+ jsonrpc: "2.0",
244
+ id: 1,
245
+ method: "getHealth",
246
+ }),
247
+ });
248
+
249
+ if (response.ok) {
250
+ return; // Validator is ready
251
+ }
252
+ } catch (_error) {
253
+ // Continue waiting
254
+ }
255
+
256
+ await new Promise((resolve) => setTimeout(resolve, 1000));
257
+ }
258
+
259
+ throw new Error("Validator failed to start within timeout period");
260
+ }
261
+
262
+ /**
263
+ * Update validator status
264
+ */
265
+ private updateStatus(status: ValidatorStatus): void {
266
+ this.state.status = status;
267
+ this.addLog(`[STATUS] Validator status changed to: ${status}`);
268
+ }
269
+
270
+ /**
271
+ * Add log entry
272
+ */
273
+ private addLog(message: string): void {
274
+ const timestamp = new Date().toISOString();
275
+ const logEntry = `[${timestamp}] ${message}`;
276
+ this.state.logs.push(logEntry);
277
+
278
+ // Keep only last 1000 log entries
279
+ if (this.state.logs.length > 1000) {
280
+ this.state.logs = this.state.logs.slice(-1000);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Clean up process references
286
+ */
287
+ private cleanup(): void {
288
+ this.process = null;
289
+ this.state.pid = undefined;
290
+ this.state.startTime = undefined;
291
+ this.state.error = undefined;
292
+ }
295
293
  }