solforge 0.2.12 → 0.2.14

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 (175) hide show
  1. package/package.json +1 -5
  2. package/start.cjs +19 -23
  3. package/docs/API.md +0 -379
  4. package/docs/CONFIGURATION.md +0 -407
  5. package/docs/bun-single-file-executable.md +0 -585
  6. package/docs/cli-plan.md +0 -154
  7. package/docs/data-indexing-plan.md +0 -214
  8. package/docs/gui-roadmap.md +0 -202
  9. package/scripts/decode-b58.ts +0 -10
  10. package/scripts/install.sh +0 -112
  11. package/server/index.ts +0 -5
  12. package/server/lib/base58.ts +0 -33
  13. package/server/lib/faucet.ts +0 -110
  14. package/server/lib/instruction-parser.ts +0 -328
  15. package/server/lib/parsers/spl-associated-token-account.ts +0 -50
  16. package/server/lib/parsers/spl-token.ts +0 -340
  17. package/server/lib/spl-token.ts +0 -57
  18. package/server/methods/TEMPLATE.md +0 -117
  19. package/server/methods/account/get-account-info.ts +0 -86
  20. package/server/methods/account/get-balance.ts +0 -23
  21. package/server/methods/account/get-multiple-accounts.ts +0 -84
  22. package/server/methods/account/get-parsed-account-info.ts +0 -17
  23. package/server/methods/account/index.ts +0 -12
  24. package/server/methods/account/parsers/index.ts +0 -52
  25. package/server/methods/account/parsers/loader-upgradeable.ts +0 -79
  26. package/server/methods/account/parsers/spl-token.ts +0 -256
  27. package/server/methods/account/parsers/system.ts +0 -4
  28. package/server/methods/account/request-airdrop.ts +0 -271
  29. package/server/methods/admin/adopt-mint-authority.ts +0 -94
  30. package/server/methods/admin/clone-program-accounts.ts +0 -55
  31. package/server/methods/admin/clone-program.ts +0 -152
  32. package/server/methods/admin/clone-token-accounts.ts +0 -117
  33. package/server/methods/admin/clone-token-mint.ts +0 -82
  34. package/server/methods/admin/create-mint.ts +0 -114
  35. package/server/methods/admin/create-token-account.ts +0 -137
  36. package/server/methods/admin/helpers.ts +0 -70
  37. package/server/methods/admin/index.ts +0 -10
  38. package/server/methods/admin/list-mints.ts +0 -21
  39. package/server/methods/admin/load-program.ts +0 -52
  40. package/server/methods/admin/mint-to.ts +0 -266
  41. package/server/methods/block/get-block-height.ts +0 -5
  42. package/server/methods/block/get-block.ts +0 -31
  43. package/server/methods/block/get-blocks-with-limit.ts +0 -19
  44. package/server/methods/block/get-latest-blockhash.ts +0 -12
  45. package/server/methods/block/get-slot.ts +0 -5
  46. package/server/methods/block/index.ts +0 -6
  47. package/server/methods/block/is-blockhash-valid.ts +0 -19
  48. package/server/methods/epoch/get-cluster-nodes.ts +0 -17
  49. package/server/methods/epoch/get-epoch-info.ts +0 -16
  50. package/server/methods/epoch/get-epoch-schedule.ts +0 -15
  51. package/server/methods/epoch/get-highest-snapshot-slot.ts +0 -9
  52. package/server/methods/epoch/get-leader-schedule.ts +0 -8
  53. package/server/methods/epoch/get-max-retransmit-slot.ts +0 -9
  54. package/server/methods/epoch/get-max-shred-insert-slot.ts +0 -9
  55. package/server/methods/epoch/get-slot-leader.ts +0 -6
  56. package/server/methods/epoch/get-slot-leaders.ts +0 -9
  57. package/server/methods/epoch/get-stake-activation.ts +0 -9
  58. package/server/methods/epoch/get-stake-minimum-delegation.ts +0 -9
  59. package/server/methods/epoch/get-vote-accounts.ts +0 -19
  60. package/server/methods/epoch/index.ts +0 -13
  61. package/server/methods/epoch/minimum-ledger-slot.ts +0 -5
  62. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +0 -12
  63. package/server/methods/fee/get-fee-for-message.ts +0 -8
  64. package/server/methods/fee/get-fee-rate-governor.ts +0 -16
  65. package/server/methods/fee/get-fees.ts +0 -14
  66. package/server/methods/fee/get-recent-prioritization-fees.ts +0 -22
  67. package/server/methods/fee/index.ts +0 -5
  68. package/server/methods/get-address-lookup-table.ts +0 -27
  69. package/server/methods/index.ts +0 -265
  70. package/server/methods/performance/get-recent-performance-samples.ts +0 -25
  71. package/server/methods/performance/get-transaction-count.ts +0 -5
  72. package/server/methods/performance/index.ts +0 -2
  73. package/server/methods/program/get-block-commitment.ts +0 -9
  74. package/server/methods/program/get-block-production.ts +0 -14
  75. package/server/methods/program/get-block-time.ts +0 -21
  76. package/server/methods/program/get-blocks.ts +0 -11
  77. package/server/methods/program/get-first-available-block.ts +0 -9
  78. package/server/methods/program/get-genesis-hash.ts +0 -6
  79. package/server/methods/program/get-identity.ts +0 -6
  80. package/server/methods/program/get-inflation-governor.ts +0 -15
  81. package/server/methods/program/get-inflation-rate.ts +0 -10
  82. package/server/methods/program/get-inflation-reward.ts +0 -12
  83. package/server/methods/program/get-largest-accounts.ts +0 -8
  84. package/server/methods/program/get-parsed-program-accounts.ts +0 -12
  85. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +0 -12
  86. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +0 -12
  87. package/server/methods/program/get-program-accounts.ts +0 -221
  88. package/server/methods/program/get-supply.ts +0 -13
  89. package/server/methods/program/get-token-account-balance.ts +0 -60
  90. package/server/methods/program/get-token-accounts-by-delegate.ts +0 -82
  91. package/server/methods/program/get-token-accounts-by-owner.ts +0 -416
  92. package/server/methods/program/get-token-largest-accounts.ts +0 -81
  93. package/server/methods/program/get-token-supply.ts +0 -39
  94. package/server/methods/program/index.ts +0 -21
  95. package/server/methods/solforge/index.ts +0 -158
  96. package/server/methods/system/get-health.ts +0 -5
  97. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +0 -13
  98. package/server/methods/system/get-version.ts +0 -9
  99. package/server/methods/system/index.ts +0 -3
  100. package/server/methods/transaction/get-confirmed-transaction.ts +0 -11
  101. package/server/methods/transaction/get-parsed-transaction.ts +0 -17
  102. package/server/methods/transaction/get-signature-statuses.ts +0 -79
  103. package/server/methods/transaction/get-signatures-for-address.ts +0 -41
  104. package/server/methods/transaction/get-transaction.ts +0 -639
  105. package/server/methods/transaction/index.ts +0 -7
  106. package/server/methods/transaction/inner-instructions.test.ts +0 -104
  107. package/server/methods/transaction/send-transaction.ts +0 -469
  108. package/server/methods/transaction/simulate-transaction.ts +0 -57
  109. package/server/rpc-server.ts +0 -521
  110. package/server/types.ts +0 -109
  111. package/server/ws-server.ts +0 -178
  112. package/src/api-server-entry.ts +0 -109
  113. package/src/cli/bootstrap.ts +0 -67
  114. package/src/cli/commands/airdrop.ts +0 -37
  115. package/src/cli/commands/config.ts +0 -39
  116. package/src/cli/commands/mint.ts +0 -187
  117. package/src/cli/commands/program-clone.ts +0 -122
  118. package/src/cli/commands/program-load.ts +0 -64
  119. package/src/cli/commands/rpc-start.ts +0 -49
  120. package/src/cli/commands/token-adopt-authority.ts +0 -37
  121. package/src/cli/commands/token-clone.ts +0 -112
  122. package/src/cli/commands/token-create.ts +0 -81
  123. package/src/cli/main.ts +0 -158
  124. package/src/cli/run-solforge.ts +0 -112
  125. package/src/cli/setup-utils.ts +0 -54
  126. package/src/cli/setup-wizard.ts +0 -258
  127. package/src/cli/utils/args.ts +0 -15
  128. package/src/commands/add-program.ts +0 -333
  129. package/src/commands/init.ts +0 -122
  130. package/src/commands/list.ts +0 -136
  131. package/src/commands/mint.ts +0 -287
  132. package/src/commands/start.ts +0 -881
  133. package/src/commands/status.ts +0 -99
  134. package/src/commands/stop.ts +0 -405
  135. package/src/config/index.ts +0 -146
  136. package/src/config/manager.ts +0 -157
  137. package/src/db/index.ts +0 -83
  138. package/src/db/schema/accounts.ts +0 -23
  139. package/src/db/schema/address-signatures.ts +0 -31
  140. package/src/db/schema/index.ts +0 -6
  141. package/src/db/schema/meta-kv.ts +0 -9
  142. package/src/db/schema/transactions.ts +0 -36
  143. package/src/db/schema/tx-account-states.ts +0 -23
  144. package/src/db/schema/tx-accounts.ts +0 -33
  145. package/src/db/tx-store.ts +0 -264
  146. package/src/gui/public/app.css +0 -1556
  147. package/src/gui/public/build/main.css +0 -1569
  148. package/src/gui/public/build/main.js +0 -303
  149. package/src/gui/public/build/main.js.txt +0 -231
  150. package/src/gui/public/index.html +0 -19
  151. package/src/gui/server.ts +0 -296
  152. package/src/gui/src/api.ts +0 -127
  153. package/src/gui/src/app.tsx +0 -441
  154. package/src/gui/src/components/airdrop-mint-form.tsx +0 -246
  155. package/src/gui/src/components/clone-program-modal.tsx +0 -202
  156. package/src/gui/src/components/clone-token-modal.tsx +0 -230
  157. package/src/gui/src/components/modal.tsx +0 -134
  158. package/src/gui/src/components/programs-panel.tsx +0 -124
  159. package/src/gui/src/components/status-panel.tsx +0 -136
  160. package/src/gui/src/components/tokens-panel.tsx +0 -122
  161. package/src/gui/src/hooks/use-interval.ts +0 -17
  162. package/src/gui/src/index.css +0 -557
  163. package/src/gui/src/main.tsx +0 -17
  164. package/src/index.ts +0 -216
  165. package/src/migrations-bundled.ts +0 -23
  166. package/src/rpc/start.ts +0 -44
  167. package/src/services/api-server.ts +0 -504
  168. package/src/services/port-manager.ts +0 -174
  169. package/src/services/process-registry.ts +0 -153
  170. package/src/services/program-cloner.ts +0 -317
  171. package/src/services/token-cloner.ts +0 -811
  172. package/src/services/validator.ts +0 -293
  173. package/src/types/config.ts +0 -110
  174. package/src/utils/shell.ts +0 -110
  175. package/src/utils/token-loader.ts +0 -115
