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,176 +1,173 @@
1
1
  import { processRegistry } from "./process-registry.js";
2
2
 
3
3
  export interface PortAllocation {
4
- rpcPort: number;
5
- faucetPort: number;
4
+ rpcPort: number;
5
+ faucetPort: number;
6
6
  }
7
7
 
8
8
  export class PortManager {
9
- private readonly defaultRpcPort = 8899;
10
- private readonly defaultFaucetPort = 9900;
11
- private readonly portRangeStart = 8000;
12
- private readonly portRangeEnd = 9999;
13
-
14
- /**
15
- * Get the next available port pair (RPC + Faucet)
16
- */
17
- async getAvailablePorts(preferredRpcPort?: number): Promise<PortAllocation> {
18
- const usedPorts = this.getUsedPorts();
19
-
20
- // If preferred port is specified and available, use it
21
- if (preferredRpcPort && !this.isPortUsed(preferredRpcPort, usedPorts)) {
22
- const faucetPort = this.findAvailableFaucetPort(
23
- preferredRpcPort,
24
- usedPorts
25
- );
26
- if (faucetPort) {
27
- return { rpcPort: preferredRpcPort, faucetPort };
28
- }
29
- }
30
-
31
- // Otherwise, find the next available ports
32
- return this.findNextAvailablePorts(usedPorts);
33
- }
34
-
35
- /**
36
- * Check if a specific port is available
37
- */
38
- async isPortAvailable(port: number): Promise<boolean> {
39
- const usedPorts = this.getUsedPorts();
40
- return (
41
- !this.isPortUsed(port, usedPorts) &&
42
- (await this.checkPortActuallyFree(port))
43
- );
44
- }
45
-
46
- /**
47
- * Get all currently used ports from running validators
48
- */
49
- private getUsedPorts(): Set<number> {
50
- const validators = processRegistry.getRunning();
51
- const usedPorts = new Set<number>();
52
-
53
- validators.forEach((validator) => {
54
- usedPorts.add(validator.rpcPort);
55
- usedPorts.add(validator.faucetPort);
56
- });
57
-
58
- return usedPorts;
59
- }
60
-
61
- /**
62
- * Check if a port is in the used ports set
63
- */
64
- private isPortUsed(port: number, usedPorts: Set<number>): boolean {
65
- return usedPorts.has(port);
66
- }
67
-
68
- /**
69
- * Find an available faucet port for a given RPC port
70
- */
71
- private findAvailableFaucetPort(
72
- rpcPort: number,
73
- usedPorts: Set<number>
74
- ): number | null {
75
- // Try default offset first (faucet = rpc + 1001)
76
- let faucetPort = rpcPort + 1001;
77
- if (
78
- !this.isPortUsed(faucetPort, usedPorts) &&
79
- this.isPortInRange(faucetPort)
80
- ) {
81
- return faucetPort;
82
- }
83
-
84
- // Try other offsets
85
- const offsets = [1000, 1002, 1003, 1004, 1005, 999, 998, 997];
86
- for (const offset of offsets) {
87
- faucetPort = rpcPort + offset;
88
- if (
89
- !this.isPortUsed(faucetPort, usedPorts) &&
90
- this.isPortInRange(faucetPort)
91
- ) {
92
- return faucetPort;
93
- }
94
- }
95
-
96
- // Search in the entire range
97
- for (let port = this.portRangeStart; port <= this.portRangeEnd; port++) {
98
- if (!this.isPortUsed(port, usedPorts)) {
99
- return port;
100
- }
101
- }
102
-
103
- return null;
104
- }
105
-
106
- /**
107
- * Find the next available port pair
108
- */
109
- private findNextAvailablePorts(usedPorts: Set<number>): PortAllocation {
110
- // Start from default ports if available
111
- if (!this.isPortUsed(this.defaultRpcPort, usedPorts)) {
112
- const faucetPort = this.findAvailableFaucetPort(
113
- this.defaultRpcPort,
114
- usedPorts
115
- );
116
- if (faucetPort) {
117
- return { rpcPort: this.defaultRpcPort, faucetPort };
118
- }
119
- }
120
-
121
- // Search for available RPC port
122
- for (
123
- let rpcPort = this.portRangeStart;
124
- rpcPort <= this.portRangeEnd;
125
- rpcPort++
126
- ) {
127
- if (!this.isPortUsed(rpcPort, usedPorts)) {
128
- const faucetPort = this.findAvailableFaucetPort(rpcPort, usedPorts);
129
- if (faucetPort) {
130
- return { rpcPort, faucetPort };
131
- }
132
- }
133
- }
134
-
135
- throw new Error("No available port pairs found in the specified range");
136
- }
137
-
138
- /**
139
- * Check if port is within allowed range
140
- */
141
- private isPortInRange(port: number): boolean {
142
- return port >= this.portRangeStart && port <= this.portRangeEnd;
143
- }
144
-
145
- /**
146
- * Actually check if a port is free by attempting to bind to it
147
- */
148
- private async checkPortActuallyFree(port: number): Promise<boolean> {
149
- return new Promise((resolve) => {
150
- const net = require("net");
151
- const server = net.createServer();
152
-
153
- server.listen(port, (err: any) => {
154
- if (err) {
155
- resolve(false);
156
- } else {
157
- server.once("close", () => resolve(true));
158
- server.close();
159
- }
160
- });
161
-
162
- server.on("error", () => resolve(false));
163
- });
164
- }
165
-
166
- /**
167
- * Get recommended ports for a configuration
168
- */
169
- async getRecommendedPorts(config: {
170
- localnet: { port: number; faucetPort: number };
171
- }): Promise<PortAllocation> {
172
- return this.getAvailablePorts(config.localnet.port);
173
- }
9
+ private readonly defaultRpcPort = 8899;
10
+ private readonly portRangeStart = 8000;
11
+ private readonly portRangeEnd = 9999;
12
+
13
+ /**
14
+ * Get the next available port pair (RPC + Faucet)
15
+ */
16
+ async getAvailablePorts(preferredRpcPort?: number): Promise<PortAllocation> {
17
+ const usedPorts = this.getUsedPorts();
18
+
19
+ // If preferred port is specified and available, use it
20
+ if (preferredRpcPort && !this.isPortUsed(preferredRpcPort, usedPorts)) {
21
+ const faucetPort = this.findAvailableFaucetPort(
22
+ preferredRpcPort,
23
+ usedPorts,
24
+ );
25
+ if (faucetPort) {
26
+ return { rpcPort: preferredRpcPort, faucetPort };
27
+ }
28
+ }
29
+
30
+ // Otherwise, find the next available ports
31
+ return this.findNextAvailablePorts(usedPorts);
32
+ }
33
+
34
+ /**
35
+ * Check if a specific port is available
36
+ */
37
+ async isPortAvailable(port: number): Promise<boolean> {
38
+ const usedPorts = this.getUsedPorts();
39
+ return (
40
+ !this.isPortUsed(port, usedPorts) &&
41
+ (await this.checkPortActuallyFree(port))
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Get all currently used ports from running validators
47
+ */
48
+ private getUsedPorts(): Set<number> {
49
+ const validators = processRegistry.getRunning();
50
+ const usedPorts = new Set<number>();
51
+
52
+ validators.forEach((validator) => {
53
+ usedPorts.add(validator.rpcPort);
54
+ usedPorts.add(validator.faucetPort);
55
+ });
56
+
57
+ return usedPorts;
58
+ }
59
+
60
+ /**
61
+ * Check if a port is in the used ports set
62
+ */
63
+ private isPortUsed(port: number, usedPorts: Set<number>): boolean {
64
+ return usedPorts.has(port);
65
+ }
66
+
67
+ /**
68
+ * Find an available faucet port for a given RPC port
69
+ */
70
+ private findAvailableFaucetPort(
71
+ rpcPort: number,
72
+ usedPorts: Set<number>,
73
+ ): number | null {
74
+ // Try default offset first (faucet = rpc + 1001)
75
+ let faucetPort = rpcPort + 1001;
76
+ if (
77
+ !this.isPortUsed(faucetPort, usedPorts) &&
78
+ this.isPortInRange(faucetPort)
79
+ ) {
80
+ return faucetPort;
81
+ }
82
+
83
+ // Try other offsets
84
+ const offsets = [1000, 1002, 1003, 1004, 1005, 999, 998, 997];
85
+ for (const offset of offsets) {
86
+ faucetPort = rpcPort + offset;
87
+ if (
88
+ !this.isPortUsed(faucetPort, usedPorts) &&
89
+ this.isPortInRange(faucetPort)
90
+ ) {
91
+ return faucetPort;
92
+ }
93
+ }
94
+
95
+ // Search in the entire range
96
+ for (let port = this.portRangeStart; port <= this.portRangeEnd; port++) {
97
+ if (!this.isPortUsed(port, usedPorts)) {
98
+ return port;
99
+ }
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ /**
106
+ * Find the next available port pair
107
+ */
108
+ private findNextAvailablePorts(usedPorts: Set<number>): PortAllocation {
109
+ // Start from default ports if available
110
+ if (!this.isPortUsed(this.defaultRpcPort, usedPorts)) {
111
+ const faucetPort = this.findAvailableFaucetPort(
112
+ this.defaultRpcPort,
113
+ usedPorts,
114
+ );
115
+ if (faucetPort) {
116
+ return { rpcPort: this.defaultRpcPort, faucetPort };
117
+ }
118
+ }
119
+
120
+ // Search for available RPC port
121
+ for (
122
+ let rpcPort = this.portRangeStart;
123
+ rpcPort <= this.portRangeEnd;
124
+ rpcPort++
125
+ ) {
126
+ if (!this.isPortUsed(rpcPort, usedPorts)) {
127
+ const faucetPort = this.findAvailableFaucetPort(rpcPort, usedPorts);
128
+ if (faucetPort) {
129
+ return { rpcPort, faucetPort };
130
+ }
131
+ }
132
+ }
133
+
134
+ throw new Error("No available port pairs found in the specified range");
135
+ }
136
+
137
+ /**
138
+ * Check if port is within allowed range
139
+ */
140
+ private isPortInRange(port: number): boolean {
141
+ return port >= this.portRangeStart && port <= this.portRangeEnd;
142
+ }
143
+
144
+ /**
145
+ * Actually check if a port is free by attempting to bind to it
146
+ */
147
+ private async checkPortActuallyFree(port: number): Promise<boolean> {
148
+ return new Promise((resolve) => {
149
+ const net = require("node:net");
150
+ const server = net.createServer();
151
+
152
+ server.once("listening", () => {
153
+ server.once("close", () => resolve(true));
154
+ server.close();
155
+ });
156
+
157
+ server.once("error", () => resolve(false));
158
+
159
+ server.listen(port);
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Get recommended ports for a configuration
165
+ */
166
+ async getRecommendedPorts(config: {
167
+ localnet: { port: number; faucetPort: number };
168
+ }): Promise<PortAllocation> {
169
+ return this.getAvailablePorts(config.localnet.port);
170
+ }
174
171
  }
175
172
 
176
173
  // Singleton instance
@@ -1,153 +1,152 @@
1
- import { writeFileSync, readFileSync, existsSync } from "fs";
2
- import { join } from "path";
3
- import { homedir } from "os";
4
- import type { Config } from "../types/config.js";
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
5
4
 
6
5
  export interface RunningValidator {
7
- id: string;
8
- name: string;
9
- pid: number;
10
- rpcPort: number;
11
- faucetPort: number;
12
- rpcUrl: string;
13
- faucetUrl: string;
14
- configPath: string;
15
- startTime: Date;
16
- status: "running" | "stopped" | "error";
17
- apiServerPort?: number;
18
- apiServerUrl?: string;
19
- apiServerPid?: number;
6
+ id: string;
7
+ name: string;
8
+ pid: number;
9
+ rpcPort: number;
10
+ faucetPort: number;
11
+ rpcUrl: string;
12
+ faucetUrl: string;
13
+ configPath: string;
14
+ startTime: Date;
15
+ status: "running" | "stopped" | "error";
16
+ apiServerPort?: number;
17
+ apiServerUrl?: string;
18
+ apiServerPid?: number;
20
19
  }
21
20
 
22
21
  export class ProcessRegistry {
23
- private registryPath: string;
24
-
25
- constructor() {
26
- // Store registry in user's home directory
27
- this.registryPath = join(homedir(), ".solforge", "running-validators.json");
28
- }
29
-
30
- /**
31
- * Get all running validators
32
- */
33
- getRunning(): RunningValidator[] {
34
- if (!existsSync(this.registryPath)) {
35
- return [];
36
- }
37
-
38
- try {
39
- const content = readFileSync(this.registryPath, "utf-8");
40
- const validators = JSON.parse(content) as RunningValidator[];
41
-
42
- // Convert startTime strings back to Date objects
43
- return validators.map((v) => ({
44
- ...v,
45
- startTime: new Date(v.startTime),
46
- }));
47
- } catch {
48
- return [];
49
- }
50
- }
51
-
52
- /**
53
- * Register a new running validator
54
- */
55
- register(validator: RunningValidator): void {
56
- const validators = this.getRunning();
57
-
58
- // Remove any existing entry with the same ID
59
- const updated = validators.filter((v) => v.id !== validator.id);
60
- updated.push(validator);
61
-
62
- this.save(updated);
63
- }
64
-
65
- /**
66
- * Unregister a validator
67
- */
68
- unregister(id: string): void {
69
- const validators = this.getRunning();
70
- const updated = validators.filter((v) => v.id !== id);
71
- this.save(updated);
72
- }
73
-
74
- /**
75
- * Update validator status
76
- */
77
- updateStatus(id: string, status: RunningValidator["status"]): void {
78
- const validators = this.getRunning();
79
- const validator = validators.find((v) => v.id === id);
80
-
81
- if (validator) {
82
- validator.status = status;
83
- this.save(validators);
84
- }
85
- }
86
-
87
- /**
88
- * Get validator by ID
89
- */
90
- getById(id: string): RunningValidator | undefined {
91
- return this.getRunning().find((v) => v.id === id);
92
- }
93
-
94
- /**
95
- * Get validator by PID
96
- */
97
- getByPid(pid: number): RunningValidator | undefined {
98
- return this.getRunning().find((v) => v.pid === pid);
99
- }
100
-
101
- /**
102
- * Get validator by port
103
- */
104
- getByPort(port: number): RunningValidator | undefined {
105
- return this.getRunning().find(
106
- (v) => v.rpcPort === port || v.faucetPort === port
107
- );
108
- }
109
-
110
- /**
111
- * Check if a process is actually running
112
- */
113
- async isProcessRunning(pid: number): Promise<boolean> {
114
- try {
115
- // Send signal 0 to check if process exists
116
- process.kill(pid, 0);
117
- return true;
118
- } catch {
119
- return false;
120
- }
121
- }
122
-
123
- /**
124
- * Clean up dead processes from registry
125
- */
126
- async cleanup(): Promise<void> {
127
- const validators = this.getRunning();
128
- const active: RunningValidator[] = [];
129
-
130
- for (const validator of validators) {
131
- if (await this.isProcessRunning(validator.pid)) {
132
- active.push(validator);
133
- }
134
- }
135
-
136
- this.save(active);
137
- }
138
-
139
- /**
140
- * Save validators to registry file
141
- */
142
- private save(validators: RunningValidator[]): void {
143
- // Ensure directory exists
144
- const dir = join(homedir(), ".solforge");
145
- if (!existsSync(dir)) {
146
- require("fs").mkdirSync(dir, { recursive: true });
147
- }
148
-
149
- writeFileSync(this.registryPath, JSON.stringify(validators, null, 2));
150
- }
22
+ private registryPath: string;
23
+
24
+ constructor() {
25
+ // Store registry in user's home directory
26
+ this.registryPath = join(homedir(), ".solforge", "running-validators.json");
27
+ }
28
+
29
+ /**
30
+ * Get all running validators
31
+ */
32
+ getRunning(): RunningValidator[] {
33
+ if (!existsSync(this.registryPath)) {
34
+ return [];
35
+ }
36
+
37
+ try {
38
+ const content = readFileSync(this.registryPath, "utf-8");
39
+ const validators = JSON.parse(content) as RunningValidator[];
40
+
41
+ // Convert startTime strings back to Date objects
42
+ return validators.map((v) => ({
43
+ ...v,
44
+ startTime: new Date(v.startTime),
45
+ }));
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Register a new running validator
53
+ */
54
+ register(validator: RunningValidator): void {
55
+ const validators = this.getRunning();
56
+
57
+ // Remove any existing entry with the same ID
58
+ const updated = validators.filter((v) => v.id !== validator.id);
59
+ updated.push(validator);
60
+
61
+ this.save(updated);
62
+ }
63
+
64
+ /**
65
+ * Unregister a validator
66
+ */
67
+ unregister(id: string): void {
68
+ const validators = this.getRunning();
69
+ const updated = validators.filter((v) => v.id !== id);
70
+ this.save(updated);
71
+ }
72
+
73
+ /**
74
+ * Update validator status
75
+ */
76
+ updateStatus(id: string, status: RunningValidator["status"]): void {
77
+ const validators = this.getRunning();
78
+ const validator = validators.find((v) => v.id === id);
79
+
80
+ if (validator) {
81
+ validator.status = status;
82
+ this.save(validators);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get validator by ID
88
+ */
89
+ getById(id: string): RunningValidator | undefined {
90
+ return this.getRunning().find((v) => v.id === id);
91
+ }
92
+
93
+ /**
94
+ * Get validator by PID
95
+ */
96
+ getByPid(pid: number): RunningValidator | undefined {
97
+ return this.getRunning().find((v) => v.pid === pid);
98
+ }
99
+
100
+ /**
101
+ * Get validator by port
102
+ */
103
+ getByPort(port: number): RunningValidator | undefined {
104
+ return this.getRunning().find(
105
+ (v) => v.rpcPort === port || v.faucetPort === port,
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Check if a process is actually running
111
+ */
112
+ async isProcessRunning(pid: number): Promise<boolean> {
113
+ try {
114
+ // Send signal 0 to check if process exists
115
+ process.kill(pid, 0);
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Clean up dead processes from registry
124
+ */
125
+ async cleanup(): Promise<void> {
126
+ const validators = this.getRunning();
127
+ const active: RunningValidator[] = [];
128
+
129
+ for (const validator of validators) {
130
+ if (await this.isProcessRunning(validator.pid)) {
131
+ active.push(validator);
132
+ }
133
+ }
134
+
135
+ this.save(active);
136
+ }
137
+
138
+ /**
139
+ * Save validators to registry file
140
+ */
141
+ private save(validators: RunningValidator[]): void {
142
+ // Ensure directory exists
143
+ const dir = join(homedir(), ".solforge");
144
+ if (!existsSync(dir)) {
145
+ require("node:fs").mkdirSync(dir, { recursive: true });
146
+ }
147
+
148
+ writeFileSync(this.registryPath, JSON.stringify(validators, null, 2));
149
+ }
151
150
  }
152
151
 
153
152
  // Singleton instance