solforge 0.2.3 ā 0.2.5
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 +323 -364
- package/cli.cjs +126 -69
- package/package.json +1 -1
- package/scripts/install.sh +112 -0
- package/scripts/postinstall.cjs +66 -58
- package/server/methods/program/get-token-accounts-by-owner.ts +7 -2
- package/server/ws-server.ts +4 -1
- package/src/api-server-entry.ts +91 -91
- package/src/cli/commands/rpc-start.ts +4 -1
- package/src/cli/main.ts +39 -14
- package/src/cli/run-solforge.ts +20 -6
- package/src/commands/add-program.ts +324 -328
- package/src/commands/init.ts +106 -106
- package/src/commands/list.ts +125 -125
- package/src/commands/mint.ts +246 -246
- package/src/commands/start.ts +834 -831
- package/src/commands/status.ts +80 -80
- package/src/commands/stop.ts +381 -382
- package/src/config/manager.ts +149 -149
- package/src/gui/public/app.css +1556 -1
- package/src/gui/public/build/main.css +1569 -1
- package/src/gui/server.ts +20 -21
- package/src/gui/src/app.tsx +56 -37
- package/src/gui/src/components/airdrop-mint-form.tsx +17 -11
- package/src/gui/src/components/clone-program-modal.tsx +6 -6
- package/src/gui/src/components/clone-token-modal.tsx +7 -7
- package/src/gui/src/components/modal.tsx +13 -11
- package/src/gui/src/components/programs-panel.tsx +27 -15
- package/src/gui/src/components/status-panel.tsx +31 -17
- package/src/gui/src/components/tokens-panel.tsx +25 -19
- package/src/gui/src/index.css +491 -463
- package/src/index.ts +161 -146
- package/src/rpc/start.ts +1 -1
- package/src/services/api-server.ts +470 -473
- package/src/services/port-manager.ts +167 -167
- package/src/services/process-registry.ts +143 -143
- package/src/services/program-cloner.ts +312 -312
- package/src/services/token-cloner.ts +799 -797
- package/src/services/validator.ts +288 -288
- package/src/types/config.ts +71 -71
- package/src/utils/shell.ts +75 -75
- package/src/utils/token-loader.ts +77 -77
package/src/commands/init.ts
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import { writeFileSync, existsSync } from "fs";
|
|
2
|
-
import { resolve } from "path";
|
|
3
1
|
import chalk from "chalk";
|
|
2
|
+
import { existsSync, writeFileSync } from "fs";
|
|
4
3
|
import inquirer from "inquirer";
|
|
4
|
+
import { resolve } from "path";
|
|
5
5
|
import type { Config } from "../types/config.js";
|
|
6
6
|
|
|
7
7
|
const defaultConfig: Config = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
8
|
+
name: "my-localnet",
|
|
9
|
+
description: "Local Solana development environment",
|
|
10
|
+
tokens: [
|
|
11
|
+
{
|
|
12
|
+
symbol: "USDC",
|
|
13
|
+
mainnetMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
14
|
+
recipients: [],
|
|
15
|
+
mintAmount: 1000000,
|
|
16
|
+
cloneMetadata: true,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
programs: [],
|
|
20
|
+
localnet: {
|
|
21
|
+
airdropAmount: 100,
|
|
22
|
+
faucetAccounts: [],
|
|
23
|
+
port: 8899,
|
|
24
|
+
faucetPort: 9900,
|
|
25
|
+
reset: false,
|
|
26
|
+
logLevel: "info",
|
|
27
|
+
bindAddress: "127.0.0.1",
|
|
28
|
+
quiet: false,
|
|
29
|
+
rpc: "https://api.mainnet-beta.solana.com",
|
|
30
|
+
limitLedgerSize: 100000,
|
|
31
|
+
},
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
export async function initCommand(): Promise<void> {
|
|
35
|
-
|
|
35
|
+
const configPath = resolve(process.cwd(), "sf.config.json");
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
// Check if config already exists
|
|
38
|
+
if (existsSync(configPath)) {
|
|
39
|
+
const { overwrite } = await inquirer.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: "confirm",
|
|
42
|
+
name: "overwrite",
|
|
43
|
+
message: "sf.config.json already exists. Overwrite?",
|
|
44
|
+
default: false,
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
if (!overwrite) {
|
|
49
|
+
console.log(chalk.yellow("ā ļø Initialization cancelled"));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
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
|
-
|
|
54
|
+
// Gather basic configuration
|
|
55
|
+
const answers = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: "input",
|
|
58
|
+
name: "name",
|
|
59
|
+
message: "Project name:",
|
|
60
|
+
default: "my-localnet",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: "input",
|
|
64
|
+
name: "description",
|
|
65
|
+
message: "Description:",
|
|
66
|
+
default: "Local Solana development environment",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: "number",
|
|
70
|
+
name: "port",
|
|
71
|
+
message: "RPC port:",
|
|
72
|
+
default: 8899,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: "confirm",
|
|
76
|
+
name: "includeUsdc",
|
|
77
|
+
message: "Include USDC token?",
|
|
78
|
+
default: true,
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
// Build config
|
|
83
|
+
const config: Config = {
|
|
84
|
+
...defaultConfig,
|
|
85
|
+
name: answers.name,
|
|
86
|
+
description: answers.description,
|
|
87
|
+
localnet: {
|
|
88
|
+
...defaultConfig.localnet,
|
|
89
|
+
port: answers.port,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if (!answers.includeUsdc) {
|
|
94
|
+
config.tokens = [];
|
|
95
|
+
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
try {
|
|
98
|
+
// Write config file
|
|
99
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
console.log(chalk.green("ā
sf.config.json created successfully!"));
|
|
102
|
+
console.log(chalk.gray(`š Config saved to: ${configPath}`));
|
|
103
|
+
console.log();
|
|
104
|
+
console.log(chalk.blue("Next steps:"));
|
|
105
|
+
console.log(
|
|
106
|
+
chalk.gray("1. Edit sf.config.json to add your tokens and programs"),
|
|
107
|
+
);
|
|
108
|
+
console.log(chalk.gray("2. Run `solforge start` to launch your localnet"));
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(
|
|
111
|
+
chalk.yellow(
|
|
112
|
+
"š” Tip: Check configs/example.sf.config.json for more examples",
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(chalk.red("ā Failed to create sf.config.json"));
|
|
117
|
+
console.error(
|
|
118
|
+
chalk.red(error instanceof Error ? error.message : String(error)),
|
|
119
|
+
);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
122
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,136 +1,136 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { processRegistry } from "../services/process-registry.js";
|
|
3
2
|
import type { RunningValidator } from "../services/process-registry.js";
|
|
3
|
+
import { processRegistry } from "../services/process-registry.js";
|
|
4
4
|
|
|
5
5
|
export async function listCommand(): Promise<void> {
|
|
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
|
-
|
|
6
|
+
console.log(chalk.blue("š Listing running validators...\n"));
|
|
7
|
+
|
|
8
|
+
// Clean up dead processes first
|
|
9
|
+
await processRegistry.cleanup();
|
|
10
|
+
|
|
11
|
+
const validators = processRegistry.getRunning();
|
|
12
|
+
|
|
13
|
+
if (validators.length === 0) {
|
|
14
|
+
console.log(chalk.yellow("ā ļø No running validators found"));
|
|
15
|
+
console.log(chalk.gray("š” Use `solforge start` to start a validator"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Verify each validator is actually still running
|
|
20
|
+
const activeValidators: RunningValidator[] = [];
|
|
21
|
+
for (const validator of validators) {
|
|
22
|
+
const isRunning = await processRegistry.isProcessRunning(validator.pid);
|
|
23
|
+
if (isRunning) {
|
|
24
|
+
activeValidators.push({ ...validator, status: "running" });
|
|
25
|
+
} else {
|
|
26
|
+
processRegistry.updateStatus(validator.id, "stopped");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (activeValidators.length === 0) {
|
|
31
|
+
console.log(
|
|
32
|
+
chalk.yellow(
|
|
33
|
+
"ā ļø No active validators found (all processes have stopped)",
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
console.log(chalk.gray("š” Use `solforge start` to start a validator"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(
|
|
41
|
+
chalk.green(
|
|
42
|
+
`ā
Found ${activeValidators.length} running validator${
|
|
43
|
+
activeValidators.length > 1 ? "s" : ""
|
|
44
|
+
}\n`,
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Display validators in a table format
|
|
49
|
+
displayValidatorsTable(activeValidators);
|
|
50
|
+
|
|
51
|
+
console.log(
|
|
52
|
+
chalk.gray("\nš” Use `solforge stop <id>` to stop a specific validator"),
|
|
53
|
+
);
|
|
54
|
+
console.log(
|
|
55
|
+
chalk.gray("š” Use `solforge stop --all` to stop all validators"),
|
|
56
|
+
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function displayValidatorsTable(validators: RunningValidator[]): void {
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
60
|
+
// Calculate column widths
|
|
61
|
+
const maxIdWidth = Math.max(2, ...validators.map((v) => v.id.length));
|
|
62
|
+
const maxNameWidth = Math.max(4, ...validators.map((v) => v.name.length));
|
|
63
|
+
const maxPidWidth = Math.max(
|
|
64
|
+
3,
|
|
65
|
+
...validators.map((v) => v.pid.toString().length),
|
|
66
|
+
);
|
|
67
|
+
const maxPortWidth = 9; // "8899/9900" format
|
|
68
|
+
const maxUptimeWidth = 7;
|
|
69
|
+
|
|
70
|
+
// Header
|
|
71
|
+
const header =
|
|
72
|
+
chalk.bold("ID".padEnd(maxIdWidth)) +
|
|
73
|
+
" | " +
|
|
74
|
+
chalk.bold("Name".padEnd(maxNameWidth)) +
|
|
75
|
+
" | " +
|
|
76
|
+
chalk.bold("PID".padEnd(maxPidWidth)) +
|
|
77
|
+
" | " +
|
|
78
|
+
chalk.bold("RPC/Faucet".padEnd(maxPortWidth)) +
|
|
79
|
+
" | " +
|
|
80
|
+
chalk.bold("Uptime".padEnd(maxUptimeWidth)) +
|
|
81
|
+
" | " +
|
|
82
|
+
chalk.bold("Status");
|
|
83
|
+
|
|
84
|
+
console.log(header);
|
|
85
|
+
console.log("-".repeat(header.length - 20)); // Subtract ANSI codes length
|
|
86
|
+
|
|
87
|
+
// Rows
|
|
88
|
+
validators.forEach((validator) => {
|
|
89
|
+
const uptime = formatUptime(validator.startTime);
|
|
90
|
+
const ports = `${validator.rpcPort}/${validator.faucetPort}`;
|
|
91
|
+
const status =
|
|
92
|
+
validator.status === "running" ? chalk.green("ā") : chalk.red("ā");
|
|
93
|
+
|
|
94
|
+
const row =
|
|
95
|
+
validator.id.padEnd(maxIdWidth) +
|
|
96
|
+
" | " +
|
|
97
|
+
validator.name.padEnd(maxNameWidth) +
|
|
98
|
+
" | " +
|
|
99
|
+
validator.pid.toString().padEnd(maxPidWidth) +
|
|
100
|
+
" | " +
|
|
101
|
+
ports.padEnd(maxPortWidth) +
|
|
102
|
+
" | " +
|
|
103
|
+
uptime.padEnd(maxUptimeWidth) +
|
|
104
|
+
" | " +
|
|
105
|
+
status;
|
|
106
|
+
|
|
107
|
+
console.log(row);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log(); // Empty line
|
|
111
|
+
|
|
112
|
+
// URLs section
|
|
113
|
+
console.log(chalk.cyan("š Connection URLs:"));
|
|
114
|
+
validators.forEach((validator) => {
|
|
115
|
+
console.log(chalk.gray(` ${validator.name} (${validator.id}):`));
|
|
116
|
+
console.log(chalk.gray(` RPC: ${validator.rpcUrl}`));
|
|
117
|
+
console.log(chalk.gray(` Faucet: ${validator.faucetUrl}`));
|
|
118
|
+
});
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function formatUptime(startTime: Date): string {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const uptimeMs = now.getTime() - startTime.getTime();
|
|
124
|
+
const uptimeSeconds = Math.floor(uptimeMs / 1000);
|
|
125
|
+
|
|
126
|
+
if (uptimeSeconds < 60) {
|
|
127
|
+
return `${uptimeSeconds}s`;
|
|
128
|
+
} else if (uptimeSeconds < 3600) {
|
|
129
|
+
const minutes = Math.floor(uptimeSeconds / 60);
|
|
130
|
+
return `${minutes}m`;
|
|
131
|
+
} else {
|
|
132
|
+
const hours = Math.floor(uptimeSeconds / 3600);
|
|
133
|
+
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
134
|
+
return `${hours}h${minutes}m`;
|
|
135
|
+
}
|
|
136
136
|
}
|