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
@@ -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) + "\n");
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) + "\n");
84
+ await Bun.write(path, `${JSON.stringify(config, null, 2)}\n`);
85
85
  }
86
86
 
87
- export function getConfigValue(cfg: any, path?: string) {
87
+ export function getConfigValue(
88
+ cfg: Record<string, unknown>,
89
+ path?: string,
90
+ ): unknown {
88
91
  if (!path) return cfg;
89
- return path.split(".").reduce((o, k) => (o ? o[k] : undefined), cfg);
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, any>>(
107
+ export function setConfigValue<T extends Record<string, unknown>>(
93
108
  cfg: T,
94
109
  path: string,
95
- value: any,
110
+ value: unknown,
96
111
  ): T {
97
112
  const parts = path.split(".");
98
- let node: any = cfg;
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: any) {
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))) return 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 as any) ?? (a as any);
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: any = { ...a };
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 any)[k];
125
- out[k] = deepMerge(ak, v as any);
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 as any) ?? (a as any);
145
+ return (b ?? a) as unknown as T;
130
146
  }
@@ -1,156 +1,156 @@
1
- import { readFileSync, writeFileSync, existsSync } from "fs";
2
- import { join, resolve } from "path";
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
- 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: any): 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
- }
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 + "-wal")) unlinkSync(DB_PATH + "-wal");
30
+ if (existsSync(`${DB_PATH}-wal`)) unlinkSync(`${DB_PATH}-wal`);
31
31
  } catch {}
32
32
  try {
33
- if (existsSync(DB_PATH + "-shm")) unlinkSync(DB_PATH + "-shm");
33
+ if (existsSync(`${DB_PATH}-shm`)) unlinkSync(`${DB_PATH}-shm`);
34
34
  } catch {}
35
35
  }
36
36
 
@@ -23,8 +23,8 @@ export type InsertTxBundle = {
23
23
  writable: boolean;
24
24
  programIdIndex?: number;
25
25
  }>;
26
- preTokenBalances?: any[];
27
- postTokenBalances?: any[];
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: any | null }>();
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: any | null }>();
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 any[];
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 = any>(s: string): T | null {
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: any, arr: T[]) {
228
- return arr.length > 0 ? inArray(col, arr as any) : eq(col, "__never__");
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
  }