solforge 0.1.7 → 0.2.0

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 (195) hide show
  1. package/.agi/agi.sqlite +0 -0
  2. package/.claude/settings.local.json +9 -0
  3. package/.github/workflows/release-binaries.yml +133 -0
  4. package/.tmp/.787ebcdbf7b8fde8-00000000.hm +0 -0
  5. package/.tmp/.bffe6efebdf8aedc-00000000.hm +0 -0
  6. package/AGENTS.md +271 -0
  7. package/CLAUDE.md +106 -0
  8. package/PROJECT_STRUCTURE.md +124 -0
  9. package/README.md +367 -393
  10. package/SOLANA_KIT_GUIDE.md +251 -0
  11. package/SOLFORGE.md +119 -0
  12. package/biome.json +34 -0
  13. package/bun.lock +743 -0
  14. package/docs/bun-single-file-executable.md +585 -0
  15. package/docs/cli-plan.md +154 -0
  16. package/docs/data-indexing-plan.md +214 -0
  17. package/docs/gui-roadmap.md +202 -0
  18. package/drizzle/0000_friendly_millenium_guard.sql +53 -0
  19. package/drizzle/0001_stale_sentinels.sql +2 -0
  20. package/drizzle/meta/0000_snapshot.json +329 -0
  21. package/drizzle/meta/0001_snapshot.json +345 -0
  22. package/drizzle/meta/_journal.json +20 -0
  23. package/drizzle.config.ts +12 -0
  24. package/index.ts +21 -0
  25. package/mint.sh +47 -0
  26. package/package.json +45 -69
  27. package/postcss.config.js +6 -0
  28. package/rpc-server.ts.backup +519 -0
  29. package/server/index.ts +5 -0
  30. package/server/lib/base58.ts +33 -0
  31. package/server/lib/faucet.ts +110 -0
  32. package/server/lib/spl-token.ts +57 -0
  33. package/server/methods/TEMPLATE.md +117 -0
  34. package/server/methods/account/get-account-info.ts +90 -0
  35. package/server/methods/account/get-balance.ts +27 -0
  36. package/server/methods/account/get-multiple-accounts.ts +83 -0
  37. package/server/methods/account/get-parsed-account-info.ts +21 -0
  38. package/server/methods/account/index.ts +12 -0
  39. package/server/methods/account/parsers/index.ts +52 -0
  40. package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
  41. package/server/methods/account/parsers/spl-token.ts +237 -0
  42. package/server/methods/account/parsers/system.ts +4 -0
  43. package/server/methods/account/request-airdrop.ts +219 -0
  44. package/server/methods/admin/adopt-mint-authority.ts +94 -0
  45. package/server/methods/admin/clone-program-accounts.ts +55 -0
  46. package/server/methods/admin/clone-program.ts +152 -0
  47. package/server/methods/admin/clone-token-accounts.ts +117 -0
  48. package/server/methods/admin/clone-token-mint.ts +82 -0
  49. package/server/methods/admin/create-mint.ts +114 -0
  50. package/server/methods/admin/create-token-account.ts +137 -0
  51. package/server/methods/admin/helpers.ts +70 -0
  52. package/server/methods/admin/index.ts +10 -0
  53. package/server/methods/admin/list-mints.ts +21 -0
  54. package/server/methods/admin/load-program.ts +52 -0
  55. package/server/methods/admin/mint-to.ts +278 -0
  56. package/server/methods/block/get-block-height.ts +5 -0
  57. package/server/methods/block/get-block.ts +35 -0
  58. package/server/methods/block/get-blocks-with-limit.ts +23 -0
  59. package/server/methods/block/get-latest-blockhash.ts +12 -0
  60. package/server/methods/block/get-slot.ts +5 -0
  61. package/server/methods/block/index.ts +6 -0
  62. package/server/methods/block/is-blockhash-valid.ts +23 -0
  63. package/server/methods/epoch/get-cluster-nodes.ts +17 -0
  64. package/server/methods/epoch/get-epoch-info.ts +16 -0
  65. package/server/methods/epoch/get-epoch-schedule.ts +15 -0
  66. package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
  67. package/server/methods/epoch/get-leader-schedule.ts +8 -0
  68. package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
  69. package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
  70. package/server/methods/epoch/get-slot-leader.ts +6 -0
  71. package/server/methods/epoch/get-slot-leaders.ts +9 -0
  72. package/server/methods/epoch/get-stake-activation.ts +9 -0
  73. package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
  74. package/server/methods/epoch/get-vote-accounts.ts +19 -0
  75. package/server/methods/epoch/index.ts +13 -0
  76. package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
  77. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
  78. package/server/methods/fee/get-fee-for-message.ts +8 -0
  79. package/server/methods/fee/get-fee-rate-governor.ts +16 -0
  80. package/server/methods/fee/get-fees.ts +14 -0
  81. package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
  82. package/server/methods/fee/index.ts +5 -0
  83. package/server/methods/get-address-lookup-table.ts +31 -0
  84. package/server/methods/index.ts +265 -0
  85. package/server/methods/performance/get-recent-performance-samples.ts +25 -0
  86. package/server/methods/performance/get-transaction-count.ts +5 -0
  87. package/server/methods/performance/index.ts +2 -0
  88. package/server/methods/program/get-block-commitment.ts +9 -0
  89. package/server/methods/program/get-block-production.ts +14 -0
  90. package/server/methods/program/get-block-time.ts +21 -0
  91. package/server/methods/program/get-blocks.ts +11 -0
  92. package/server/methods/program/get-first-available-block.ts +9 -0
  93. package/server/methods/program/get-genesis-hash.ts +6 -0
  94. package/server/methods/program/get-identity.ts +6 -0
  95. package/server/methods/program/get-inflation-governor.ts +15 -0
  96. package/server/methods/program/get-inflation-rate.ts +10 -0
  97. package/server/methods/program/get-inflation-reward.ts +12 -0
  98. package/server/methods/program/get-largest-accounts.ts +8 -0
  99. package/server/methods/program/get-parsed-program-accounts.ts +12 -0
  100. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
  101. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
  102. package/server/methods/program/get-program-accounts.ts +221 -0
  103. package/server/methods/program/get-supply.ts +13 -0
  104. package/server/methods/program/get-token-account-balance.ts +64 -0
  105. package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
  106. package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
  107. package/server/methods/program/get-token-largest-accounts.ts +80 -0
  108. package/server/methods/program/get-token-supply.ts +38 -0
  109. package/server/methods/program/index.ts +21 -0
  110. package/server/methods/solforge/index.ts +155 -0
  111. package/server/methods/system/get-health.ts +5 -0
  112. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
  113. package/server/methods/system/get-version.ts +9 -0
  114. package/server/methods/system/index.ts +3 -0
  115. package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
  116. package/server/methods/transaction/get-parsed-transaction.ts +21 -0
  117. package/server/methods/transaction/get-signature-statuses.ts +72 -0
  118. package/server/methods/transaction/get-signatures-for-address.ts +45 -0
  119. package/server/methods/transaction/get-transaction.ts +428 -0
  120. package/server/methods/transaction/index.ts +7 -0
  121. package/server/methods/transaction/send-transaction.ts +232 -0
  122. package/server/methods/transaction/simulate-transaction.ts +56 -0
  123. package/server/rpc-server.ts +474 -0
  124. package/server/types.ts +74 -0
  125. package/server/ws-server.ts +171 -0
  126. package/sf.config.json +38 -0
  127. package/src/cli/bootstrap.ts +67 -0
  128. package/src/cli/commands/airdrop.ts +37 -0
  129. package/src/cli/commands/config.ts +39 -0
  130. package/src/cli/commands/mint.ts +187 -0
  131. package/src/cli/commands/program-clone.ts +124 -0
  132. package/src/cli/commands/program-load.ts +64 -0
  133. package/src/cli/commands/rpc-start.ts +46 -0
  134. package/src/cli/commands/token-adopt-authority.ts +37 -0
  135. package/src/cli/commands/token-clone.ts +113 -0
  136. package/src/cli/commands/token-create.ts +81 -0
  137. package/src/cli/main.ts +130 -0
  138. package/src/cli/run-solforge.ts +98 -0
  139. package/src/cli/setup-utils.ts +54 -0
  140. package/src/cli/setup-wizard.ts +256 -0
  141. package/src/cli/utils/args.ts +15 -0
  142. package/src/config/index.ts +130 -0
  143. package/src/db/index.ts +83 -0
  144. package/src/db/schema/accounts.ts +23 -0
  145. package/src/db/schema/address-signatures.ts +31 -0
  146. package/src/db/schema/index.ts +5 -0
  147. package/src/db/schema/meta-kv.ts +9 -0
  148. package/src/db/schema/transactions.ts +29 -0
  149. package/src/db/schema/tx-accounts.ts +33 -0
  150. package/src/db/tx-store.ts +229 -0
  151. package/src/gui/public/app.css +1 -0
  152. package/src/gui/public/index.html +19 -0
  153. package/src/gui/server.ts +297 -0
  154. package/src/gui/src/api.ts +127 -0
  155. package/src/gui/src/app.tsx +390 -0
  156. package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
  157. package/src/gui/src/components/clone-program-modal.tsx +183 -0
  158. package/src/gui/src/components/clone-token-modal.tsx +211 -0
  159. package/src/gui/src/components/modal.tsx +127 -0
  160. package/src/gui/src/components/programs-panel.tsx +112 -0
  161. package/src/gui/src/components/status-panel.tsx +122 -0
  162. package/src/gui/src/components/tokens-panel.tsx +116 -0
  163. package/src/gui/src/hooks/use-interval.ts +17 -0
  164. package/src/gui/src/index.css +529 -0
  165. package/src/gui/src/main.tsx +17 -0
  166. package/src/migrations-bundled.ts +17 -0
  167. package/src/rpc/start.ts +44 -0
  168. package/tailwind.config.js +27 -0
  169. package/test-client.ts +120 -0
  170. package/tmp/inspect-html.ts +4 -0
  171. package/tmp/response-test.ts +5 -0
  172. package/tmp/test-html.ts +5 -0
  173. package/tmp/test-server.ts +13 -0
  174. package/tsconfig.json +24 -23
  175. package/LICENSE +0 -21
  176. package/scripts/postinstall.cjs +0 -103
  177. package/src/api-server-entry.ts +0 -109
  178. package/src/commands/add-program.ts +0 -337
  179. package/src/commands/init.ts +0 -122
  180. package/src/commands/list.ts +0 -136
  181. package/src/commands/mint.ts +0 -288
  182. package/src/commands/start.ts +0 -877
  183. package/src/commands/status.ts +0 -99
  184. package/src/commands/stop.ts +0 -406
  185. package/src/config/manager.ts +0 -157
  186. package/src/index.ts +0 -188
  187. package/src/services/api-server.ts +0 -485
  188. package/src/services/port-manager.ts +0 -177
  189. package/src/services/process-registry.ts +0 -154
  190. package/src/services/program-cloner.ts +0 -317
  191. package/src/services/token-cloner.ts +0 -809
  192. package/src/services/validator.ts +0 -295
  193. package/src/types/config.ts +0 -110
  194. package/src/utils/shell.ts +0 -110
  195. package/src/utils/token-loader.ts +0 -115