@@ -1,811 +0,0 @@
1
- import { Keypair, PublicKey } from "@solana/web3.js";
2
- import chalk from "chalk";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import type { TokenConfig } from "../types/config.js";
6
- import { runCommand } from "../utils/shell.js";
7
-
8
- // Metaplex Token Metadata Program ID
9
- const METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
10
-
11
- export interface ClonedToken {
12
- config: TokenConfig;
13
- mintAuthorityPath: string;
14
- modifiedAccountPath: string;
15
- metadataAccountPath?: string; // Path to cloned metadata account
16
- mintAuthority: {
17
- publicKey: string;
18
- secretKey: number[];
19
- };
20
- }
21
-
22
- export class TokenCloner {
23
- private workDir: string;
24
-
25
- constructor(workDir: string = ".solforge") {
26
- this.workDir = workDir;
27
- // Ensure work directory exists
28
- if (!existsSync(this.workDir)) {
29
- mkdirSync(this.workDir, { recursive: true });
30
- }
31
- }
32
-
33
- /**
34
- * Clone tokens by fetching account data, modifying mint authority, and preparing for validator
35
- */
36
- async cloneTokens(
37
- tokens: TokenConfig[],
38
- rpcUrl: string = "https://api.mainnet-beta.solana.com",
39
- debug: boolean = false,
40
- ): Promise<ClonedToken[]> {
41
- const clonedTokens: ClonedToken[] = [];
42
-
43
- // Generate one shared mint authority for all tokens
44
- const sharedMintAuthorityPath = join(
45
- this.workDir,
46
- "shared-mint-authority.json",
47
- );
48
- let sharedMintAuthority: { publicKey: string; secretKey: number[] };
49
-
50
- if (existsSync(sharedMintAuthorityPath)) {
51
- // Use existing shared mint authority
52
- console.log(chalk.gray(`🔑 Using existing shared mint authority...`));
53
- const fileContent = JSON.parse(
54
- readFileSync(sharedMintAuthorityPath, "utf8"),
55
- );
56
-
57
- // Handle both old format {publicKey, secretKey} and new format [secretKey array]
58
- if (Array.isArray(fileContent)) {
59
- // New format: file contains just the secret key array
60
- const keypair = Keypair.fromSecretKey(new Uint8Array(fileContent));
61
- sharedMintAuthority = {
62
- publicKey: keypair.publicKey.toBase58(),
63
- secretKey: Array.from(keypair.secretKey),
64
- };
65
-
66
- // Verify consistency with metadata if it exists
67
- const metadataPath = join(
68
- this.workDir,
69
- "shared-mint-authority-meta.json",
70
- );
71
- if (existsSync(metadataPath)) {
72
- const metadata = JSON.parse(readFileSync(metadataPath, "utf8"));
73
- if (metadata.publicKey !== sharedMintAuthority.publicKey) {
74
- console.log(chalk.yellow(`⚠️ Public key mismatch detected!`));
75
- console.log(chalk.gray(` Expected: ${metadata.publicKey}`));
76
- console.log(chalk.gray(` Got: ${sharedMintAuthority.publicKey}`));
77
- console.log(
78
- chalk.gray(
79
- ` Using the original saved public key for consistency`,
80
- ),
81
- );
82
- sharedMintAuthority.publicKey = metadata.publicKey;
83
- }
84
- }
85
- } else {
86
- // Old format: file contains {publicKey, secretKey}
87
- sharedMintAuthority = fileContent;
88
- }
89
- } else {
90
- // Generate new shared mint authority
91
- console.log(
92
- chalk.gray(`🔑 Generating shared mint authority for all tokens...`),
93
- );
94
- const keypair = Keypair.generate();
95
- sharedMintAuthority = {
96
- publicKey: keypair.publicKey.toBase58(),
97
- secretKey: Array.from(keypair.secretKey),
98
- };
99
-
100
- // Ensure work directory exists
101
- if (!existsSync(this.workDir)) {
102
- mkdirSync(this.workDir, { recursive: true });
103
- }
104
-
105
- // Save shared mint authority in the format expected by spl-token (just the secret key array)
106
- // But also save a metadata file for verification
107
- writeFileSync(
108
- sharedMintAuthorityPath,
109
- JSON.stringify(sharedMintAuthority.secretKey),
110
- );
111
-
112
- // Save metadata for debugging and verification
113
- const metadataPath = join(
114
- this.workDir,
115
- "shared-mint-authority-meta.json",
116
- );
117
- writeFileSync(
118
- metadataPath,
119
- JSON.stringify(
120
- {
121
- publicKey: sharedMintAuthority.publicKey,
122
- savedAt: new Date().toISOString(),
123
- },
124
- null,
125
- 2,
126
- ),
127
- );
128
- }
129
-
130
- console.log(
131
- chalk.gray(
132
- ` 📋 Shared mint authority: ${sharedMintAuthority.publicKey}`,
133
- ),
134
- );
135
-
136
- for (const token of tokens) {
137
- console.log(
138
- chalk.cyan(`🪙 Cloning ${token.symbol} (${token.mainnetMint})...`),
139
- );
140
-
141
- try {
142
- const cloned = await this.cloneToken(
143
- token,
144
- rpcUrl,
145
- debug,
146
- sharedMintAuthority,
147
- sharedMintAuthorityPath,
148
- );
149
- clonedTokens.push(cloned);
150
- console.log(chalk.green(` ✅ ${token.symbol} cloned successfully`));
151
- } catch (error) {
152
- console.error(chalk.red(` ❌ Failed to clone ${token.symbol}:`));
153
- console.error(
154
- chalk.red(
155
- ` ${error instanceof Error ? error.message : String(error)}`,
156
- ),
157
- );
158
- throw error; // Stop on first failure
159
- }
160
- }
161
-
162
- return clonedTokens;
163
- }
164
-
165
- /**
166
- * Clone a single token
167
- */
168
- private async cloneToken(
169
- token: TokenConfig,
170
- rpcUrl: string,
171
- debug: boolean = false,
172
- sharedMintAuthority: { publicKey: string; secretKey: number[] },
173
- sharedMintAuthorityPath: string,
174
- ): Promise<ClonedToken> {
175
- const tokenDir = join(this.workDir, `token-${token.symbol.toLowerCase()}`);
176
- if (!existsSync(tokenDir)) {
177
- mkdirSync(tokenDir, { recursive: true });
178
- }
179
-
180
- const originalAccountPath = join(tokenDir, "original.json");
181
- const modifiedAccountPath = join(tokenDir, "modified.json");
182
- const metadataAccountPath = join(tokenDir, "metadata.json");
183
-
184
- // Step 1: Fetch original account data from mainnet
185
- console.log(chalk.gray(` 📥 Fetching account data from mainnet...`));
186
- if (debug) {
187
- console.log(chalk.gray(` 🔗 Using RPC: ${rpcUrl}`));
188
- }
189
-
190
- const fetchResult = await runCommand(
191
- "solana",
192
- [
193
- "account",
194
- token.mainnetMint,
195
- "--output",
196
- "json-compact",
197
- "--output-file",
198
- originalAccountPath,
199
- "--url",
200
- rpcUrl,
201
- ],
202
- { silent: !debug, debug },
203
- );
204
-
205
- if (!fetchResult.success) {
206
- console.error(
207
- chalk.red(` ❌ Failed to fetch account data for ${token.symbol}`),
208
- );
209
- throw new Error(
210
- `Failed to fetch account: ${fetchResult.stderr || fetchResult.stdout}`,
211
- );
212
- }
213
-
214
- // Step 2: Clone metadata if requested
215
- let hasMetadata = false;
216
- if (token.cloneMetadata !== false) {
217
- // Default to true
218
- try {
219
- hasMetadata = await this.cloneTokenMetadata(
220
- token,
221
- rpcUrl,
222
- metadataAccountPath,
223
- debug,
224
- );
225
- if (hasMetadata) {
226
- console.log(chalk.gray(` 📋 Token metadata cloned successfully`));
227
- }
228
- } catch (error) {
229
- console.log(
230
- chalk.yellow(
231
- ` ⚠️ Failed to clone metadata: ${
232
- error instanceof Error ? error.message : String(error)
233
- }`,
234
- ),
235
- );
236
- // Don't fail the entire token cloning if metadata fails
237
- }
238
- }
239
-
240
- // Step 3: Modify mint authority
241
- console.log(chalk.gray(` 🔧 Modifying mint authority...`));
242
- await this.modifyMintAuthority(
243
- originalAccountPath,
244
- modifiedAccountPath,
245
- sharedMintAuthority,
246
- );
247
-
248
- const result: ClonedToken = {
249
- config: token,
250
- mintAuthorityPath: sharedMintAuthorityPath,
251
- modifiedAccountPath,
252
- mintAuthority: sharedMintAuthority,
253
- };
254
-
255
- // Add metadata path if we successfully cloned metadata
256
- if (hasMetadata) {
257
- result.metadataAccountPath = metadataAccountPath;
258
- }
259
-
260
- return result;
261
- }
262
-
263
- /**
264
- * Clone token metadata from Metaplex Token Metadata program
265
- */
266
- private async cloneTokenMetadata(
267
- token: TokenConfig,
268
- rpcUrl: string,
269
- metadataAccountPath: string,
270
- debug: boolean = false,
271
- ): Promise<boolean> {
272
- try {
273
- // Derive metadata account address
274
- const metadataAddress = await this.deriveMetadataAddress(
275
- token.mainnetMint,
276
- );
277
-
278
- if (debug) {
279
- console.log(chalk.gray(` 🔍 Metadata PDA: ${metadataAddress}`));
280
- }
281
-
282
- // Fetch metadata account data
283
- const fetchResult = await runCommand(
284
- "solana",
285
- [
286
- "account",
287
- metadataAddress,
288
- "--output",
289
- "json-compact",
290
- "--output-file",
291
- metadataAccountPath,
292
- "--url",
293
- rpcUrl,
294
- ],
295
- { silent: !debug, debug },
296
- );
297
-
298
- if (!fetchResult.success) {
299
- if (debug) {
300
- console.log(
301
- chalk.gray(` ℹ️ No metadata account found for ${token.symbol}`),
302
- );
303
- }
304
- return false;
305
- }
306
-
307
- return true;
308
- } catch (error) {
309
- throw new Error(
310
- `Failed to clone metadata: ${
311
- error instanceof Error ? error.message : String(error)
312
- }`,
313
- );
314
- }
315
- }
316
-
317
- /**
318
- * Derive the metadata account address for a given mint
319
- * PDA: ["metadata", metadata_program_id, mint_address]
320
- */
321
- private async deriveMetadataAddress(mintAddress: string): Promise<string> {
322
- try {
323
- // Use web3.js to derive the actual PDA
324
- const metadataProgram = new PublicKey(METADATA_PROGRAM_ID);
325
- const mint = new PublicKey(mintAddress);
326
-
327
- const [metadataAddress] = PublicKey.findProgramAddressSync(
328
- [Buffer.from("metadata"), metadataProgram.toBuffer(), mint.toBuffer()],
329
- metadataProgram,
330
- );
331
-
332
- return metadataAddress.toBase58();
333
- } catch (error) {
334
- throw new Error(
335
- `Failed to derive metadata PDA: ${
336
- error instanceof Error ? error.message : String(error)
337
- }`,
338
- );
339
- }
340
- }
341
-
342
- /**
343
- * Modify the mint account data to use a new mint authority
344
- */
345
- private async modifyMintAuthority(
346
- originalPath: string,
347
- modifiedPath: string,
348
- mintAuthority: { publicKey: string; secretKey: number[] },
349
- ): Promise<void> {
350
- // Read original account data
351
- const originalData = JSON.parse(readFileSync(originalPath, "utf8"));
352
-
353
- // Decode the base64 account data
354
- const accountData = Buffer.from(originalData.account.data[0], "base64");
355
-
356
- // Convert to mutable array for modification
357
- const dataArray = new Uint8Array(accountData);
358
-
359
- // Check if mint authority is set to None (fixed supply)
360
- const mintAuthorityOption = dataArray[0];
361
- const hasFixedSupply = mintAuthorityOption === 0;
362
-
363
- if (hasFixedSupply) {
364
- console.log(
365
- chalk.yellow(` ⚠️ Token has fixed supply, making it mintable...`),
366
- );
367
-
368
- // Set mint_authority_option to Some (1)
369
- dataArray[0] = 1;
370
-
371
- // Set the mint authority (bytes 4-35)
372
- const keypair = Keypair.fromSecretKey(
373
- new Uint8Array(mintAuthority.secretKey),
374
- );
375
- const newAuthorityBytes = keypair.publicKey.toBuffer();
376
-
377
- for (let i = 0; i < 32; i++) {
378
- const byte = newAuthorityBytes[i];
379
- if (byte !== undefined) {
380
- dataArray[4 + i] = byte;
381
- }
382
- }
383
- } else {
384
- console.log(chalk.gray(` 🔄 Updating existing mint authority...`));
385
-
386
- // Create mint authority public key from the keypair
387
- const keypair = Keypair.fromSecretKey(
388
- new Uint8Array(mintAuthority.secretKey),
389
- );
390
- const newAuthorityBytes = keypair.publicKey.toBuffer();
391
-
392
- // Replace mint authority (starts at byte 4, 32 bytes long)
393
- for (let i = 0; i < 32; i++) {
394
- const byte = newAuthorityBytes[i];
395
- if (byte !== undefined) {
396
- dataArray[4 + i] = byte;
397
- }
398
- }
399
- }
400
-
401
- // Convert back to base64
402
- const modifiedData = Buffer.from(dataArray).toString("base64");
403
-
404
- // Create modified account data
405
- const modifiedAccountData = {
406
- ...originalData,
407
- account: {
408
- ...originalData.account,
409
- data: [modifiedData, "base64"],
410
- },
411
- };
412
-
413
- // Handle rentEpoch properly (needs to be a number, not string)
414
- let jsonString = JSON.stringify(modifiedAccountData, null, 2);
415
- jsonString = jsonString.replace(
416
- /"rentEpoch":\s*\d+/g,
417
- '"rentEpoch": 18446744073709551615',
418
- );
419
-
420
- // Save modified account
421
- writeFileSync(modifiedPath, jsonString);
422
- }
423
-
424
- /**
425
- * Get validator arguments for cloned tokens
426
- */
427
- getValidatorArgs(clonedTokens: ClonedToken[]): string[] {
428
- const args: string[] = [];
429
-
430
- for (const token of clonedTokens) {
431
- // Add the modified mint account
432
- args.push(
433
- "--account",
434
- token.config.mainnetMint,
435
- token.modifiedAccountPath,
436
- );
437
-
438
- // Add metadata account if it exists
439
- if (token.metadataAccountPath && existsSync(token.metadataAccountPath)) {
440
- try {
441
- // Derive metadata address for this token
442
- const metadataAddress = this.deriveMetadataAddressSync(
443
- token.config.mainnetMint,
444
- );
445
- args.push("--account", metadataAddress, token.metadataAccountPath);
446
- console.log(
447
- chalk.gray(
448
- ` 📋 Adding metadata account for ${token.config.symbol}`,
449
- ),
450
- );
451
- } catch (error) {
452
- console.log(
453
- chalk.yellow(
454
- `⚠️ Failed to add metadata account for ${token.config.symbol}: ${
455
- error instanceof Error ? error.message : String(error)
456
- }`,
457
- ),
458
- );
459
- }
460
- }
461
- }
462
-
463
- return args;
464
- }
465
-
466
- /**
467
- * Synchronous version of deriveMetadataAddress for use in getValidatorArgs
468
- */
469
- private deriveMetadataAddressSync(mintAddress: string): string {
470
- try {
471
- // Use web3.js to derive the actual PDA
472
- const metadataProgram = new PublicKey(METADATA_PROGRAM_ID);
473
- const mint = new PublicKey(mintAddress);
474
-
475
- const [metadataAddress] = PublicKey.findProgramAddressSync(
476
- [Buffer.from("metadata"), metadataProgram.toBuffer(), mint.toBuffer()],
477
- metadataProgram,
478
- );
479
-
480
- return metadataAddress.toBase58();
481
- } catch (error) {
482
- throw new Error(
483
- `Failed to derive metadata PDA: ${
484
- error instanceof Error ? error.message : String(error)
485
- }`,
486
- );
487
- }
488
- }
489
-
490
- /**
491
- * Mint initial tokens to mint authority, then to recipients after validator is running
492
- */
493
- async mintTokensToRecipients(
494
- clonedTokens: ClonedToken[],
495
- rpcUrl: string,
496
- debug: boolean = false,
497
- ): Promise<void> {
498
- for (const token of clonedTokens) {
499
- console.log(chalk.cyan(`💰 Processing ${token.config.symbol}...`));
500
-
501
- // First, mint the default amount to the mint authority
502
- try {
503
- await this.mintToMintAuthority(token, rpcUrl, debug);
504
- console.log(
505
- chalk.green(
506
- ` ✅ Minted ${token.config.mintAmount} ${token.config.symbol} to mint authority`,
507
- ),
508
- );
509
- } catch (error) {
510
- console.error(chalk.red(` ❌ Failed to mint to mint authority:`));
511
- console.error(
512
- chalk.red(
513
- ` ${error instanceof Error ? error.message : String(error)}`,
514
- ),
515
- );
516
- continue; // Skip recipients if mint authority minting failed
517
- }
518
-
519
- // Then mint to recipients if any are configured
520
- if (token.config.recipients.length > 0) {
521
- console.log(
522
- chalk.gray(
523
- ` 🔄 Minting to ${token.config.recipients.length} recipients...`,
524
- ),
525
- );
526
-
527
- for (const recipient of token.config.recipients) {
528
- try {
529
- await this.mintToRecipient(
530
- token,
531
- recipient.wallet,
532
- recipient.amount,
533
- rpcUrl,
534
- );
535
- console.log(
536
- chalk.green(
537
- ` ✅ Minted ${recipient.amount} ${
538
- token.config.symbol
539
- } to ${recipient.wallet.slice(0, 8)}...`,
540
- ),
541
- );
542
- } catch (error) {
543
- console.error(
544
- chalk.red(
545
- ` ❌ Failed to mint to ${recipient.wallet.slice(0, 8)}...:`,
546
- ),
547
- );
548
- console.error(
549
- chalk.red(
550
- ` ${error instanceof Error ? error.message : String(error)}`,
551
- ),
552
- );
553
- }
554
- }
555
- }
556
- }
557
- }
558
-
559
- /**
560
- * Mint initial tokens to the mint authority account
561
- */
562
- private async mintToMintAuthority(
563
- token: ClonedToken,
564
- rpcUrl: string,
565
- debug: boolean = false,
566
- ): Promise<void> {
567
- console.log(
568
- chalk.gray(
569
- ` 🔄 Minting ${token.config.mintAmount} tokens to mint authority...`,
570
- ),
571
- );
572
-
573
- // Check if associated token account already exists
574
- if (debug) {
575
- console.log(
576
- chalk.gray(` 🔍 Checking if token account already exists...`),
577
- );
578
- }
579
-
580
- const checkAccountsResult = await runCommand(
581
- "spl-token",
582
- [
583
- "accounts",
584
- "--owner",
585
- token.mintAuthority.publicKey,
586
- "--url",
587
- rpcUrl,
588
- "--output",
589
- "json",
590
- ],
591
- { silent: true },
592
- );
593
-
594
- let tokenAccountAddress = "";
595
-
596
- if (checkAccountsResult.success && checkAccountsResult.stdout) {
597
- try {
598
- const accountsData = JSON.parse(checkAccountsResult.stdout);
599
-
600
- // Look for existing token account for this mint
601
- for (const account of accountsData.accounts || []) {
602
- if (account.mint === token.config.mainnetMint) {
603
- tokenAccountAddress = account.address;
604
- break;
605
- }
606
- }
607
-
608
- if (tokenAccountAddress) {
609
- if (debug) {
610
- console.log(
611
- chalk.gray(
612
- ` ℹ️ Token account already exists: ${tokenAccountAddress}`,
613
- ),
614
- );
615
- }
616
- }
617
- } catch (_error) {
618
- if (debug) {
619
- console.log(
620
- chalk.gray(` ℹ️ No existing accounts found or parsing error`),
621
- );
622
- }
623
- }
624
- }
625
-
626
- if (!tokenAccountAddress) {
627
- // Account doesn't exist, create it
628
- if (debug) {
629
- console.log(
630
- chalk.gray(` 🔧 Creating token account for mint authority...`),
631
- );
632
- }
633
-
634
- const createAccountResult = await runCommand(
635
- "spl-token",
636
- [
637
- "create-account",
638
- token.config.mainnetMint,
639
- "--owner",
640
- token.mintAuthority.publicKey,
641
- "--fee-payer",
642
- token.mintAuthorityPath,
643
- "--url",
644
- rpcUrl,
645
- ],
646
- { silent: !debug, debug },
647
- );
648
-
649
- if (!createAccountResult.success) {
650
- console.error(chalk.red(` ❌ Failed to create token account:`));
651
- console.error(
652
- chalk.red(
653
- ` Command: spl-token create-account ${token.config.mainnetMint} --owner ${token.mintAuthority.publicKey} --fee-payer ${token.mintAuthorityPath} --url ${rpcUrl}`,
654
- ),
655
- );
656
- console.error(
657
- chalk.red(` Exit code: ${createAccountResult.exitCode}`),
658
- );
659
- console.error(chalk.red(` Stderr: ${createAccountResult.stderr}`));
660
- if (createAccountResult.stdout) {
661
- console.error(
662
- chalk.red(` Stdout: ${createAccountResult.stdout}`),
663
- );
664
- }
665
- throw new Error(
666
- `Failed to create token account: ${createAccountResult.stderr}`,
667
- );
668
- } else {
669
- if (debug) {
670
- console.log(chalk.gray(` ✅ Token account created successfully`));
671
- }
672
- // Extract token account address from create-account output
673
- const match = createAccountResult.stdout.match(
674
- /Creating account (\S+)/,
675
- );
676
- tokenAccountAddress = match?.[1] || "";
677
- }
678
- }
679
-
680
- if (!tokenAccountAddress) {
681
- throw new Error("Failed to determine token account address");
682
- }
683
-
684
- if (debug) {
685
- console.log(chalk.gray(` 📍 Token account: ${tokenAccountAddress}`));
686
- }
687
-
688
- // Mint tokens to the specific token account
689
- const mintResult = await runCommand(
690
- "spl-token",
691
- [
692
- "mint",
693
- token.config.mainnetMint,
694
- token.config.mintAmount.toString(),
695
- tokenAccountAddress,
696
- "--mint-authority",
697
- token.mintAuthorityPath,
698
- "--fee-payer",
699
- token.mintAuthorityPath,
700
- "--url",
701
- rpcUrl,
702
- ],
703
- { silent: !debug, debug },
704
- );
705
-
706
- if (!mintResult.success) {
707
- throw new Error(`Failed to mint tokens: ${mintResult.stderr}`);
708
- }
709
- }
710
-
711
- /**
712
- * Mint tokens to a specific recipient
713
- */
714
- private async mintToRecipient(
715
- token: ClonedToken,
716
- recipientAddress: string,
717
- amount: number,
718
- rpcUrl: string,
719
- ): Promise<void> {
720
- console.log(
721
- chalk.gray(` 🔄 Minting ${amount} tokens to ${recipientAddress}...`),
722
- );
723
-
724
- // Check if associated token account already exists
725
- const checkAccountsResult = await runCommand(
726
- "spl-token",
727
- [
728
- "accounts",
729
- "--owner",
730
- recipientAddress,
731
- "--url",
732
- rpcUrl,
733
- "--output",
734
- "json",
735
- ],
736
- { silent: true },
737
- );
738
-
739
- let tokenAccountAddress = "";
740
-
741
- if (checkAccountsResult.success && checkAccountsResult.stdout) {
742
- try {
743
- const accountsData = JSON.parse(checkAccountsResult.stdout);
744
-
745
- // Look for existing token account for this mint
746
- for (const account of accountsData.accounts || []) {
747
- if (account.mint === token.config.mainnetMint) {
748
- tokenAccountAddress = account.address;
749
- break;
750
- }
751
- }
752
- } catch (_error) {
753
- // No existing accounts found or parsing error, will create new account
754
- }
755
- }
756
-
757
- if (!tokenAccountAddress) {
758
- // Account doesn't exist, create it
759
- const createAccountResult = await runCommand(
760
- "spl-token",
761
- [
762
- "create-account",
763
- token.config.mainnetMint,
764
- "--owner",
765
- recipientAddress,
766
- "--fee-payer",
767
- token.mintAuthorityPath,
768
- "--url",
769
- rpcUrl,
770
- ],
771
- { silent: true },
772
- );
773
-
774
- if (!createAccountResult.success) {
775
- throw new Error(
776
- `Failed to create token account: ${createAccountResult.stderr}`,
777
- );
778
- }
779
-
780
- // Extract token account address from create-account output
781
- const match = createAccountResult.stdout.match(/Creating account (\S+)/);
782
- tokenAccountAddress = match?.[1] || "";
783
- }
784
-
785
- if (!tokenAccountAddress) {
786
- throw new Error("Failed to determine token account address");
787
- }
788
-
789
- // Mint tokens to the specific token account
790
- const mintResult = await runCommand(
791
- "spl-token",
792
- [
793
- "mint",
794
- token.config.mainnetMint,
795
- amount.toString(),
796
- tokenAccountAddress,
797
- "--mint-authority",
798
- token.mintAuthorityPath,
799
- "--fee-payer",
800
- token.mintAuthorityPath,
801
- "--url",
802
- rpcUrl,
803
- ],
804
- { silent: true },
805
- );
806
-
807
- if (!mintResult.success) {
808
- throw new Error(`Failed to mint tokens: ${mintResult.stderr}`);
809
- }
810
- }
811
- }