@lightprotocol/zk-compression-cli 0.27.1-alpha.9 → 0.28.0-beta.10

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.
@@ -7,17 +7,22 @@ exports.initTestEnvIfNeeded = initTestEnvIfNeeded;
7
7
  exports.programsDirPath = programsDirPath;
8
8
  exports.programFilePath = programFilePath;
9
9
  exports.getSolanaArgs = getSolanaArgs;
10
+ exports.getSurfpoolArgs = getSurfpoolArgs;
10
11
  exports.startTestValidator = startTestValidator;
11
12
  exports.killTestValidator = killTestValidator;
12
13
  const tslib_1 = require("tslib");
13
14
  const utils_1 = require("./utils");
14
15
  const constants_1 = require("./constants");
16
+ const fs_1 = tslib_1.__importDefault(require("fs"));
15
17
  const path_1 = tslib_1.__importDefault(require("path"));
18
+ const os_1 = tslib_1.__importDefault(require("os"));
16
19
  const psp_utils_1 = require("../psp-utils");
17
20
  const process_1 = require("./process");
18
21
  const processProverServer_1 = require("./processProverServer");
19
22
  const processPhotonIndexer_1 = require("./processPhotonIndexer");
23
+ const processForester_1 = require("./processForester");
20
24
  const web3_js_1 = require("@solana/web3.js");
