solforge 0.1.6 → 0.1.7

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 CHANGED
@@ -318,7 +318,7 @@ Here's the complete schema:
318
318
  "faucetAccounts": ["YourWalletPublicKeyHere"],
319
319
  "port": 8899,
320
320
  "faucetPort": 9900,
321
- "reset": true,
321
+ "reset": false,
322
322
  "logLevel": "info",
323
323
  "bindAddress": "127.0.0.1",
324
324
  "limitLedgerSize": 100000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Solana localnet orchestration tool for cloning mainnet programs and tokens",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -22,11 +22,11 @@ const defaultConfig: Config = {
22
22
  faucetAccounts: [],
23
23
  port: 8899,
24
24
  faucetPort: 9900,
25
- reset: true,
25
+ reset: false,
26
26
  logLevel: "info",
27
27
  bindAddress: "127.0.0.1",
28
28
  quiet: false,
29
- rpc: "https://mainnet.helius-rpc.com/?api-key=3a3b84ef-2985-4543-9d67-535eb707b6ec",
29
+ rpc: "https://api.mainnet-beta.solana.com",
30
30
  limitLedgerSize: 100000,
31
31
  },
32
32
  };
@@ -5,22 +5,12 @@ import { join } from "path";
5
5
  import { input, select } from "@inquirer/prompts";
6
6
  import { runCommand } from "../utils/shell";
7
7
  import { Keypair, PublicKey } from "@solana/web3.js";
8
-
9
- interface TokenConfig {
10
- symbol: string;
11
- mainnetMint: string;
12
- mintAmount: number;
13
- }
14
-
15
- interface ClonedToken {
16
- config: TokenConfig;
17
- mintAuthorityPath: string;
18
- modifiedAccountPath: string;
19
- mintAuthority: {
20
- publicKey: string;
21
- secretKey: number[];
22
- };
23
- }
8
+ import {
9
+ loadClonedTokens,
10
+ findTokenBySymbol,
11
+ type ClonedToken,
12
+ } from "../utils/token-loader.js";
13
+ import type { TokenConfig } from "../types/config.js";
24
14
 
25
15
  export const mintCommand = new Command()
26
16
  .name("mint")
@@ -71,9 +61,7 @@ export const mintCommand = new Command()
71
61
  // Select token (or use provided symbol)
72
62
  let selectedToken: ClonedToken;
