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,504 +0,0 @@
1
- import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
2
- import { Connection, PublicKey } from "@solana/web3.js";
3
- import chalk from "chalk";
4
- import cors from "cors";
5
- import express from "express";
6
- import { existsSync } from "node:fs";
7
- import type { Server } from "node:http";
8
- import { join } from "node:path";
9
- import { mintTokenToWallet as mintTokenToWalletShared } from "../commands/mint.js";
10
- import type { Config } from "../types/config.js";
11
- import { runCommand } from "../utils/shell.js";
12
- import {
13
- type ClonedToken,
14
- findTokenByMint,
15
- loadClonedTokens,
16
- } from "../utils/token-loader.js";
17
- import { ProgramCloner } from "./program-cloner.js";
18
- import { TokenCloner } from "./token-cloner.js";
19
-
20
- export interface APIServerConfig {
21
- port: number;
22
- host?: string;
23
- validatorRpcUrl: string;
24
- validatorFaucetUrl: string;
25
- config: Config;
26
- workDir: string;
27
- }
28
-
29
- export class APIServer {
30
- private app: express.Application;
31
- private server: Server | null = null;
32
- private config: APIServerConfig;
33
- private connection: Connection;
34
-
35
- constructor(config: APIServerConfig) {
36
- this.config = config;
37
- this.tokenCloner = new TokenCloner(config.workDir);
38
- this.programCloner = new ProgramCloner(config.workDir);
39
- this.connection = new Connection(config.validatorRpcUrl, "confirmed");
40
-
41
- this.app = express();
42
- this.setupMiddleware();
43
- this.setupRoutes();
44
- }
45
-
46
- private setupMiddleware(): void {
47
- this.app.use(cors());
48
- this.app.use(express.json());
49
-
50
- // Request logging
51
- this.app.use((req, _res, next) => {
52
- console.log(chalk.gray(`🌐 API: ${req.method} ${req.path}`));
53
- next();
54
- });
55
- }
56
-
57
- private setupRoutes(): void {
58
- const router = express.Router();
59
-
60
- // Health check
61
- router.get("/health", (_req, res) => {
62
- res.json({ status: "ok", timestamp: new Date().toISOString() });
63
- });
64
-
65
- // Get validator info
66
- router.get("/validator/info", async (_req, res) => {
67
- try {
68
- const version = await this.connection.getVersion();
69
- const blockHeight = await this.connection.getBlockHeight();
70
- const slotLeader = await this.connection.getSlotLeader();
71
-
72
- res.json({
73
- version,
74
- blockHeight,
75
- slotLeader: slotLeader.toString(),
76
- rpcUrl: this.config.validatorRpcUrl,
77
- faucetUrl: this.config.validatorFaucetUrl,
78
- });
79
- } catch (error) {
80
- res.status(500).json({
81
- error: "Failed to fetch validator info",
82
- details: error instanceof Error ? error.message : String(error),
83
- });
84
- }
85
- });
86
-
87
- // Get all cloned tokens
88
- router.get("/tokens", async (_req, res) => {
89
- try {
90
- const clonedTokens = await this.getClonedTokens();
91
- res.json({
92
- tokens: clonedTokens.map((token) => ({
93
- symbol: token.config.symbol,
94
- mainnetMint: token.config.mainnetMint,
95
- mintAuthority: token.mintAuthority.publicKey,
96
- recipients: token.config.recipients,
97
- cloneMetadata: token.config.cloneMetadata,
98
- })),
99
- count: clonedTokens.length,
100
- });
101
- } catch (error) {
102
- res.status(500).json({
103
- error: "Failed to fetch cloned tokens",
104
- details: error instanceof Error ? error.message : String(error),
105
- });
106
- }
107
- });
108
-
109
- // Get all cloned programs
110
- router.get("/programs", async (_req, res) => {
111
- try {
112
- const clonedPrograms = await this.getClonedPrograms();
113
- res.json({
114
- programs: clonedPrograms,
115
- count: clonedPrograms.length,
116
- });
117
- } catch (error) {
118
- res.status(500).json({
119
- error: "Failed to fetch cloned programs",
120
- details: error instanceof Error ? error.message : String(error),
121
- });
122
- }
123
- });
124
-
125
- // Mint tokens to a wallet
126
- router.post("/tokens/:mintAddress/mint", async (req, res) => {
127
- try {
128
- const { mintAddress } = req.params;
129
- const { walletAddress, amount } = req.body;
130
-
131
- if (!walletAddress || !amount) {
132
- return res.status(400).json({
133
- error: "Missing required fields: walletAddress and amount",
134
- });
135
- }
136
-
137
- // Validate mint address
138
- try {
139
- new PublicKey(mintAddress);
140
- } catch {
141
- return res.status(400).json({
142
- error: "Invalid mint address",
143
- });
144
- }
145
-
146
- // Validate wallet address
147
- try {
148
- new PublicKey(walletAddress);
149
- } catch {
150
- return res.status(400).json({
151
- error: "Invalid wallet address",
152
- });
153
- }
154
-
155
- // Validate amount
156
- if (!Number.isInteger(amount) || amount <= 0) {
157
- return res.status(400).json({
158
- error: "Amount must be a positive integer",
159
- });
160
- }
161
-
162
- const result = await this.mintTokenToWallet(
163
- mintAddress,
164
- walletAddress,
165
- amount,
166
- );
167
- res.json(result);
168
- } catch (error) {
169
- res.status(500).json({
170
- error: "Failed to mint tokens",
171
- details: error instanceof Error ? error.message : String(error),
172
- });
173
- }
174
- });
175
-
176
- // Get account balances for a wallet
177
- router.get("/wallet/:address/balances", async (req, res) => {
178
- try {
179
- const { address } = req.params;
180
-
181
- // Validate wallet address
182
- try {
183
- new PublicKey(address);
184
- } catch {
185
- return res.status(400).json({
186
- error: "Invalid wallet address",
187
- });
188
- }
189
-
190
- const balances = await this.getWalletBalances(address);
191
- res.json(balances);
192
- } catch (error) {
193
- res.status(500).json({
194
- error: "Failed to fetch wallet balances",
195
- details: error instanceof Error ? error.message : String(error),
196
- });
197
- }
198
- });
199
-
200
- // Airdrop SOL to a wallet
201
- router.post("/airdrop", async (req, res) => {
202
- try {
203
- const { walletAddress, amount } = req.body;
204
-
205
- if (!walletAddress || !amount) {
206
- return res.status(400).json({
207
- error: "Missing required fields: walletAddress and amount",
208
- });
209
- }
210
-
211
- // Validate wallet address
212
- try {
213
- new PublicKey(walletAddress);
214
- } catch {
215
- return res.status(400).json({
216
- error: "Invalid wallet address",
217
- });
218
- }
219
-
220
- const result = await this.airdropSol(walletAddress, amount);
221
- res.json(result);
222
- } catch (error) {
223
- res.status(500).json({
224
- error: "Failed to airdrop SOL",
225
- details: error instanceof Error ? error.message : String(error),
226
- });
227
- }
228
- });
229
-
230
- // Get recent transactions
231
- router.get("/transactions/recent", async (req, res) => {
232
- try {
233
- const limit = Math.min(
234
- parseInt(req.query.limit as string, 10) || 10,
235
- 100,
236
- );
237
- const signatures = await this.connection.getSignaturesForAddress(
238
- new PublicKey("11111111111111111111111111111111"), // System program
239
- { limit },
240
- );
241
-
242
- res.json({
243
- transactions: signatures,
244
- count: signatures.length,
245
- });
246
- } catch (error) {
247
- res.status(500).json({
248
- error: "Failed to fetch recent transactions",
249
- details: error instanceof Error ? error.message : String(error),
250
- });
251
- }
252
- });
253
-
254
- this.app.use("/api", router);
255
-
256
- // 404 handler
257
- this.app.use("*", (_req, res) => {
258
- res.status(404).json({ error: "Endpoint not found" });
259
- });
260
- }
261
-
262
- private async getClonedTokens(): Promise<ClonedToken[]> {
263
- return await loadClonedTokens(
264
- this.config.config.tokens,
265
- this.config.workDir,
266
- );
267
- }
268
-
269
- private async getClonedPrograms(): Promise<
270
- Array<{ name?: string; programId: string; filePath?: string }>
271
- > {
272
- const clonedPrograms: Array<{
273
- name?: string;
274
- programId: string;
275
- filePath?: string;
276
- }> = [];
277
-
278
- for (const programConfig of this.config.config.programs) {
279
- const programsDir = join(this.config.workDir, "programs");
280
- const fileName = programConfig.name
281
- ? `${programConfig.name.toLowerCase().replace(/\s+/g, "-")}.so`
282
- : `${programConfig.mainnetProgramId}.so`;
283
- const filePath = join(programsDir, fileName);
284
-
285
- clonedPrograms.push({
286
- name: programConfig.name,
287
- programId: programConfig.mainnetProgramId,
288
- filePath: existsSync(filePath) ? filePath : undefined,
289
- });
290
- }
291
-
292
- return clonedPrograms;
293
- }
294
-
295
- private async mintTokenToWallet(
296
- mintAddress: string,
297
- walletAddress: string,
298
- amount: number,
299
- ): Promise<{
300
- success: true;
301
- symbol: string;
302
- amount: number;
303
- walletAddress: string;
304
- mintAddress: string;
305
- }> {
306
- const clonedTokens = await this.getClonedTokens();
307
- const token = findTokenByMint(clonedTokens, mintAddress);
308
-
309
- if (!token) {
310
- throw new Error(`Token ${mintAddress} not found in cloned tokens`);
311
- }
312
-
313
- // Use the shared minting function from the mint command
314
- await mintTokenToWalletShared(
315
- token,
316
- walletAddress,
317
- amount,
318
- this.config.validatorRpcUrl,
319
- );
320
-
321
- return {
322
- success: true,
323
- symbol: token.config.symbol,
324
- amount,
325
- walletAddress,
326
- mintAddress: token.config.mainnetMint,
327
- };
328
- }
329
-
330
- private async getWalletBalances(walletAddress: string): Promise<{
331
- walletAddress: string;
332
- solBalance: { lamports: number; sol: number };
333
- tokenBalances: Array<{
334
- mint: string;
335
- symbol: string;
336
- balance: string;
337
- decimals: number;
338
- uiAmount: number | null;
339
- }>;
340
- timestamp: string;
341
- }> {
342
- try {
343
- const publicKey = new PublicKey(walletAddress);
344
-
345
- // Get SOL balance
346
- const solBalance = await this.connection.getBalance(publicKey);
347
-
348
- // Get token accounts
349
- const tokenAccounts = await this.connection.getTokenAccountsByOwner(
350
- publicKey,
351
- {
352
- programId: TOKEN_PROGRAM_ID,
353
- },
354
- );
355
-
356
- const tokenBalances = [];
357
- const clonedTokens = await this.getClonedTokens();
358
-
359
- for (const tokenAccount of tokenAccounts.value) {
360
- try {
361
- const accountInfo = await this.connection.getAccountInfo(
362
- tokenAccount.pubkey,
363
- );
364
- if (accountInfo) {
365
- // Parse token account data (simplified)
366
- const data = accountInfo.data;
367
- if (data.length >= 32) {
368
- const mintBytes = data.slice(0, 32);
369
- const mintAddress = new PublicKey(mintBytes).toBase58();
370
-
371
- // Find matching cloned token
372
- const clonedToken = clonedTokens.find(
373
- (t) => t.config.mainnetMint === mintAddress,
374
- );
375
-
376
- if (clonedToken) {
377
- // Get token balance
378
- const balance = await this.connection.getTokenAccountBalance(
379
- tokenAccount.pubkey,
380
- );
381
- tokenBalances.push({
382
- mint: mintAddress,
383
- symbol: clonedToken.config.symbol,
384
- balance: balance.value.amount,
385
- decimals: balance.value.decimals,
386
- uiAmount: balance.value.uiAmount,
387
- });
388
- }
389
- }
390
- }
391
- } catch (_error) {}
392
- }
393
-
394
- return {
395
- walletAddress,
396
- solBalance: {
397
- lamports: solBalance,
398
- sol: solBalance / 1e9,
399
- },
400
- tokenBalances,
401
- timestamp: new Date().toISOString(),
402
- };
403
- } catch (error) {
404
- throw new Error(
405
- `Failed to get wallet balances: ${
406
- error instanceof Error ? error.message : String(error)
407
- }`,
408
- );
409
- }
410
- }
411
-
412
- private async airdropSol(
413
- walletAddress: string,
414
- amount: number,
415
- ): Promise<{
416
- success: true;
417
- amount: number;
418
- walletAddress: string;
419
- signature: string;
420
- }> {
421
- const result = await runCommand(
422
- "solana",
423
- [
424
- "airdrop",
425
- amount.toString(),
426
- walletAddress,
427
- "--url",
428
- this.config.validatorRpcUrl,
429
- ],
430
- { silent: false, debug: false },
431
- );
432
-
433
- if (!result.success) {
434
- throw new Error(`Failed to airdrop SOL: ${result.stderr}`);
435
- }
436
-
437
- return {
438
- success: true,
439
- amount,
440
- walletAddress,
441
- signature:
442
- result.stdout.match(/Signature: ([A-Za-z0-9]+)/)?.[1] || "unknown",
443
- };
444
- }
445
-
446
- async start(): Promise<{ success: boolean; error?: string }> {
447
- return new Promise((resolve) => {
448
- try {
449
- const host = this.config.host || "127.0.0.1";
450
- this.server = this.app.listen(this.config.port, host, () => {
451
- console.log(
452
- chalk.green(
453
- `🚀 API Server started on http://${host}:${this.config.port}`,
454
- ),
455
- );
456
- console.log(
457
- chalk.gray(
458
- ` 📋 Endpoints available at http://${host}:${this.config.port}/api`,
459
- ),
460
- );
461
- resolve({ success: true });
462
- });
463
-
464
- this.server.on("error", (error) => {
465
- console.error(
466
- chalk.red(`❌ API Server failed to start: ${error.message}`),
467
- );
468
- resolve({ success: false, error: error.message });
469
- });
470
- } catch (error) {
471
- resolve({
472
- success: false,
473
- error: error instanceof Error ? error.message : String(error),
474
- });
475
- }
476
- });
477
- }
478
-
479
- async stop(): Promise<{ success: boolean; error?: string }> {
480
- return new Promise((resolve) => {
481
- if (!this.server) {
482
- resolve({ success: true });
483
- return;
484
- }
485
-
486
- this.server.close((error) => {
487
- if (error) {
488
- resolve({
489
- success: false,
490
- error: error instanceof Error ? error.message : String(error),
491
- });
492
- } else {
493
- console.log(chalk.yellow("🛑 API Server stopped"));
494
- resolve({ success: true });
495
- }
496
- this.server = null;
497
- });
498
- });
499
- }
500
-
501
- isRunning(): boolean {
502
- return this.server?.listening;
503
- }
504
- }
@@ -1,174 +0,0 @@
1
- import { processRegistry } from "./process-registry.js";
2
-
3
- export interface PortAllocation {
4
- rpcPort: number;
5
- faucetPort: number;
6
- }
7
-
8
- export class PortManager {
9
- private readonly defaultRpcPort = 8899;
10
- private readonly portRangeStart = 8000;
11
- private readonly portRangeEnd = 9999;
12
-
13
- /**
14
- * Get the next available port pair (RPC + Faucet)
15
- */
16
- async getAvailablePorts(preferredRpcPort?: number): Promise<PortAllocation> {
17
- const usedPorts = this.getUsedPorts();
18
-
19
- // If preferred port is specified and available, use it
20
- if (preferredRpcPort && !this.isPortUsed(preferredRpcPort, usedPorts)) {
21
- const faucetPort = this.findAvailableFaucetPort(
22
- preferredRpcPort,
23
- usedPorts,
24
- );
25
- if (faucetPort) {
26
- return { rpcPort: preferredRpcPort, faucetPort };
27
- }
28
- }
29
-
30
- // Otherwise, find the next available ports
31
- return this.findNextAvailablePorts(usedPorts);
32
- }
33
-
34
- /**
35
- * Check if a specific port is available
36
- */
37
- async isPortAvailable(port: number): Promise<boolean> {
38
- const usedPorts = this.getUsedPorts();
39
- return (
40
- !this.isPortUsed(port, usedPorts) &&
41
- (await this.checkPortActuallyFree(port))
42
- );
43
- }
44
-
45
- /**
46
- * Get all currently used ports from running validators
47
- */
48
- private getUsedPorts(): Set<number> {
49
- const validators = processRegistry.getRunning();
50
- const usedPorts = new Set<number>();
51
-
52
- validators.forEach((validator) => {
53
- usedPorts.add(validator.rpcPort);
54
- usedPorts.add(validator.faucetPort);
55
- });
56
-
57
- return usedPorts;
58
- }
59
-
60
- /**
61
- * Check if a port is in the used ports set
62
- */
63
- private isPortUsed(port: number, usedPorts: Set<number>): boolean {
64
- return usedPorts.has(port);
65
- }
66
-
67
- /**
68
- * Find an available faucet port for a given RPC port
69
- */
70
- private findAvailableFaucetPort(
71
- rpcPort: number,
72
- usedPorts: Set<number>,
73
- ): number | null {
74
- // Try default offset first (faucet = rpc + 1001)
75
- let faucetPort = rpcPort + 1001;
76
- if (
77
- !this.isPortUsed(faucetPort, usedPorts) &&
78
- this.isPortInRange(faucetPort)
79
- ) {
80
- return faucetPort;
81
- }
82
-
83
- // Try other offsets
84
- const offsets = [1000, 1002, 1003, 1004, 1005, 999, 998, 997];
85
- for (const offset of offsets) {
86
- faucetPort = rpcPort + offset;
87
- if (
88
- !this.isPortUsed(faucetPort, usedPorts) &&
89
- this.isPortInRange(faucetPort)
90
- ) {
91
- return faucetPort;
92
- }
93
- }
94
-
95
- // Search in the entire range
96
- for (let port = this.portRangeStart; port <= this.portRangeEnd; port++) {
97
- if (!this.isPortUsed(port, usedPorts)) {
98
- return port;
99
- }
100
- }
101
-
102
- return null;
103
- }
104
-
105
- /**
106
- * Find the next available port pair
107
- */
108
- private findNextAvailablePorts(usedPorts: Set<number>): PortAllocation {
109
- // Start from default ports if available
110
- if (!this.isPortUsed(this.defaultRpcPort, usedPorts)) {
111
- const faucetPort = this.findAvailableFaucetPort(
112
- this.defaultRpcPort,
113
- usedPorts,
114
- );
115
- if (faucetPort) {
116
- return { rpcPort: this.defaultRpcPort, faucetPort };
117
- }
118
- }
119
-
120
- // Search for available RPC port
121
- for (
122
- let rpcPort = this.portRangeStart;
123
- rpcPort <= this.portRangeEnd;
124
- rpcPort++
125
- ) {
126
- if (!this.isPortUsed(rpcPort, usedPorts)) {
127
- const faucetPort = this.findAvailableFaucetPort(rpcPort, usedPorts);
128
- if (faucetPort) {
129
- return { rpcPort, faucetPort };
130
- }
131
- }
132
- }
133
-
134
- throw new Error("No available port pairs found in the specified range");
135
- }
136
-
137
- /**
138
- * Check if port is within allowed range
139
- */
140
- private isPortInRange(port: number): boolean {
141
- return port >= this.portRangeStart && port <= this.portRangeEnd;
142
- }
143
-
144
- /**
145
- * Actually check if a port is free by attempting to bind to it
146
- */
147
- private async checkPortActuallyFree(port: number): Promise<boolean> {
148
- return new Promise((resolve) => {
149
- const net = require("node:net");
150
- const server = net.createServer();
151
-
152
- server.once("listening", () => {
153
- server.once("close", () => resolve(true));
154
- server.close();
155
- });
156
-
157
- server.once("error", () => resolve(false));
158
-
159
- server.listen(port);
160
- });
161
- }
162
-
163
- /**
164
- * Get recommended ports for a configuration
165
- */
166
- async getRecommendedPorts(config: {
167
- localnet: { port: number; faucetPort: number };
168
- }): Promise<PortAllocation> {
169
- return this.getAvailablePorts(config.localnet.port);
170
- }
171
- }
172
-
173
- // Singleton instance
174
- export const portManager = new PortManager();