25
+ const child_process_1 = require("child_process");
21
26
  exports.SYSTEM_PROGRAMS = [
22
27
  {
23
28
  id: "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV",
@@ -79,6 +84,7 @@ async function getProgramOwnedAccounts(programId, rpcUrl) {
79
84
  }
80
85
  async function stopTestEnv(options) {
81
86
  const processesToKill = [
87
+ { name: "forester", condition: options.forester ?? false, killFunction: processForester_1.killForester },
82
88
  { name: "photon", condition: options.indexer, killFunction: processPhotonIndexer_1.killIndexer },
83
89
  { name: "prover", condition: options.prover, killFunction: processProverServer_1.killProver },
84
90
  {
@@ -103,23 +109,62 @@ async function stopTestEnv(options) {
103
109
  await Promise.all(killPromises);
104
110
  console.log("All specified processes and validator stopped.");
105
111
  }
106
- async function initTestEnv({ additionalPrograms, skipSystemAccounts, indexer = true, prover = true, rpcPort = 8899, indexerPort = 8784, proverPort = 3001, gossipHost = "127.0.0.1", checkPhotonVersion = true, photonDatabaseUrl, limitLedgerSize, geyserConfig, validatorArgs, cloneNetwork, verbose, skipReset, }) {
107
- // We cannot await this promise directly because it will hang the process
108
- startTestValidator({
109
- additionalPrograms,
110
- skipSystemAccounts,
111
- limitLedgerSize,
112
- rpcPort,
113
- gossipHost,
114
- validatorArgs,
115
- geyserConfig,
116
- cloneNetwork,
117
- verbose,
118
- skipReset,
119
- });
120
- await (0, process_1.waitForServers)([{ port: rpcPort, path: "/health" }]);
121
- await (0, process_1.confirmServerStability)(`http://127.0.0.1:${rpcPort}/health`);
122
- await (0, process_1.confirmRpcReadiness)(`http://127.0.0.1:${rpcPort}`);
112
+ async function initTestEnv({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, indexer = true, prover = true, forester = false, rpcPort = 8899, indexerPort = 8784, proverPort = 3001, foresterPort = 8080, gossipHost = "127.0.0.1", checkPhotonVersion = true, photonDatabaseUrl, limitLedgerSize, geyserConfig, validatorArgs, cloneNetwork, verbose, skipReset, useSurfpool, lightPdaPrograms, additionalAccountDirs, }) {
113
+ if (useSurfpool) {
114
+ // For surfpool we can await startTestValidator because spawnBinary returns
115
+ // immediately (surfpool starts in ~30ms). For solana-test-validator we must
116
+ // NOT await because the validator is a long-running process.
117
+ await startTestValidator({
118
+ additionalPrograms,
119
+ upgradeablePrograms,
120
+ skipSystemAccounts,
121
+ limitLedgerSize,
122
+ rpcPort,
123
+ gossipHost,
124
+ validatorArgs,
125
+ geyserConfig,
126
+ cloneNetwork,
127
+ verbose,
128
+ skipReset,
129
+ useSurfpool,
130
+ additionalAccountDirs,
131
+ });
132
+ // Surfpool only supports JSON-RPC POST, not GET /health.
133
+ await (0, process_1.confirmRpcReadiness)(`http://127.0.0.1:${rpcPort}`);
134
+ }
135
+ else {
136
+ // We cannot await this promise directly because it will hang the process
137
+ startTestValidator({
138
+ additionalPrograms,
139
+ upgradeablePrograms,
140
+ skipSystemAccounts,
141
+ limitLedgerSize,
142
+ rpcPort,
143
+ gossipHost,
144
+ validatorArgs,
145
+ geyserConfig,
146
+ cloneNetwork,
147
+ verbose,
148
+ skipReset,
149
+ useSurfpool,
150
+ additionalAccountDirs,
151
+ });
152
+ await (0, process_1.waitForServers)([{ port: rpcPort, path: "/health" }]);
153
+ await (0, process_1.confirmServerStability)(`http://127.0.0.1:${rpcPort}/health`);
154
+ await (0, process_1.confirmRpcReadiness)(`http://127.0.0.1:${rpcPort}`);
155
+ }
156
+ if (prover) {
157
+ const config = (0, utils_1.getConfig)();
158
+ config.proverUrl = `http://127.0.0.1:${proverPort}`;
159
+ (0, utils_1.setConfig)(config);
160
+ try {
161
+ await (0, processProverServer_1.startProver)(proverPort);
162
+ }
163
+ catch (error) {
164
+ console.error("Failed to start prover:", error);
165
+ throw error;
166
+ }
167
+ }
123
168
  if (indexer) {
124
169
  const config = (0, utils_1.getConfig)();
125
170
  config.indexerUrl = `http://127.0.0.1:${indexerPort}`;
@@ -127,19 +172,54 @@ async function initTestEnv({ additionalPrograms, skipSystemAccounts, indexer = t
127
172
  const proverUrlForIndexer = prover
128
173
  ? `http://127.0.0.1:${proverPort}`
129
174
  : undefined;
130
- await (0, processPhotonIndexer_1.startIndexer)(`http://127.0.0.1:${rpcPort}`, indexerPort, checkPhotonVersion, photonDatabaseUrl, proverUrlForIndexer);
175
+ // Surfpool's first available block may not be slot 0.
176
+ // Query the RPC so Photon starts from the correct slot.
177
+ let startSlot;
178
+ if (useSurfpool) {
179
+ const conn = new web3_js_1.Connection(`http://127.0.0.1:${rpcPort}`);
180
+ startSlot = await conn.getFirstAvailableBlock();
181
+ }
182
+ await (0, processPhotonIndexer_1.startIndexer)(`http://127.0.0.1:${rpcPort}`, indexerPort, checkPhotonVersion, photonDatabaseUrl, proverUrlForIndexer, startSlot);
131
183
  }
132
- if (prover) {
133
- const config = (0, utils_1.getConfig)();
134
- config.proverUrl = `http://127.0.0.1:${proverPort}`;
135
- (0, utils_1.setConfig)(config);
184
+ if (forester) {
185
+ if (!indexer || !prover) {
186
+ throw new Error("Forester requires both indexer and prover to be running");
187
+ }
136
188
  try {
137
- // TODO: check if using redisUrl is better here.
138
- await (0, processProverServer_1.startProver)(proverPort);
189
+ const payer = (0, processForester_1.getPayerForForester)();
190
+ await (0, processForester_1.startForester)({
191
+ rpcUrl: `http://127.0.0.1:${rpcPort}`,
192
+ wsRpcUrl: `ws://127.0.0.1:${rpcPort + 1}`,
193
+ indexerUrl: `http://127.0.0.1:${indexerPort}`,
194
+ proverUrl: `http://127.0.0.1:${proverPort}`,
195
+ payer,
196
+ foresterPort,
197
+ lightPdaPrograms,
198
+ });
139
199
  }
140
200
  catch (error) {
141
- console.error("Failed to start prover:", error);
142
- // Prover logs will be automatically displayed by spawnBinary in process.ts
201
+ console.error("Failed to start forester:", error);
202
+ throw error;
203
+ }
204
+ }
205
+ if (forester) {
206
+ if (!indexer || !prover) {
207
+ throw new Error("Forester requires both indexer and prover to be running");
208
+ }
209
+ try {
210
+ const payer = (0, processForester_1.getPayerForForester)();
211
+ await (0, processForester_1.startForester)({
212
+ rpcUrl: `http://127.0.0.1:${rpcPort}`,
213
+ wsRpcUrl: `ws://127.0.0.1:${rpcPort + 1}`,
214
+ indexerUrl: `http://127.0.0.1:${indexerPort}`,
215
+ proverUrl: `http://127.0.0.1:${proverPort}`,
216
+ payer,
217
+ foresterPort,
218
+ lightPdaPrograms,
219
+ });
220
+ }
221
+ catch (error) {
222
+ console.error("Failed to start forester:", error);
143
223
  throw error;
144
224
  }
145
225
  }
@@ -194,12 +274,12 @@ function programFilePath(programName) {
194
274
  }
195
275
  return path_1.default.resolve(__dirname, path_1.default.join(constants_1.BASE_PATH, programName));
196
276
  }
197
- async function getSolanaArgs({ additionalPrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, downloadBinaries = true, cloneNetwork, verbose = false, skipReset = false, }) {
277
+ async function getSolanaArgs({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, downloadBinaries = true, cloneNetwork, verbose = false, skipReset = false, }) {
198
278
  const dirPath = programsDirPath();
199
279
  const solanaArgs = [
200
280
  `--limit-ledger-size=${limitLedgerSize}`,
201
281
  `--rpc-port=${rpcPort}`,
202
- `--gossip-host=${gossipHost}`,
282
+ `--bind-address=${gossipHost}`,
203
283
  "--quiet",
204
284
  ];
205
285
  if (!skipReset) {
@@ -257,6 +337,12 @@ async function getSolanaArgs({ additionalPrograms, skipSystemAccounts, limitLedg
257
337
  solanaArgs.push("--bpf-program", program.address, program.path);
258
338
  }
259
339
  }
340
+ // Add upgradeable programs (with upgrade authority)
341
+ if (upgradeablePrograms) {
342
+ for (const program of upgradeablePrograms) {
343
+ solanaArgs.push("--upgradeable-program", program.address, program.path, program.upgradeAuthority);
344
+ }
345
+ }
260
346
  // Load local system accounts only if not cloning from network
261
347
  if (!skipSystemAccounts && !cloneNetwork) {
262
348
  const accountsRelPath = "../../accounts";
@@ -265,34 +351,172 @@ async function getSolanaArgs({ additionalPrograms, skipSystemAccounts, limitLedg
265
351
  }
266
352
  return solanaArgs;
267
353
  }
268
- async function startTestValidator({ additionalPrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, validatorArgs, geyserConfig, cloneNetwork, verbose, skipReset, }) {
269
- const command = "solana-test-validator";
270
- const solanaArgs = await getSolanaArgs({
271
- additionalPrograms,
272
- skipSystemAccounts,
273
- limitLedgerSize,
274
- rpcPort,
275
- gossipHost,
276
- cloneNetwork,
277
- verbose,
278
- skipReset,
279
- });
280
- await killTestValidator();
281
- await new Promise((r) => setTimeout(r, 1000));
282
- // Add geyser config if provided
283
- if (geyserConfig) {
284
- solanaArgs.push("--geyser-plugin-config", geyserConfig);
285
- }
286
- // Add custom validator args last
287
- if (validatorArgs) {
288
- solanaArgs.push(...validatorArgs.split(" "));
289
- }
290
- console.log("Starting test validator...");
291
- await (0, process_1.executeCommand)({
292
- command,
293
- args: [...solanaArgs],
354
+ async function getSurfpoolArgs({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, rpcPort, gossipHost, downloadBinaries = true, additionalAccountDirs, }) {
355
+ const dirPath = programsDirPath();
356
+ const args = ["start", "--offline", "--no-tui", "--no-deploy", "--no-studio"];
357
+ args.push("--port", String(rpcPort));
358
+ args.push("--host", String(gossipHost));
359
+ // Load system programs
360
+ for (const program of exports.SYSTEM_PROGRAMS) {
361
+ const localFilePath = programFilePath(program.name);
362
+ if (program.name === "spl_noop.so" || downloadBinaries) {
363
+ await (0, psp_utils_1.downloadBinIfNotExists)({
364
+ localFilePath,
365
+ dirPath,
366
+ owner: "Lightprotocol",
367
+ repoName: "light-protocol",
368
+ remoteFileName: program.name,
369
+ tag: program.tag,
370
+ });
371
+ }
372
+ args.push("--bpf-program", program.id, localFilePath);
373
+ }
374
+ // Load additional programs
375
+ if (additionalPrograms) {
376
+ for (const program of additionalPrograms) {
377
+ args.push("--bpf-program", program.address, program.path);
378
+ }
379
+ }
380
+ // Load upgradeable programs with full BPF upgradeable loader account layout
381
+ if (upgradeablePrograms) {
382
+ for (const program of upgradeablePrograms) {
383
+ args.push("--upgradeable-program", program.address, program.path, program.upgradeAuthority);
384
+ }
385
+ }
386
+ // Load system accounts
387
+ if (!skipSystemAccounts) {
388
+ const accountsRelPath = "../../accounts";
389
+ const accountsPath = path_1.default.resolve(__dirname, accountsRelPath);
390
+ args.push("--account-dir", accountsPath);
391
+ }
392
+ // Load additional account directories
393
+ if (additionalAccountDirs) {
394
+ for (const accountDir of additionalAccountDirs) {
395
+ args.push("--account-dir", path_1.default.resolve(accountDir));
396
+ }
397
+ }
398
+ return args;
399
+ }
400
+ function getSurfpoolAssetName() {
401
+ const platform = process.platform; // "darwin" | "linux"
402
+ const arch = process.arch; // "arm64" | "x64"
403
+ return `surfpool-${platform}-${arch}.tar.gz`;
404
+ }
405
+ function getSurfpoolBinDir() {
406
+ return path_1.default.join(os_1.default.homedir(), ".config", "light", "bin");
407
+ }
408
+ function getSurfpoolBinaryPath() {
409
+ return path_1.default.join(getSurfpoolBinDir(), "surfpool");
410
+ }
411
+ function getInstalledSurfpoolVersion() {
412
+ const binaryPath = getSurfpoolBinaryPath();
413
+ if (!fs_1.default.existsSync(binaryPath)) {
414
+ return null;
415
+ }
416
+ try {
417
+ const output = (0, child_process_1.execSync)(`"${binaryPath}" --version`, {
418
+ encoding: "utf-8",
419
+ timeout: 5000,
420
+ }).trim();
421
+ const match = output.match(/(\d+\.\d+\.\d+)/);
422
+ return match ? match[1] : null;
423
+ }
424
+ catch {
425
+ return null;
426
+ }
427
+ }
428
+ async function downloadSurfpoolBinary() {
429
+ const binPath = getSurfpoolBinaryPath();
430
+ const dirPath = getSurfpoolBinDir();
431
+ const assetName = getSurfpoolAssetName();
432
+ if (!fs_1.default.existsSync(dirPath)) {
433
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
434
+ }
435
+ // Remove existing binary so downloadBinIfNotExists actually downloads
436
+ if (fs_1.default.existsSync(binPath)) {
437
+ fs_1.default.unlinkSync(binPath);
438
+ }
439
+ await (0, psp_utils_1.downloadBinIfNotExists)({
440
+ localFilePath: binPath,
441
+ dirPath,
442
+ owner: "Lightprotocol",
443
+ repoName: "surfpool",
444
+ remoteFileName: assetName,
445
+ tag: constants_1.SURFPOOL_RELEASE_TAG,
294
446
  });
295
447
  }
296
- async function killTestValidator() {
448
+ async function ensureSurfpoolBinary() {
449
+ const binPath = getSurfpoolBinaryPath();
450
+ const installedVersion = getInstalledSurfpoolVersion();
451
+ if (installedVersion === constants_1.SURFPOOL_VERSION) {
452
+ return binPath;
453
+ }
454
+ if (installedVersion) {
455
+ console.log(`Surfpool version mismatch. Expected: ${constants_1.SURFPOOL_VERSION}, Found: ${installedVersion}. Downloading correct version...`);
456
+ }
457
+ else if (fs_1.default.existsSync(binPath)) {
458
+ console.log("Surfpool binary found but version could not be determined. Downloading latest version...");
459
+ }
460
+ await downloadSurfpoolBinary();
461
+ return binPath;
462
+ }
463
+ async function startTestValidator({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, validatorArgs, geyserConfig, cloneNetwork, verbose, skipReset, useSurfpool, additionalAccountDirs, }) {
464
+ if (useSurfpool) {
465
+ const command = await ensureSurfpoolBinary();
466
+ const surfpoolArgs = await getSurfpoolArgs({
467
+ additionalPrograms,
468
+ upgradeablePrograms,
469
+ skipSystemAccounts,
470
+ rpcPort,
471
+ gossipHost,
472
+ additionalAccountDirs,
473
+ });
474
+ await killTestValidator(rpcPort);
475
+ await new Promise((r) => setTimeout(r, 1000));
476
+ console.log("Starting surfpool...");
477
+ (0, process_1.spawnBinary)(command, surfpoolArgs, process.env);
478
+ }
479
+ else {
480
+ const command = "solana-test-validator";
481
+ const solanaArgs = await getSolanaArgs({
482
+ additionalPrograms,
483
+ upgradeablePrograms,
484
+ skipSystemAccounts,
485
+ limitLedgerSize,
486
+ rpcPort,
487
+ gossipHost,
488
+ cloneNetwork,
489
+ verbose,
490
+ skipReset,
491
+ });
492
+ await killTestValidator(rpcPort);
493
+ await new Promise((r) => setTimeout(r, 1000));
494
+ // Add geyser config if provided
495
+ if (geyserConfig) {
496
+ solanaArgs.push("--geyser-plugin-config", geyserConfig);
497
+ }
498
+ // Add custom validator args last
499
+ if (validatorArgs) {
500
+ solanaArgs.push(...validatorArgs.split(" "));
501
+ }
502
+ console.log("Starting test validator...");
503
+ // Use spawnBinary instead of executeCommand to properly detach the process.
504
+ // This ensures the validator survives when the CLI exits (executeCommand uses
505
+ // piped stdio which causes SIGPIPE when parent exits).
506
+ // Pass process.env directly to maintain same env behavior as before.
507
+ (0, process_1.spawnBinary)(command, solanaArgs, process.env);
508
+ }
509
+ }
510
+ async function killTestValidator(rpcPort = 8899) {
297
511
  await (0, process_1.killProcess)("solana-test-validator");
512
+ await (0, process_1.killProcess)("surfpool");
513
+ // Fallback: kill anything still listening on the RPC port.
514
+ // find-process name matching can miss processes depending on platform
515
+ // and how the binary path appears in the process table.
516
+ try {
517
+ await (0, process_1.killProcessByPort)(rpcPort);
518
+ }
519
+ catch {
520
+ // No process listening on the port, nothing to do.
521
+ }
298
522
  }
@@ -0,0 +1,3 @@
1
+ export declare const PHOTON_VERSION = "0.51.2";
2
+ export declare const PHOTON_GIT_COMMIT = "7a649f9c45a138ef47b090445163abe84775145c";
3
+ export declare const PHOTON_GIT_REPO = "https://github.com/lightprotocol/photon.git";
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PHOTON_GIT_REPO = exports.PHOTON_GIT_COMMIT = exports.PHOTON_VERSION = void 0;
4
+ // Auto-generated from external/photon submodule - do not edit manually
5
+ exports.PHOTON_VERSION = "0.51.2";
6
+ exports.PHOTON_GIT_COMMIT = "7a649f9c45a138ef47b090445163abe84775145c";
7
+ exports.PHOTON_GIT_REPO = "https://github.com/lightprotocol/photon.git";
@@ -27,7 +27,7 @@ export declare function executeCommand({ command, args, additionalPath, logFile,
27
27
  * @example const output = await execute("ls -alh");
28
28
  */
29
29
  export declare function execute(command: string): Promise<string>;
30
- export declare function spawnBinary(command: string, args?: string[]): import("child_process").ChildProcess;
30
+ export declare function spawnBinary(command: string, args?: string[], env?: NodeJS.ProcessEnv): import("child_process").ChildProcess;
31
31
  export declare function waitForServers(servers: {
32
32
  port: number;
33
33
  path: string;
@@ -55,19 +55,17 @@ async function logProverFileContents() {
55
55
  }
56
56
  async function killProcess(processName) {
57
57
  const processList = await (0, find_process_1.default)("name", processName);
58
- const targetProcesses = processList.filter((proc) => proc.name.includes(processName) || proc.cmd.includes(processName));
58
+ const targetProcesses = processList.filter((proc) => proc.pid !== process.pid &&
59
+ proc.pid !== process.ppid &&
60
+ (proc.name.includes(processName) || proc.cmd.includes(processName)));
59
61
  for (const proc of targetProcesses) {
60
62
  try {
61
63
  process.kill(proc.pid, "SIGKILL");
62
64
  }
63
65
  catch (error) {
64
- console.error(`Failed to kill process ${proc.pid}: ${error}`);
66
+ // Process may have already exited between find and kill.
65
67
  }
66
68
  }
67
- const remainingProcesses = await (0, find_process_1.default)("name", processName);
68
- if (remainingProcesses.length > 0) {
69
- console.warn(`Warning: ${remainingProcesses.length} processes still running after kill attempt`);
70
- }
71
69
  }
72
70
  async function killProcessByPort(port) {
73
71
  if (port < 0) {
@@ -165,7 +163,7 @@ async function execute(command) {
165
163
  throw err;
166
164
  }
167
165
  }
168
- function spawnBinary(command, args = []) {
166
+ function spawnBinary(command, args = [], env) {
169
167
  const logDir = "test-ledger";
170
168
  const binaryName = path_1.default.basename(command);
171
169
  const dir = path_1.default.join(process.cwd(), logDir);
@@ -180,16 +178,33 @@ function spawnBinary(command, args = []) {
180
178
  stdio: ["ignore", out, err],
181
179
  shell: false,
182
180
  detached: true,
183
- env: {
181
+ env: env ?? {
184
182
  ...process.env,
185
183
  RUST_LOG: process.env.RUST_LOG || "debug",
186
184
  },
187
185
  });
186
+ // Close parent's copy of the file descriptors so the child owns them
187
+ // exclusively and node's event loop isn't held open.
188
+ fs_1.default.closeSync(out);
189
+ fs_1.default.closeSync(err);
190
+ // Allow node to exit without waiting for the detached child.
191
+ spawnedProcess.unref();
188
192
  spawnedProcess.on("close", async (code) => {
189
193
  console.log(`${binaryName} process exited with code ${code}`);
190
- if (code !== 0 && binaryName.includes("prover")) {
191
- console.error(`Prover process failed with exit code ${code}`);
192
- await logProverFileContents();
194
+ if (code !== 0) {
195
+ console.error(`${binaryName} process failed with exit code ${code}`);
196
+ try {
197
+ const contents = fs_1.default.readFileSync(logPath, "utf8");
198
+ console.error(`--- ${binaryName}.log ---`);
199
+ console.error(contents);
200
+ console.error(`--- End of ${binaryName}.log ---`);
201
+ }
202
+ catch {
203
+ // log file may not exist yet
204
+ }
205
+ if (binaryName.includes("prover")) {
206
+ await logProverFileContents();
207
+ }
193
208
  }
194
209
  });
195
210
  return spawnedProcess;
@@ -0,0 +1,23 @@
1
+ export interface ForesterConfig {
2
+ rpcUrl: string;
3
+ wsRpcUrl: string;
4
+ indexerUrl: string;
5
+ proverUrl: string;
6
+ payer: string;
7
+ foresterPort: number;
8
+ lightPdaPrograms?: string[];
9
+ }
10
+ /**
11
+ * Starts the forester service for auto-compression of compressible accounts.
12
+ *
13
+ * @param config - Forester configuration
14
+ */
15
+ export declare function startForester(config: ForesterConfig): Promise<undefined>;
16
+ export declare function killForester(): Promise<void>;
17
+ /**
18
+ * Gets the payer keypair as a JSON array string for forester.
19
+ * Reads from ~/.config/solana/id.json or SOLANA_PAYER environment variable.
20
+ *
21
+ * @returns JSON array string of the keypair bytes
22
+ */
23
+ export declare function getPayerForForester(): string;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startForester = startForester;
4
+ exports.killForester = killForester;
5
+ exports.getPayerForForester = getPayerForForester;
6
+ const tslib_1 = require("tslib");
7
+ const which_1 = tslib_1.__importDefault(require("which"));
8
+ const process_1 = require("./process");
9
+ const constants_1 = require("./constants");
10
+ const node_child_process_1 = require("node:child_process");
11
+ const util = tslib_1.__importStar(require("node:util"));
12
+ const node_process_1 = require("node:process");
13
+ const fs = tslib_1.__importStar(require("fs"));
14
+ const path = tslib_1.__importStar(require("path"));
15
+ const execAsync = util.promisify(node_child_process_1.exec);
16
+ async function isForesterInstalled() {
17
+ try {
18
+ const resolvedOrNull = which_1.default.sync("forester", { nothrow: true });
19
+ return resolvedOrNull !== null;
20
+ }
21
+ catch (error) {
22
+ return false;
23
+ }
24
+ }
25
+ function getForesterInstallMessage() {
26
+ return `\nForester not found. Please install it by running: "cargo install --git https://github.com/Lightprotocol/light-protocol forester --locked --force"`;
27
+ }
28
+ /**
29
+ * Starts the forester service for auto-compression of compressible accounts.
30
+ *
31
+ * @param config - Forester configuration
32
+ */
33
+ async function startForester(config) {
34
+ await killForester();
35
+ if (!(await isForesterInstalled())) {
36
+ console.log(getForesterInstallMessage());
37
+ return (0, node_process_1.exit)(1);
38
+ }
39
+ console.log("Starting forester...");
40
+ const args = [
41
+ "start",
42
+ "--rpc-url",
43
+ config.rpcUrl,
44
+ "--ws-rpc-url",
45
+ config.wsRpcUrl,
46
+ "--indexer-url",
47
+ config.indexerUrl,
48
+ "--prover-url",
49
+ config.proverUrl,
50
+ "--payer",
51
+ config.payer,
52
+ "--api-server-port",
53
+ config.foresterPort.toString(),
54
+ "--enable-compressible",
55
+ ];
56
+ // Add light PDA programs if specified
57
+ if (config.lightPdaPrograms && config.lightPdaPrograms.length > 0) {
58
+ for (const program of config.lightPdaPrograms) {
59
+ args.push("--light-pda-program", program);
60
+ }
61
+ }
62
+ (0, process_1.spawnBinary)(constants_1.FORESTER_PROCESS_NAME, args);
63
+ await (0, process_1.waitForServers)([{ port: config.foresterPort, path: "/health" }]);
64
+ console.log("Forester started successfully!");
65
+ }
66
+ async function killForester() {
67
+ await (0, process_1.killProcess)(constants_1.FORESTER_PROCESS_NAME);
68
+ }
69
+ /**
70
+ * Gets the payer keypair as a JSON array string for forester.
71
+ * Reads from ~/.config/solana/id.json or SOLANA_PAYER environment variable.
72
+ *
73
+ * @returns JSON array string of the keypair bytes
74
+ */
75
+ function getPayerForForester() {
76
+ // Check for SOLANA_PAYER environment variable first
77
+ if (process.env.SOLANA_PAYER) {
78
+ return process.env.SOLANA_PAYER;
79
+ }
80
+ // Default to standard Solana keypair location
81
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
82
+ const keypairPath = path.join(homeDir, ".config", "solana", "id.json");
83
+ if (fs.existsSync(keypairPath)) {
84
+ const keypairData = fs.readFileSync(keypairPath, "utf-8");
85
+ return keypairData.trim();
86
+ }
87
+ throw new Error("No payer keypair found. Set SOLANA_PAYER environment variable or create ~/.config/solana/id.json");
88
+ }
@@ -1,2 +1,2 @@
1
- export declare function startIndexer(rpcUrl: string, indexerPort: number, checkPhotonVersion?: boolean, photonDatabaseUrl?: string, proverUrl?: string): Promise<undefined>;
1
+ export declare function startIndexer(rpcUrl: string, indexerPort: number, checkPhotonVersion?: boolean, photonDatabaseUrl?: string, proverUrl?: string, startSlot?: number): Promise<undefined>;
2
2
  export declare function killIndexer(): Promise<void>;
@@ -6,6 +6,7 @@ const tslib_1 = require("tslib");
6
6
  const which_1 = tslib_1.__importDefault(require("which"));
7
7
  const process_1 = require("./process");
8
8
  const constants_1 = require("./constants");
9
+ const photonVersion_generated_1 = require("./photonVersion.generated");
9
10
  const node_child_process_1 = require("node:child_process");
10
11
  const util = tslib_1.__importStar(require("node:util"));
11
12
  const node_process_1 = require("node:process");
@@ -22,21 +23,13 @@ async function isExpectedPhotonVersion(requiredVersion) {
22
23
  }
23
24
  }
24
25
  function getPhotonInstallMessage() {
25
- if (constants_1.USE_PHOTON_FROM_GIT && constants_1.PHOTON_GIT_COMMIT) {
26
- return `\nPhoton indexer ${constants_1.PHOTON_VERSION} (commit ${constants_1.PHOTON_GIT_COMMIT}) not found. Please install it by running: "cargo install --git ${constants_1.PHOTON_GIT_REPO} --rev ${constants_1.PHOTON_GIT_COMMIT} --locked --force"`;
27
- }
28
- else if (constants_1.USE_PHOTON_FROM_GIT) {
29
- return `\nPhoton indexer ${constants_1.PHOTON_VERSION} not found. Please install it by running: "cargo install --git ${constants_1.PHOTON_GIT_REPO} --locked --force"`;
30
- }
31
- else {
32
- return `\nPhoton indexer ${constants_1.PHOTON_VERSION} not found. Please install it by running: "cargo install photon-indexer --version ${constants_1.PHOTON_VERSION} --locked --force"`;
33
- }
26
+ return `\nPhoton indexer ${photonVersion_generated_1.PHOTON_VERSION} (commit ${photonVersion_generated_1.PHOTON_GIT_COMMIT}) not found. Please install it by running: "cargo install --git ${photonVersion_generated_1.PHOTON_GIT_REPO} --rev ${photonVersion_generated_1.PHOTON_GIT_COMMIT} --locked --force"`;
34
27
  }
35
- async function startIndexer(rpcUrl, indexerPort, checkPhotonVersion = true, photonDatabaseUrl, proverUrl) {
28
+ async function startIndexer(rpcUrl, indexerPort, checkPhotonVersion = true, photonDatabaseUrl, proverUrl, startSlot) {
36
29
  await killIndexer();
37
30
  const resolvedOrNull = which_1.default.sync("photon", { nothrow: true });
38
31
  if (resolvedOrNull === null ||
39
- (checkPhotonVersion && !(await isExpectedPhotonVersion(constants_1.PHOTON_VERSION)))) {
32
+ (checkPhotonVersion && !(await isExpectedPhotonVersion(photonVersion_generated_1.PHOTON_VERSION)))) {
40
33
  console.log(getPhotonInstallMessage());
41
34
  return (0, node_process_1.exit)(1);
42
35
  }
@@ -54,6 +47,9 @@ async function startIndexer(rpcUrl, indexerPort, checkPhotonVersion = true, phot
54
47
  if (proverUrl) {
55
48
  args.push("--prover-url", proverUrl);
56
49
  }
50
+ if (startSlot !== undefined) {
51
+ args.push("--start-slot", startSlot.toString());
52
+ }
57
53
  (0, process_1.spawnBinary)(constants_1.INDEXER_PROCESS_NAME, args);
58
54
  await (0, process_1.waitForServers)([{ port: indexerPort, path: "/getIndexerHealth" }]);
59
55
  console.log("Indexer started successfully!");
@@ -84,6 +84,7 @@ async function startProver(proverPort, redisUrl) {
84
84
  }
85
85
  (0, process_1.spawnBinary)(getProverPathByArch(), args);
86
86
  await (0, process_1.waitForServers)([{ port: proverPort, path: "/" }]);
87
+ await new Promise((r) => setTimeout(r, 5000));
87
88
  console.log(`Prover started successfully!`);
88
89
  }
89
90
  function getProverNameByArch() {