@lightprotocol/zk-compression-cli 0.28.0-beta.1 → 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,24 +109,50 @@ 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, upgradeablePrograms, 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
- upgradeablePrograms,
111
- skipSystemAccounts,
112
- limitLedgerSize,
113
- rpcPort,
114
- gossipHost,
115
- validatorArgs,
116
- geyserConfig,
117
- cloneNetwork,
118
- verbose,
119
- skipReset,
120
- });
121
- await (0, process_1.waitForServers)([{ port: rpcPort, path: "/health" }]);
122
- await (0, process_1.confirmServerStability)(`http://127.0.0.1:${rpcPort}/health`);
123
- 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
+ }
124
156
  if (prover) {
125
157
  const config = (0, utils_1.getConfig)();
126
158
  config.proverUrl = `http://127.0.0.1:${proverPort}`;
@@ -140,7 +172,56 @@ async function initTestEnv({ additionalPrograms, upgradeablePrograms, skipSystem
140
172
  const proverUrlForIndexer = prover
141
173
  ? `http://127.0.0.1:${proverPort}`
142
174
  : undefined;
143
- 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);
183
+ }
184
+ if (forester) {
185
+ if (!indexer || !prover) {
186
+ throw new Error("Forester requires both indexer and prover to be running");
187
+ }
188
+ try {
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
+ });
199
+ }
200
+ catch (error) {
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);
223
+ throw error;
224
+ }
144
225
  }
145
226
  }
146
227
  async function initTestEnvIfNeeded({ additionalPrograms, skipSystemAccounts, indexer = false, prover = false, geyserConfig, validatorArgs, } = {}) {
@@ -270,36 +351,172 @@ async function getSolanaArgs({ additionalPrograms, upgradeablePrograms, skipSyst
270
351
  }
271
352
  return solanaArgs;
272
353
  }
273
- async function startTestValidator({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, validatorArgs, geyserConfig, cloneNetwork, verbose, skipReset, }) {
274
- const command = "solana-test-validator";
275
- const solanaArgs = await getSolanaArgs({
276
- additionalPrograms,
277
- upgradeablePrograms,
278
- skipSystemAccounts,
279
- limitLedgerSize,
280
- rpcPort,
281
- gossipHost,
282
- cloneNetwork,
283
- verbose,
284
- skipReset,
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,
285
446
  });
286
- await killTestValidator();
287
- await new Promise((r) => setTimeout(r, 1000));
288
- // Add geyser config if provided
289
- if (geyserConfig) {
290
- solanaArgs.push("--geyser-plugin-config", geyserConfig);
291
- }
292
- // Add custom validator args last
293
- if (validatorArgs) {
294
- solanaArgs.push(...validatorArgs.split(" "));
295
- }
296
- console.log("Starting test validator...");
297
- // Use spawnBinary instead of executeCommand to properly detach the process.
298
- // This ensures the validator survives when the CLI exits (executeCommand uses
299
- // piped stdio which causes SIGPIPE when parent exits).
300
- // Pass process.env directly to maintain same env behavior as before.
301
- (0, process_1.spawnBinary)(command, solanaArgs, process.env);
302
447
  }
303
- 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) {
304
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
+ }
305
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";
@@ -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) {
@@ -185,11 +183,28 @@ function spawnBinary(command, args = [], 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!");