@@ -1,295 +0,0 @@
1
- import { spawn, ChildProcess } from "child_process";
2
- import { existsSync } from "fs";
3
- import { join } from "path";
4
- import type {
5
- ValidatorState,
6
- ValidatorStatus,
7
- LocalnetConfig,
8
- OperationResult,
9
- } from "../types/config.js";
10
-
11
- 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
- }
295
- }
@@ -1,110 +0,0 @@
1
- import { z } from "zod";
2
-
3
- // Token configuration schema
4
- export const TokenConfigSchema = z.object({
5
- symbol: z.string().min(1, "Token symbol is required"),
6
- mainnetMint: z.string().min(1, "Mainnet mint address is required"),
7
- mintAuthority: z.string().optional(), // Path to keypair file
8
- mintAmount: z
9
- .number()
10
- .positive("Mint amount must be positive")
11
- .default(1000000), // Amount to mint to mint authority
12
- recipients: z
13
- .array(
14
- z.object({
15
- wallet: z.string().min(1, "Wallet address is required"),
16
- amount: z.number().positive("Amount must be positive"),
17
- })
18
- )
19
- .default([]),
20
- cloneMetadata: z.boolean().default(true), // Whether to clone token metadata
21
- });
22
-
23
- // Program configuration schema
24
- export const ProgramConfigSchema = z.object({
25
- name: z.string().optional(),
26
- mainnetProgramId: z.string().min(1, "Program ID is required"),
27
- deployPath: z.string().optional(), // Optional path to local .so file
28
- upgradeable: z.boolean().default(false),
29
- cluster: z
30
- .enum(["mainnet-beta", "devnet", "testnet"])
31
- .default("mainnet-beta"),
32
- dependencies: z.array(z.string()).default([]), // Other program IDs this program depends on
33
- });
34
-
35
- // Localnet configuration schema
36
- export const LocalnetConfigSchema = z.object({
37
- airdropAmount: z.number().positive().default(100),
38
- faucetAccounts: z.array(z.string()).default([]),
39
- logLevel: z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
40
- port: z.number().int().min(1000).max(65535).default(8899),
41
- faucetPort: z.number().int().min(1000).max(65535).default(9900),
42
- reset: z.boolean().default(false),
43
- quiet: z.boolean().default(false),
44
- ledgerPath: z.string().optional(),
45
- bindAddress: z.string().default("127.0.0.1"),
46
- limitLedgerSize: z.number().int().positive().default(100000),
47
- rpc: z
48
- .string()
49
- .url("RPC must be a valid URL")
50
- .default("https://api.mainnet-beta.solana.com"),
51
- });
52
-
53
- // Complete configuration schema
54
- export const ConfigSchema = z.object({
55
- name: z.string().default("solforge-localnet"),
56
- description: z.string().optional(),
57
- tokens: z.array(TokenConfigSchema).default([]),
58
- programs: z.array(ProgramConfigSchema).default([]),
59
- localnet: LocalnetConfigSchema.default({}),
60
- });
61
-
62
- // Inferred TypeScript types
63
- export type TokenConfig = z.infer<typeof TokenConfigSchema>;
64
- export type ProgramConfig = z.infer<typeof ProgramConfigSchema>;
65
- export type LocalnetConfig = z.infer<typeof LocalnetConfigSchema>;
66
- export type Config = z.infer<typeof ConfigSchema>;
67
-
68
- // Validator status types
69
- export type ValidatorStatus =
70
- | "stopped"
71
- | "starting"
72
- | "running"
73
- | "stopping"
74
- | "error";
75
-
76
- export interface ValidatorState {
77
- status: ValidatorStatus;
78
- pid?: number;
79
- port: number;
80
- faucetPort: number;
81
- rpcUrl: string;
82
- wsUrl: string;
83
- startTime?: Date;
84
- logs: string[];
85
- error?: string;
86
- }
87
-
88
- // Operation result types
89
- export interface OperationResult<T = any> {
90
- success: boolean;
91
- data?: T;
92
- error?: string;
93
- details?: any;
94
- }
95
-
96
- export interface CloneResult {
97
- type: "program" | "token";
98
- id: string;
99
- name?: string;
100
- success: boolean;
101
- error?: string;
102
- }
103
-
104
- export interface ValidationResult {
105
- valid: boolean;
106
- errors: Array<{
107
- path: string;
108
- message: string;
109
- }>;
110
- }
@@ -1,110 +0,0 @@
1
- import { $ } from "bun";
2
- import chalk from "chalk";
3
-
4
- export interface CommandResult {
5
- success: boolean;
6
- stdout: string;
7
- stderr: string;
8
- exitCode: number;
9
- }
10
-
11
- /**
12
- * Execute a shell command and return the result
13
- */
14
- export async function runCommand(
15
- command: string,
16
- args: string[] = [],
17
- options: {
18
- silent?: boolean;
19
- jsonOutput?: boolean;
20
- debug?: boolean;
21
- } = {}
22
- ): Promise<CommandResult> {
23
- const { silent = false, jsonOutput = false, debug = false } = options;
24
-
25
- try {
26
- if (!silent || debug) {
27
- console.log(chalk.gray(`$ ${command} ${args.join(" ")}`));
28
- }
29
-
30
- const result = await $`${command} ${args}`.quiet();
31
-
32
- const stdout = result.stdout.toString();
33
- const stderr = result.stderr.toString();
34
- const exitCode = result.exitCode;
35
- const success = exitCode === 0;
36
-
37
- if (debug) {
38
- console.log(chalk.gray(`Exit code: ${exitCode}`));
39
- if (stdout) {
40
- console.log(chalk.gray(`Stdout: ${stdout}`));
41
- }
42
- if (stderr) {
43
- console.log(chalk.gray(`Stderr: ${stderr}`));
44
- }
45
- }
46
-
47
- if (!silent && !success) {
48
- console.error(chalk.red(`Command failed with exit code ${exitCode}`));
49
- if (stderr) {
50
- console.error(chalk.red(`Error: ${stderr}`));
51
- }
52
- }
53
-
54
- // If JSON output is expected, try to parse it
55
- let parsedOutput = stdout;
56
- if (jsonOutput && success && stdout.trim()) {
57
- try {
58
- parsedOutput = JSON.parse(stdout);
59
- } catch (e) {
60
- // If JSON parsing fails, keep original stdout
61
- console.warn(
62
- chalk.yellow("Warning: Expected JSON output but got invalid JSON")
63
- );
64
- }
65
- }
66
-
67
- return {
68
- success,
69
- stdout: parsedOutput,
70
- stderr,
71
- exitCode,
72
- };
73
- } catch (error) {
74
- const errorMessage = error instanceof Error ? error.message : String(error);
75
-
76
- if (!silent) {
77
- console.error(chalk.red(`Command execution failed: ${errorMessage}`));
78
- }
79
-
80
- return {
81
- success: false,
82
- stdout: "",
83
- stderr: errorMessage,
84
- exitCode: 1,
85
- };
86
- }
87
- }
88
-
89
- /**
90
- * Check if a command exists in PATH
91
- */
92
- export async function commandExists(command: string): Promise<boolean> {
93
- const result = await runCommand("which", [command], { silent: true });
94
- return result.success;
95
- }
96
-
97
- /**
98
- * Check if solana CLI tools are available
99
- */
100
- export async function checkSolanaTools(): Promise<{
101
- solana: boolean;
102
- splToken: boolean;
103
- }> {
104
- const [solana, splToken] = await Promise.all([
105
- commandExists("solana"),
106
- commandExists("spl-token"),
107
- ]);
108
-
109
- return { solana, splToken };
110
- }
@@ -1,115 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- import { Keypair } from "@solana/web3.js";
4
- import type { TokenConfig } from "../types/config.js";
5
-
6
- export interface ClonedToken {
7
- config: TokenConfig;
8
- mintAuthorityPath: string;
9
- modifiedAccountPath: string;
10
- metadataAccountPath?: string;
11
- mintAuthority: {
12
- publicKey: string;
13
- secretKey: number[];
14
- };
15
- }
16
-
17
- /**
18
- * Shared utility to load cloned tokens from the work directory.
19
- * This ensures consistent token loading across CLI and API server.
20
- */
21
- export async function loadClonedTokens(
22
- tokenConfigs: TokenConfig[],
23
- workDir: string = ".solforge"
24
- ): Promise<ClonedToken[]> {
25
- const clonedTokens: ClonedToken[] = [];
26
-
27
- // Load shared mint authority
28
- const sharedMintAuthorityPath = join(workDir, "shared-mint-authority.json");
29
- let sharedMintAuthority: { publicKey: string; secretKey: number[] } | null =
30
- null;
31
-
32
- if (existsSync(sharedMintAuthorityPath)) {
33
- try {
34
- const fileContent = JSON.parse(
35
- readFileSync(sharedMintAuthorityPath, "utf8")
36
- );
37
-
38
- if (Array.isArray(fileContent)) {
39
- // New format: file contains just the secret key array
40
- const keypair = Keypair.fromSecretKey(new Uint8Array(fileContent));
41
- sharedMintAuthority = {
42
- publicKey: keypair.publicKey.toBase58(),
43
- secretKey: Array.from(keypair.secretKey),
44
- };
45
-
46
- // Check metadata for consistency
47
- const metadataPath = join(workDir, "shared-mint-authority-meta.json");
48
- if (existsSync(metadataPath)) {
49
- const metadata = JSON.parse(readFileSync(metadataPath, "utf8"));
50
- if (metadata.publicKey !== sharedMintAuthority.publicKey) {
51
- sharedMintAuthority.publicKey = metadata.publicKey;
52
- }
53
- }
54
- } else {
55
- // Old format: file contains {publicKey, secretKey}
56
- sharedMintAuthority = fileContent;
57
- }
58
- } catch (error) {
59
- console.error("Failed to load shared mint authority:", error);
60
- return [];
61
- }
62
- }
63
-
64
- if (!sharedMintAuthority) {
65
- return [];
66
- }
67
-
68
- // Load each token that has been cloned
69
- for (const tokenConfig of tokenConfigs) {
70
- const tokenDir = join(workDir, `token-${tokenConfig.symbol.toLowerCase()}`);
71
- const modifiedAccountPath = join(tokenDir, "modified.json");
72
- const metadataAccountPath = join(tokenDir, "metadata.json");
73
-
74
- // Check if this token has already been cloned
75
- if (existsSync(modifiedAccountPath)) {
76
- const clonedToken: ClonedToken = {
77
- config: tokenConfig,
78
- mintAuthorityPath: sharedMintAuthorityPath,
79
- modifiedAccountPath,
80
- mintAuthority: sharedMintAuthority,
81
- };
82
-
83
- // Add metadata path if it exists
84
- if (existsSync(metadataAccountPath)) {
85
- clonedToken.metadataAccountPath = metadataAccountPath;
86
- }
87
-
88
- clonedTokens.push(clonedToken);
89
- }
90
- }
91
-
92
- return clonedTokens;
93
- }
94
-
95
- /**
96
- * Find a cloned token by its mint address
97
- */
98
- export function findTokenByMint(
99
- clonedTokens: ClonedToken[],
100
- mintAddress: string
101
- ): ClonedToken | undefined {
102
- return clonedTokens.find((token) => token.config.mainnetMint === mintAddress);
103
- }
104
-
105
- /**
106
- * Find a cloned token by its symbol
107
- */
108
- export function findTokenBySymbol(
109
- clonedTokens: ClonedToken[],
110
- symbol: string
111
- ): ClonedToken | undefined {
112
- return clonedTokens.find(
113
- (token) => token.config.symbol.toLowerCase() === symbol.toLowerCase()
114
- );
115
- }