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.
- package/README.md +471 -79
- package/cli.cjs +106 -78
- package/package.json +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.cjs +69 -61
- package/server/lib/base58.ts +1 -1
- package/server/methods/account/get-account-info.ts +3 -7
- package/server/methods/account/get-balance.ts +3 -7
- package/server/methods/account/get-multiple-accounts.ts +2 -1
- package/server/methods/account/get-parsed-account-info.ts +3 -7
- package/server/methods/account/parsers/index.ts +2 -2
- package/server/methods/account/parsers/loader-upgradeable.ts +14 -1
- package/server/methods/account/parsers/spl-token.ts +29 -10
- package/server/methods/account/request-airdrop.ts +44 -31
- package/server/methods/block/get-block.ts +3 -7
- package/server/methods/block/get-blocks-with-limit.ts +3 -7
- package/server/methods/block/is-blockhash-valid.ts +3 -7
- package/server/methods/get-address-lookup-table.ts +3 -7
- package/server/methods/program/get-program-accounts.ts +9 -9
- package/server/methods/program/get-token-account-balance.ts +3 -7
- package/server/methods/program/get-token-accounts-by-delegate.ts +4 -3
- package/server/methods/program/get-token-accounts-by-owner.ts +61 -35
- package/server/methods/program/get-token-largest-accounts.ts +3 -2
- package/server/methods/program/get-token-supply.ts +3 -2
- package/server/methods/solforge/index.ts +9 -6
- package/server/methods/transaction/get-parsed-transaction.ts +3 -7
- package/server/methods/transaction/get-signature-statuses.ts +14 -7
- package/server/methods/transaction/get-signatures-for-address.ts +3 -7
- package/server/methods/transaction/get-transaction.ts +167 -81
- package/server/methods/transaction/send-transaction.ts +29 -16
- package/server/methods/transaction/simulate-transaction.ts +3 -2
- package/server/rpc-server.ts +47 -34
- package/server/types.ts +9 -6
- package/server/ws-server.ts +15 -8
- package/src/api-server-entry.ts +91 -91
- package/src/cli/commands/airdrop.ts +2 -2
- package/src/cli/commands/config.ts +2 -2
- package/src/cli/commands/mint.ts +3 -3
- package/src/cli/commands/program-clone.ts +9 -11
- package/src/cli/commands/program-load.ts +3 -3
- package/src/cli/commands/rpc-start.ts +8 -5
- package/src/cli/commands/token-adopt-authority.ts +1 -1
- package/src/cli/commands/token-clone.ts +5 -6
- package/src/cli/commands/token-create.ts +5 -5
- package/src/cli/main.ts +38 -37
- package/src/cli/run-solforge.ts +20 -6
- package/src/cli/setup-wizard.ts +8 -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 +247 -248
- package/src/commands/start.ts +837 -833
- package/src/commands/status.ts +80 -80
- package/src/commands/stop.ts +381 -382
- package/src/config/index.ts +33 -17
- package/src/config/manager.ts +150 -150
- package/src/db/index.ts +2 -2
- package/src/db/tx-store.ts +12 -8
- package/src/gui/public/app.css +1556 -1
- package/src/gui/public/build/main.css +1569 -1
- package/src/gui/server.ts +21 -22
- package/src/gui/src/api.ts +1 -1
- package/src/gui/src/app.tsx +96 -45
- package/src/gui/src/components/airdrop-mint-form.tsx +49 -19
- package/src/gui/src/components/clone-program-modal.tsx +31 -12
- package/src/gui/src/components/clone-token-modal.tsx +32 -13
- package/src/gui/src/components/modal.tsx +18 -11
- package/src/gui/src/components/programs-panel.tsx +27 -15
- package/src/gui/src/components/status-panel.tsx +32 -18
- package/src/gui/src/components/tokens-panel.tsx +25 -19
- package/src/gui/src/index.css +491 -463
- package/src/index.ts +177 -149
- package/src/rpc/start.ts +1 -1
- package/src/services/api-server.ts +494 -475
- package/src/services/port-manager.ts +164 -167
- package/src/services/process-registry.ts +144 -145
- package/src/services/program-cloner.ts +312 -312
- package/src/services/token-cloner.ts +799 -797
- package/src/services/validator.ts +288 -290
- package/src/types/config.ts +72 -72
- package/src/utils/shell.ts +75 -75
- package/src/utils/token-loader.ts +78 -78
package/src/config/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export async function writeDefaultConfig(opts: { force?: boolean } = {}) {
|
|
|
68
68
|
mkdirSync(dir, { recursive: true });
|
|
69
69
|
} catch {}
|
|
70
70
|
}
|
|
71
|
-
writeFileSync(p, JSON.stringify(defaultConfig, null, 2)
|
|
71
|
+
writeFileSync(p, `${JSON.stringify(defaultConfig, null, 2)}\n`);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export async function writeConfig(
|
|
@@ -81,50 +81,66 @@ export async function writeConfig(
|
|
|
81
81
|
mkdirSync(dir, { recursive: true });
|
|
82
82
|
} catch {}
|
|
83
83
|
}
|
|
84
|
-
await Bun.write(path, JSON.stringify(config, null, 2)
|
|
84
|
+
await Bun.write(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export function getConfigValue(
|
|
87
|
+
export function getConfigValue(
|
|
88
|
+
cfg: Record<string, unknown>,
|
|
89
|
+
path?: string,
|
|
90
|
+
): unknown {
|
|
88
91
|
if (!path) return cfg;
|
|
89
|
-
|
|
92
|
+
let cur: unknown = cfg;
|
|
93
|
+
for (const k of path.split(".")) {
|
|
94
|
+
if (
|
|
95
|
+
cur &&
|
|
96
|
+
typeof cur === "object" &&
|
|
97
|
+
k in (cur as Record<string, unknown>)
|
|
98
|
+
) {
|
|
99
|
+
cur = (cur as Record<string, unknown>)[k];
|
|
100
|
+
} else {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return cur;
|
|
90
105
|
}
|
|
91
106
|
|
|
92
|
-
export function setConfigValue<T extends Record<string,
|
|
107
|
+
export function setConfigValue<T extends Record<string, unknown>>(
|
|
93
108
|
cfg: T,
|
|
94
109
|
path: string,
|
|
95
|
-
value:
|
|
110
|
+
value: unknown,
|
|
96
111
|
): T {
|
|
97
112
|
const parts = path.split(".");
|
|
98
|
-
let node:
|
|
113
|
+
let node: Record<string, unknown> = cfg;
|
|
99
114
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
100
115
|
const k = parts[i];
|
|
101
116
|
if (!node[k] || typeof node[k] !== "object") node[k] = {};
|
|
102
|
-
node = node[k]
|
|
117
|
+
node = node[k] as Record<string, unknown>;
|
|
103
118
|
}
|
|
104
119
|
node[parts[parts.length - 1]] = coerceValue(value);
|
|
105
120
|
return cfg;
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
function coerceValue(v:
|
|
123
|
+
function coerceValue(v: unknown): unknown {
|
|
109
124
|
if (v === "true") return true;
|
|
110
125
|
if (v === "false") return false;
|
|
111
|
-
if (v !== "" && !isNaN(Number(v)))
|
|
126
|
+
if (typeof v === "string" && v !== "" && !Number.isNaN(Number(v)))
|
|
127
|
+
return Number(v);
|
|
112
128
|
try {
|
|
113
|
-
return JSON.parse(v);
|
|
129
|
+
return typeof v === "string" ? JSON.parse(v) : v;
|
|
114
130
|
} catch {
|
|
115
131
|
return v;
|
|
116
132
|
}
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
function deepMerge<T>(a: T, b: Partial<T>): T {
|
|
120
|
-
if (Array.isArray(a) || Array.isArray(b)) return (b
|
|
136
|
+
if (Array.isArray(a) || Array.isArray(b)) return (b ?? a) as unknown as T;
|
|
121
137
|
if (typeof a === "object" && typeof b === "object" && a && b) {
|
|
122
|
-
const out:
|
|
138
|
+
const out: Record<string, unknown> = { ...(a as Record<string, unknown>) };
|
|
123
139
|
for (const [k, v] of Object.entries(b)) {
|
|
124
|
-
const ak = (a as
|
|
125
|
-
out[k] = deepMerge(ak, v as
|
|
140
|
+
const ak = (a as Record<string, unknown>)[k];
|
|
141
|
+
out[k] = deepMerge(ak as unknown, v as unknown) as unknown;
|
|
126
142
|
}
|
|
127
|
-
return out;
|
|
143
|
+
return out as unknown as T;
|
|
128
144
|
}
|
|
129
|
-
return (b
|
|
145
|
+
return (b ?? a) as unknown as T;
|
|
130
146
|
}
|
package/src/config/manager.ts
CHANGED
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync
|
|
2
|
-
import {
|
|
3
|
-
import { ConfigSchema } from "../types/config.js";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
4
3
|
import type { Config, ValidationResult } from "../types/config.js";
|
|
4
|
+
import { ConfigSchema } from "../types/config.js";
|
|
5
5
|
|
|
6
6
|
export class ConfigManager {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
7
|
+
private config: Config | null = null;
|
|
8
|
+
private configPath: string | null = null;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load configuration from a file path
|
|
12
|
+
*/
|
|
13
|
+
async load(configPath: string): Promise<Config> {
|
|
14
|
+
try {
|
|
15
|
+
const fullPath = resolve(configPath);
|
|
16
|
+
|
|
17
|
+
if (!existsSync(fullPath)) {
|
|
18
|
+
throw new Error(`Configuration file not found: ${fullPath}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const configContent = readFileSync(fullPath, "utf-8");
|
|
22
|
+
const rawConfig = JSON.parse(configContent);
|
|
23
|
+
|
|
24
|
+
// Validate and parse with Zod
|
|
25
|
+
const result = ConfigSchema.safeParse(rawConfig);
|
|
26
|
+
|
|
27
|
+
if (!result.success) {
|
|
28
|
+
const errors = result.error.issues.map((issue) => ({
|
|
29
|
+
path: issue.path.join("."),
|
|
30
|
+
message: issue.message,
|
|
31
|
+
}));
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Configuration validation failed:\n${errors
|
|
34
|
+
.map((e) => ` - ${e.path}: ${e.message}`)
|
|
35
|
+
.join("\n")}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.config = result.data;
|
|
40
|
+
this.configPath = fullPath;
|
|
41
|
+
|
|
42
|
+
return this.config;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof SyntaxError) {
|
|
45
|
+
throw new Error(`Invalid JSON in configuration file: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Save current configuration to file
|
|
53
|
+
*/
|
|
54
|
+
async save(configPath?: string): Promise<void> {
|
|
55
|
+
if (!this.config) {
|
|
56
|
+
throw new Error("No configuration loaded");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const targetPath = configPath || this.configPath;
|
|
60
|
+
if (!targetPath) {
|
|
61
|
+
throw new Error("No configuration path specified");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const configContent = JSON.stringify(this.config, null, 2);
|
|
66
|
+
writeFileSync(targetPath, configContent, "utf-8");
|
|
67
|
+
this.configPath = targetPath;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Failed to save configuration: ${
|
|
71
|
+
error instanceof Error ? error.message : String(error)
|
|
72
|
+
}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate a configuration object
|
|
79
|
+
*/
|
|
80
|
+
validate(config: unknown): ValidationResult {
|
|
81
|
+
const result = ConfigSchema.safeParse(config);
|
|
82
|
+
|
|
83
|
+
if (result.success) {
|
|
84
|
+
return { valid: true, errors: [] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const errors = result.error.issues.map((issue) => ({
|
|
88
|
+
path: issue.path.join("."),
|
|
89
|
+
message: issue.message,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
return { valid: false, errors };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a default configuration
|
|
97
|
+
*/
|
|
98
|
+
createDefault(): Config {
|
|
99
|
+
const defaultConfig = ConfigSchema.parse({});
|
|
100
|
+
this.config = defaultConfig;
|
|
101
|
+
return defaultConfig;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get current configuration
|
|
106
|
+
*/
|
|
107
|
+
getConfig(): Config {
|
|
108
|
+
if (!this.config) {
|
|
109
|
+
throw new Error("No configuration loaded. Call load() first.");
|
|
110
|
+
}
|
|
111
|
+
return this.config;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Update configuration
|
|
116
|
+
*/
|
|
117
|
+
updateConfig(updates: Partial<Config>): Config {
|
|
118
|
+
if (!this.config) {
|
|
119
|
+
throw new Error("No configuration loaded. Call load() first.");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const updated = { ...this.config, ...updates };
|
|
123
|
+
const result = ConfigSchema.safeParse(updated);
|
|
124
|
+
|
|
125
|
+
if (!result.success) {
|
|
126
|
+
const errors = result.error.issues.map((issue) => ({
|
|
127
|
+
path: issue.path.join("."),
|
|
128
|
+
message: issue.message,
|
|
129
|
+
}));
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Configuration update validation failed:\n${errors
|
|
132
|
+
.map((e) => ` - ${e.path}: ${e.message}`)
|
|
133
|
+
.join("\n")}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.config = result.data;
|
|
138
|
+
return this.config;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get configuration file path
|
|
143
|
+
*/
|
|
144
|
+
getConfigPath(): string | null {
|
|
145
|
+
return this.configPath;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if configuration is loaded
|
|
150
|
+
*/
|
|
151
|
+
isLoaded(): boolean {
|
|
152
|
+
return this.config !== null;
|
|
153
|
+
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// Singleton instance
|
package/src/db/index.ts
CHANGED
|
@@ -27,10 +27,10 @@ if (!PERSIST && DB_PATH !== ":memory:") {
|
|
|
27
27
|
if (existsSync(DB_PATH)) unlinkSync(DB_PATH);
|
|
28
28
|
} catch {}
|
|
29
29
|
try {
|
|
30
|
-
if (existsSync(DB_PATH
|
|
30
|
+
if (existsSync(`${DB_PATH}-wal`)) unlinkSync(`${DB_PATH}-wal`);
|
|
31
31
|
} catch {}
|
|
32
32
|
try {
|
|
33
|
-
if (existsSync(DB_PATH
|
|
33
|
+
if (existsSync(`${DB_PATH}-shm`)) unlinkSync(`${DB_PATH}-shm`);
|
|
34
34
|
} catch {}
|
|
35
35
|
}
|
|
36
36
|
|
package/src/db/tx-store.ts
CHANGED
|
@@ -23,8 +23,8 @@ export type InsertTxBundle = {
|
|
|
23
23
|
writable: boolean;
|
|
24
24
|
programIdIndex?: number;
|
|
25
25
|
}>;
|
|
26
|
-
preTokenBalances?:
|
|
27
|
-
postTokenBalances?:
|
|
26
|
+
preTokenBalances?: unknown[];
|
|
27
|
+
postTokenBalances?: unknown[];
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type AccountSnapshot = {
|
|
@@ -133,7 +133,7 @@ export class TxStore {
|
|
|
133
133
|
|
|
134
134
|
async getStatuses(signatures: string[]) {
|
|
135
135
|
if (!Array.isArray(signatures) || signatures.length === 0)
|
|
136
|
-
return new Map<string, { slot: number; err:
|
|
136
|
+
return new Map<string, { slot: number; err: unknown | null }>();
|
|
137
137
|
const results = await db
|
|
138
138
|
.select({
|
|
139
139
|
signature: transactions.signature,
|
|
@@ -142,7 +142,7 @@ export class TxStore {
|
|
|
142
142
|
})
|
|
143
143
|
.from(transactions)
|
|
144
144
|
.where(inArraySafe(transactions.signature, signatures));
|
|
145
|
-
const map = new Map<string, { slot: number; err:
|
|
145
|
+
const map = new Map<string, { slot: number; err: unknown | null }>();
|
|
146
146
|
for (const r of results)
|
|
147
147
|
map.set(r.signature, {
|
|
148
148
|
slot: Number(r.slot),
|
|
@@ -167,7 +167,7 @@ export class TxStore {
|
|
|
167
167
|
}
|
|
168
168
|
const limit = Math.min(Math.max(opts.limit ?? 1000, 1), 1000);
|
|
169
169
|
|
|
170
|
-
const whereClauses = [eq(addressSignatures.address, address)] as
|
|
170
|
+
const whereClauses = [eq(addressSignatures.address, address)] as unknown[];
|
|
171
171
|
if (typeof beforeSlot === "number")
|
|
172
172
|
whereClauses.push(lt(addressSignatures.slot, beforeSlot));
|
|
173
173
|
if (typeof untilSlot === "number")
|
|
@@ -216,7 +216,7 @@ export class TxStore {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function safeParse<T =
|
|
219
|
+
function safeParse<T = unknown>(s: string): T | null {
|
|
220
220
|
try {
|
|
221
221
|
return JSON.parse(s) as T;
|
|
222
222
|
} catch {
|
|
@@ -224,6 +224,10 @@ function safeParse<T = any>(s: string): T | null {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
function inArraySafe<T>(col:
|
|
228
|
-
return arr.length > 0
|
|
227
|
+
function inArraySafe<T>(col: unknown, arr: T[]) {
|
|
228
|
+
return arr.length > 0
|
|
229
|
+
? // biome-ignore lint/suspicious/noExplicitAny: Drizzle generic typing workaround
|
|
230
|
+
(inArray as unknown as (c: unknown, a: T[]) => any)(col, arr)
|
|
231
|
+
: // biome-ignore lint/suspicious/noExplicitAny: Force an always-false predicate without over-constraining types
|
|
232
|
+
eq(col as any, "__never__");
|
|
229
233
|
}
|