solforge 0.1.6 → 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 (194) 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 -336
  182. package/src/commands/start.ts +0 -878
  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 -532
  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
package/src/index.ts DELETED
@@ -1,188 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- // Suppress bigint-buffer warning
4
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
5
- process.stderr.write = function(chunk: any, encoding?: any, callback?: any) {
6
- if (typeof chunk === 'string' && chunk.includes('bigint: Failed to load bindings')) {
7
- return true; // Suppress this specific warning
8
- }
9
- return originalStderrWrite(chunk, encoding, callback);
10
- };
11
-
12
- import { Command } from "commander";
13
- import chalk from "chalk";
14
- import { existsSync } from "fs";
15
- import { resolve } from "path";
16
- import { initCommand } from "./commands/init.js";
17
- import { statusCommand } from "./commands/status.js";
18
- import { startCommand } from "./commands/start.js";
19
- import { mintCommand } from "./commands/mint.js";
20
- import { listCommand } from "./commands/list.js";
21
- import { stopCommand, killCommand } from "./commands/stop.js";
22
- import { addProgramCommand } from "./commands/add-program.js";
23
- import packageJson from "../package.json" with { type: "json" };
24
-
25
- const program = new Command();
26
-
27
- program
28
- .name("solforge")
29
- .description("Solana localnet orchestration tool")
30
- .version(packageJson.version);
31
-
32
- // Check for sf.config.json in current directory
33
- function findConfig(): string | null {
34
- const configPath = resolve(process.cwd(), "sf.config.json");
35
- return existsSync(configPath) ? configPath : null;
36
- }
37
-
38
- program
39
- .command("init")
40
- .description("Initialize a new sf.config.json in current directory")
41
- .action(async () => {
42
- console.log(chalk.blue("🚀 Initializing SolForge configuration..."));
43
- await initCommand();
44
- });
45
-
46
- program
47
- .command("start")
48
- .description("Start localnet with current sf.config.json")
49
- .option("--debug", "Enable debug logging to see commands and detailed output")
50
- .option("--network", "Make API server accessible over network (binds to 0.0.0.0 instead of 127.0.0.1)")
51
- .action(async (options) => {
52
- const configPath = findConfig();
53
- if (!configPath) {
54
- console.error(
55
- chalk.red("❌ No sf.config.json found in current directory")
56
- );
57
- console.log(chalk.yellow("💡 Run `solforge init` to create one"));
58
- process.exit(1);
59
- }
60
-
61
- await startCommand(options.debug || false, options.network || false);
62
- });
63
-
64
- program
65
- .command("list")
66
- .description("List all running validators")
67
- .action(async () => {
68
- await listCommand();
69
- });
70
-
71
- program
72
- .command("stop")
73
- .description("Stop running validator(s)")
74
- .argument("[validator-id]", "ID of validator to stop")
75
- .option("--all", "Stop all running validators")
76
- .option("--kill", "Force kill the validator (SIGKILL instead of SIGTERM)")
77
- .action(async (validatorId, options) => {
78
- await stopCommand(validatorId, options);
79
- });
80
-
81
- program
82
- .command("kill")
83
- .description("Force kill running validator(s)")
84
- .argument("[validator-id]", "ID of validator to kill")
85
- .option("--all", "Kill all running validators")
86
- .action(async (validatorId, options) => {
87
- await killCommand(validatorId, options);
88
- });
89
-
90
- program
91
- .command("api-server")
92
- .description("Start API server standalone")
93
- .option("-p, --port <port>", "Port for API server", "3000")
94
- .option("--host <host>", "Host to bind to (default: 127.0.0.1, use 0.0.0.0 for network access)")
95
- .option("--rpc-url <url>", "Validator RPC URL", "http://127.0.0.1:8899")
96
- .option("--faucet-url <url>", "Validator faucet URL", "http://127.0.0.1:9900")
97
- .option("--work-dir <dir>", "Work directory", "./.solforge")
98
- .action(async (options) => {
99
- const configPath = findConfig();
100
- if (!configPath) {
101
- console.error(
102
- chalk.red("❌ No sf.config.json found in current directory")
103
- );
104
- console.log(chalk.yellow("💡 Run `solforge init` to create one"));
105
- process.exit(1);
106
- }
107
-
108
- // Import API server components
109
- const { APIServer } = await import("./services/api-server.js");
110
- const { configManager } = await import("./config/manager.js");
111
-
112
- try {
113
- await configManager.load(configPath);
114
- const config = configManager.getConfig();
115
-
116
- const apiServer = new APIServer({
117
- port: parseInt(options.port),
118
- host: options.host,
119
- validatorRpcUrl: options.rpcUrl,
120
- validatorFaucetUrl: options.faucetUrl,
121
- config,
122
- workDir: options.workDir,
123
- });
124
-
125
- const result = await apiServer.start();
126
- if (result.success) {
127
- console.log(chalk.green("✅ API Server started successfully!"));
128
-
129
- // Keep the process alive
130
- process.on("SIGTERM", async () => {
131
- console.log(chalk.yellow("📡 API Server received SIGTERM, shutting down..."));
132
- await apiServer.stop();
133
- process.exit(0);
134
- });
135
-
136
- process.on("SIGINT", async () => {
137
- console.log(chalk.yellow("📡 API Server received SIGINT, shutting down..."));
138
- await apiServer.stop();
139
- process.exit(0);
140
- });
141
-
142
- // Keep process alive
143
- setInterval(() => {}, 1000);
144
- } else {
145
- console.error(chalk.red(`❌ Failed to start API server: ${result.error}`));
146
- process.exit(1);
147
- }
148
- } catch (error) {
149
- console.error(
150
- chalk.red(
151
- `❌ API Server error: ${
152
- error instanceof Error ? error.message : String(error)
153
- }`
154
- )
155
- );
156
- process.exit(1);
157
- }
158
- });
159
-
160
- program
161
- .command("add-program")
162
- .description("Add a program to sf.config.json")
163
-
164
- .option("--program-id <address>", "Mainnet program ID to clone and deploy")
165
- .option("--name <name>", "Friendly name for the program")
166
- .option("--no-interactive", "Run in non-interactive mode")
167
- .action(async (options) => {
168
- await addProgramCommand(options);
169
- });
170
-
171
- program
172
- .command("status")
173
- .description("Show localnet status")
174
- .action(async () => {
175
- await statusCommand();
176
- });
177
-
178
- program.addCommand(mintCommand);
179
-
180
- program
181
- .command("reset")
182
- .description("Reset localnet ledger")
183
- .action(async () => {
184
- console.log(chalk.blue("🔄 Resetting localnet..."));
185
- // TODO: Implement reset
186
- });
187
-
188
- program.parse();
@@ -1,532 +0,0 @@
1
- import express from "express";
2
- import cors from "cors";
3
- import { Server } from "http";
4
- import { spawn, ChildProcess } from "child_process";
5
- import { existsSync, readFileSync } from "fs";
6
- import { join } from "path";
7
- import chalk from "chalk";
8
- import { Connection, PublicKey, Keypair } from "@solana/web3.js";
9
- import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
10
- import { runCommand } from "../utils/shell.js";
11
- import { TokenCloner } from "./token-cloner.js";
12
- import { ProgramCloner } from "./program-cloner.js";
13
- import { mintTokenToWallet as mintTokenToWalletShared } from "../commands/mint.js";
14
- import type { Config } from "../types/config.js";
15
- import type { ClonedToken } from "./token-cloner.js";
16
-
17
- export interface APIServerConfig {
18
- port: number;
19
- host?: string;
20
- validatorRpcUrl: string;
21
- validatorFaucetUrl: string;
22
- config: Config;
23
- workDir: string;
24
- }
25
-
26
- export class APIServer {
27
- private app: express.Application;
28
- private server: Server | null = null;
29
- private config: APIServerConfig;
30
- private tokenCloner: TokenCloner;
31
- private programCloner: ProgramCloner;
32
- private connection: Connection;
33
-
34
- constructor(config: APIServerConfig) {
35
- this.config = config;
36
- this.tokenCloner = new TokenCloner(config.workDir);
37
- this.programCloner = new ProgramCloner(config.workDir);
38
- this.connection = new Connection(config.validatorRpcUrl, "confirmed");
39
-
40
- this.app = express();
41
- this.setupMiddleware();
42
- this.setupRoutes();
43
- }
44
-
45
- private setupMiddleware(): void {
46
- this.app.use(cors());
47
- this.app.use(express.json());
48
-
49
- // Request logging
50
- this.app.use((req, res, next) => {
51
- console.log(chalk.gray(`🌐 API: ${req.method} ${req.path}`));
52
- next();
53
- });
54
- }
55
-
56
- private setupRoutes(): void {
57
- const router = express.Router();
58
-
59
- // Health check
60
- router.get("/health", (req, res) => {
61
- res.json({ status: "ok", timestamp: new Date().toISOString() });
62
- });
63
-
64
- // Get validator info
65
- router.get("/validator/info", async (req, res) => {
66
- try {
67
- const version = await this.connection.getVersion();
68
- const blockHeight = await this.connection.getBlockHeight();
69
- const slotLeader = await this.connection.getSlotLeader();
70
-
71
- res.json({
72
- version,
73
- blockHeight,
74
- slotLeader: slotLeader.toString(),
75
- rpcUrl: this.config.validatorRpcUrl,
76
- faucetUrl: this.config.validatorFaucetUrl,
77
- });
78
- } catch (error) {
79
- res.status(500).json({
80
- error: "Failed to fetch validator info",
81
- details: error instanceof Error ? error.message : String(error),
82
- });
83
- }
84
- });
85
-
86
- // Get all cloned tokens
87
- router.get("/tokens", async (req, res) => {
88
- try {
89
- const clonedTokens = await this.getClonedTokens();
90
- res.json({
91
- tokens: clonedTokens.map((token) => ({
92
- symbol: token.config.symbol,
93
- mainnetMint: token.config.mainnetMint,
94
- mintAuthority: token.mintAuthority.publicKey,
95
- recipients: token.config.recipients,
96
- cloneMetadata: token.config.cloneMetadata,
97
- })),
98
- count: clonedTokens.length,
99
- });
100
- } catch (error) {
101
- res.status(500).json({
102
- error: "Failed to fetch cloned tokens",
103
- details: error instanceof Error ? error.message : String(error),
104
- });
105
- }
106
- });
107
-
108
- // Get all cloned programs
109
- router.get("/programs", async (req, res) => {
110
- try {
111
- const clonedPrograms = await this.getClonedPrograms();
112
- res.json({
113
- programs: clonedPrograms,
114
- count: clonedPrograms.length,
115
- });
116
- } catch (error) {
117
- res.status(500).json({
118
- error: "Failed to fetch cloned programs",
119
- details: error instanceof Error ? error.message : String(error),
120
- });
121
- }
122
- });
123
-
124
- // Mint tokens to a wallet
125
- router.post("/tokens/:mintAddress/mint", async (req, res) => {
126
- try {
127
- const { mintAddress } = req.params;
128
- const { walletAddress, amount } = req.body;
129
-
130
- if (!walletAddress || !amount) {
131
- return res.status(400).json({
132
- error: "Missing required fields: walletAddress and amount",
133
- });
134
- }
135
-
136
- // Validate mint address
137
- try {
138
- new PublicKey(mintAddress);
139
- } catch {
140
- return res.status(400).json({
141
- error: "Invalid mint address",
142
- });
143
- }
144
-
145
- // Validate wallet address
146
- try {
147
- new PublicKey(walletAddress);
148
- } catch {
149
- return res.status(400).json({
150
- error: "Invalid wallet address",
151
- });
152
- }
153
-
154
- // Validate amount
155
- if (!Number.isInteger(amount) || amount <= 0) {
156
- return res.status(400).json({
157
- error: "Amount must be a positive integer",
158
- });
159
- }
160
-
161
- const result = await this.mintTokenToWallet(
162
- mintAddress,
163
- walletAddress,
164
- amount
165
- );
166
- res.json(result);
167
- } catch (error) {
168
- res.status(500).json({
169
- error: "Failed to mint tokens",
170
- details: error instanceof Error ? error.message : String(error),
171
- });
172
- }
173
- });
174
-
175
- // Get account balances for a wallet
176
- router.get("/wallet/:address/balances", async (req, res) => {
177
- try {
178
- const { address } = req.params;
179
-
180
- // Validate wallet address
181
- try {
182
- new PublicKey(address);
183
- } catch {
184
- return res.status(400).json({
185
- error: "Invalid wallet address",
186
- });
187
- }
188
-
189
- const balances = await this.getWalletBalances(address);
190
- res.json(balances);
191
- } catch (error) {
192
- res.status(500).json({
193
- error: "Failed to fetch wallet balances",
194
- details: error instanceof Error ? error.message : String(error),
195
- });
196
- }
197
- });
198
-
199
- // Airdrop SOL to a wallet
200
- router.post("/airdrop", async (req, res) => {
201
- try {
202
- const { walletAddress, amount } = req.body;
203
-
204
- if (!walletAddress || !amount) {
205
- return res.status(400).json({
206
- error: "Missing required fields: walletAddress and amount",
207
- });
208
- }
209
-
210
- // Validate wallet address
211
- try {
212
- new PublicKey(walletAddress);
213
- } catch {
214
- return res.status(400).json({
215
- error: "Invalid wallet address",
216
- });
217
- }
218
-
219
- const result = await this.airdropSol(walletAddress, amount);
220
- res.json(result);
221
- } catch (error) {
222
- res.status(500).json({
223
- error: "Failed to airdrop SOL",
224
- details: error instanceof Error ? error.message : String(error),
225
- });
226
- }
227
- });
228
-
229
- // Get recent transactions
230
- router.get("/transactions/recent", async (req, res) => {
231
- try {
232
- const limit = Math.min(parseInt(req.query.limit as string) || 10, 100);
233
- const signatures = await this.connection.getSignaturesForAddress(
234
- new PublicKey("11111111111111111111111111111111"), // System program
235
- { limit }
236
- );
237
-
238
- res.json({
239
- transactions: signatures,
240
- count: signatures.length,
241
- });
242
- } catch (error) {
243
- res.status(500).json({
244
- error: "Failed to fetch recent transactions",
245
- details: error instanceof Error ? error.message : String(error),
246
- });
247
- }
248
- });
249
-
250
- this.app.use("/api", router);
251
-
252
- // 404 handler
253
- this.app.use("*", (req, res) => {
254
- res.status(404).json({ error: "Endpoint not found" });
255
- });
256
- }
257
-
258
- 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;
310
- }
311
-
312
- private async getClonedPrograms(): Promise<
313
- Array<{ name?: string; programId: string; filePath?: string }>
314
- > {
315
- const clonedPrograms: Array<{
316
- name?: string;
317
- programId: string;
318
- filePath?: string;
319
- }> = [];
320
-
321
- for (const programConfig of this.config.config.programs) {
322
- const programsDir = join(this.config.workDir, "programs");
323
- const fileName = programConfig.name
324
- ? `${programConfig.name.toLowerCase().replace(/\s+/g, "-")}.so`
325
- : `${programConfig.mainnetProgramId}.so`;
326
- const filePath = join(programsDir, fileName);
327
-
328
- clonedPrograms.push({
329
- name: programConfig.name,
330
- programId: programConfig.mainnetProgramId,
331
- filePath: existsSync(filePath) ? filePath : undefined,
332
- });
333
- }
334
-
335
- return clonedPrograms;
336
- }
337
-
338
- private async mintTokenToWallet(
339
- mintAddress: string,
340
- walletAddress: string,
341
- amount: number
342
- ): Promise<any> {
343
- const clonedTokens = await this.getClonedTokens();
344
- const token = clonedTokens.find(
345
- (t) => t.config.mainnetMint === mintAddress
346
- );
347
-
348
- if (!token) {
349
- throw new Error(
350
- `Token with mint address ${mintAddress} not found in cloned tokens`
351
- );
352
- }
353
-
354
- // Use the shared minting function from the mint command
355
- await mintTokenToWalletShared(
356
- token,
357
- walletAddress,
358
- amount,
359
- this.config.validatorRpcUrl
360
- );
361
-
362
- return {
363
- success: true,
364
- symbol: token.config.symbol,
365
- amount,
366
- walletAddress,
367
- mintAddress: token.config.mainnetMint,
368
- };
369
- }
370
-
371
- private async getWalletBalances(walletAddress: string): Promise<any> {
372
- try {
373
- const publicKey = new PublicKey(walletAddress);
374
-
375
- // Get SOL balance
376
- const solBalance = await this.connection.getBalance(publicKey);
377
-
378
- // Get token accounts
379
- const tokenAccounts = await this.connection.getTokenAccountsByOwner(
380
- publicKey,
381
- {
382
- programId: TOKEN_PROGRAM_ID,
383
- }
384
- );
385
-
386
- const tokenBalances = [];
387
- const clonedTokens = await this.getClonedTokens();
388
-
389
- for (const tokenAccount of tokenAccounts.value) {
390
- try {
391
- const accountInfo = await this.connection.getAccountInfo(
392
- tokenAccount.pubkey
393
- );
394
- if (accountInfo) {
395
- // Parse token account data (simplified)
396
- const data = accountInfo.data;
397
- if (data.length >= 32) {
398
- const mintBytes = data.slice(0, 32);
399
- const mintAddress = new PublicKey(mintBytes).toBase58();
400
-
401
- // Find matching cloned token
402
- const clonedToken = clonedTokens.find(
403
- (t) => t.config.mainnetMint === mintAddress
404
- );
405
-
406
- if (clonedToken) {
407
- // Get token balance
408
- const balance = await this.connection.getTokenAccountBalance(
409
- tokenAccount.pubkey
410
- );
411
- tokenBalances.push({
412
- mint: mintAddress,
413
- symbol: clonedToken.config.symbol,
414
- balance: balance.value.amount,
415
- decimals: balance.value.decimals,
416
- uiAmount: balance.value.uiAmount,
417
- });
418
- }
419
- }
420
- }
421
- } catch (error) {
422
- // Skip failed token accounts
423
- continue;
424
- }
425
- }
426
-
427
- return {
428
- walletAddress,
429
- solBalance: {
430
- lamports: solBalance,
431
- sol: solBalance / 1e9,
432
- },
433
- tokenBalances,
434
- timestamp: new Date().toISOString(),
435
- };
436
- } catch (error) {
437
- throw new Error(
438
- `Failed to get wallet balances: ${
439
- error instanceof Error ? error.message : String(error)
440
- }`
441
- );
442
- }
443
- }
444
-
445
- private async airdropSol(
446
- walletAddress: string,
447
- amount: number
448
- ): Promise<any> {
449
- const result = await runCommand(
450
- "solana",
451
- [
452
- "airdrop",
453
- amount.toString(),
454
- walletAddress,
455
- "--url",
456
- this.config.validatorRpcUrl,
457
- ],
458
- { silent: false, debug: false }
459
- );
460
-
461
- if (!result.success) {
462
- throw new Error(`Failed to airdrop SOL: ${result.stderr}`);
463
- }
464
-
465
- return {
466
- success: true,
467
- amount,
468
- walletAddress,
469
- signature:
470
- result.stdout.match(/Signature: ([A-Za-z0-9]+)/)?.[1] || "unknown",
471
- };
472
- }
473
-
474
- async start(): Promise<{ success: boolean; error?: string }> {
475
- return new Promise((resolve) => {
476
- try {
477
- const host = this.config.host || "127.0.0.1";
478
- this.server = this.app.listen(this.config.port, host, () => {
479
- console.log(
480
- chalk.green(
481
- `🚀 API Server started on http://${host}:${this.config.port}`
482
- )
483
- );
484
- console.log(
485
- chalk.gray(
486
- ` 📋 Endpoints available at http://${host}:${this.config.port}/api`
487
- )
488
- );
489
- resolve({ success: true });
490
- });
491
-
492
- this.server.on("error", (error) => {
493
- console.error(
494
- chalk.red(`❌ API Server failed to start: ${error.message}`)
495
- );
496
- resolve({ success: false, error: error.message });
497
- });
498
- } catch (error) {
499
- resolve({
500
- success: false,
501
- error: error instanceof Error ? error.message : String(error),
502
- });
503
- }
504
- });
505
- }
506
-
507
- async stop(): Promise<{ success: boolean; error?: string }> {
508
- return new Promise((resolve) => {
509
- if (!this.server) {
510
- resolve({ success: true });
511
- return;
512
- }
513
-
514
- this.server.close((error) => {
515
- if (error) {
516
- resolve({
517
- success: false,
518
- error: error instanceof Error ? error.message : String(error),
519
- });
520
- } else {
521
- console.log(chalk.yellow("🛑 API Server stopped"));
522
- resolve({ success: true });
523
- }
524
- this.server = null;
525
- });
526
- });
527
- }
528
-
529
- isRunning(): boolean {
530
- return this.server !== null && this.server.listening;
531
- }
532
- }