73
63
  if (options.symbol) {
74
- const token = tokens.find(
75
- (t) => t.config.symbol.toLowerCase() === options.symbol.toLowerCase()
76
- );
64
+ const token = findTokenBySymbol(tokens, options.symbol);
77
65
  if (!token) {
78
66
  console.error(
79
67
  chalk.red(`❌ Token ${options.symbol} not found in cloned tokens`)
@@ -181,8 +169,6 @@ export const mintCommand = new Command()
181
169
  });
182
170
 
183
171
  async function loadAvailableTokens(workDir: string): Promise<ClonedToken[]> {
184
- const tokens: ClonedToken[] = [];
185
-
186
172
  try {
187
173
  // Load token config from sf.config.json
188
174
  const configPath = "sf.config.json";
@@ -193,42 +179,8 @@ async function loadAvailableTokens(workDir: string): Promise<ClonedToken[]> {
193
179
  const config = JSON.parse(readFileSync(configPath, "utf8"));
194
180
  const tokenConfigs: TokenConfig[] = config.tokens || [];
195
181
 
196
- // Load shared mint authority
197
- const sharedMintAuthorityPath = join(workDir, "shared-mint-authority.json");
198
- if (!existsSync(sharedMintAuthorityPath)) {
199
- throw new Error("Shared mint authority not found");
200
- }
201
-
202
- const secretKeyArray = JSON.parse(
203
- readFileSync(sharedMintAuthorityPath, "utf8")
204
- );
205
- const mintAuthorityKeypair = Keypair.fromSecretKey(
206
- new Uint8Array(secretKeyArray)
207
- );
208
- const mintAuthority = {
209
- publicKey: mintAuthorityKeypair.publicKey.toBase58(),
210
- secretKey: Array.from(mintAuthorityKeypair.secretKey),
211
- };
212
-
213
- // Build cloned tokens list
214
- for (const tokenConfig of tokenConfigs) {
215
- const tokenDir = join(
216
- workDir,
217
- `token-${tokenConfig.symbol.toLowerCase()}`
218
- );
219
- const modifiedAccountPath = join(tokenDir, "modified.json");
220
-
221
- if (existsSync(modifiedAccountPath)) {
222
- tokens.push({
223
- config: tokenConfig,
224
- mintAuthorityPath: sharedMintAuthorityPath,
225
- modifiedAccountPath,
226
- mintAuthority,
227
- });
228
- }
229
- }
230
-
231
- return tokens;
182
+ // Use the shared token loader
183
+ return await loadClonedTokens(tokenConfigs, workDir);
232
184
  } catch (error) {
233
185
  throw new Error(`Failed to load tokens: ${error}`);
234
186
  }
@@ -6,11 +6,10 @@ import { join } from "path";
6
6
  import { runCommand, checkSolanaTools } from "../utils/shell.js";
7
7
  import { configManager } from "../config/manager.js";
8
8
  import { TokenCloner } from "../services/token-cloner.js";
9
- import { ProgramCloner } from "../services/program-cloner.js";
10
9
  import { processRegistry } from "../services/process-registry.js";
11
10
  import { portManager } from "../services/port-manager.js";
12
11
 
13
- import type { Config, TokenConfig, ProgramConfig } from "../types/config.js";
12
+ import type { Config, TokenConfig } from "../types/config.js";
14
13
  import type { ClonedToken } from "../services/token-cloner.js";
15
14
  import type { RunningValidator } from "../services/process-registry.js";
16
15
 
@@ -11,8 +11,12 @@ import { runCommand } from "../utils/shell.js";
11
11
  import { TokenCloner } from "./token-cloner.js";
12
12
  import { ProgramCloner } from "./program-cloner.js";
13
13
  import { mintTokenToWallet as mintTokenToWalletShared } from "../commands/mint.js";
14
+ import {
15
+ loadClonedTokens,
16
+ findTokenByMint,
17
+ type ClonedToken,
18
+ } from "../utils/token-loader.js";
14
19
  import type { Config } from "../types/config.js";
15
- import type { ClonedToken } from "./token-cloner.js";
16
20
 
17
21
  export interface APIServerConfig {
18
22
  port: number;
@@ -256,57 +260,10 @@ export class APIServer {
256
260
  }
257
261
 
258
262
  private async getClonedTokens(): Promise<ClonedToken[]> {
259
- const clonedTokens: ClonedToken[] = [];
260
-
261
- for (const tokenConfig of this.config.config.tokens) {
262
- const tokenDir = join(
263
- this.config.workDir,
264
- `token-${tokenConfig.symbol.toLowerCase()}`
265
- );
266
- const modifiedAccountPath = join(tokenDir, "modified.json");
267
- const sharedMintAuthorityPath = join(
268
- this.config.workDir,
269
- "shared-mint-authority.json"
270
- );
271
-
272
- if (
273
- existsSync(modifiedAccountPath) &&
274
- existsSync(sharedMintAuthorityPath)
275
- ) {
276
- try {
277
- const mintAuthorityData = JSON.parse(
278
- readFileSync(sharedMintAuthorityPath, "utf8")
279
- );
280
- let mintAuthority;
281
-
282
- if (Array.isArray(mintAuthorityData)) {
283
- const keypair = Keypair.fromSecretKey(
284
- new Uint8Array(mintAuthorityData)
285
- );
286
- mintAuthority = {
287
- publicKey: keypair.publicKey.toBase58(),
288
- secretKey: Array.from(keypair.secretKey),
289
- };
290
- } else {
291
- mintAuthority = mintAuthorityData;
292
- }
293
-
294
- clonedTokens.push({
295
- config: tokenConfig,
296
- mintAuthorityPath: sharedMintAuthorityPath,
297
- modifiedAccountPath,
298
- mintAuthority,
299
- });
300
- } catch (error) {
301
- console.error(
302
- `Failed to load cloned token ${tokenConfig.symbol}:`,
303
- error
304
- );
305
- }
306
- }
307
- }
308
-
309
- return clonedTokens;
263
+ return await loadClonedTokens(
264
+ this.config.config.tokens,
265
+ this.config.workDir
266
+ );
310
267
  }
311
268
 
312
269
  private async getClonedPrograms(): Promise<
@@ -341,14 +298,10 @@ export class APIServer {
341
298
  amount: number
342
299
  ): Promise<any> {
343
300
  const clonedTokens = await this.getClonedTokens();
344
- const token = clonedTokens.find(
345
- (t) => t.config.mainnetMint === mintAddress
346
- );
301
+ const token = findTokenByMint(clonedTokens, mintAddress);
347
302
 
348
303
  if (!token) {
349
- throw new Error(
350
- `Token with mint address ${mintAddress} not found in cloned tokens`
351
- );
304
+ throw new Error(`Token ${mintAddress} not found in cloned tokens`);
352
305
  }
353
306
 
354
307
  // Use the shared minting function from the mint command
@@ -0,0 +1,115 @@
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
+ }