Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/js-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
attempt=1
max_attempts=2
cd js/compressed-token
until LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:ctoken:all; do
until LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:ctoken; do
attempt=$((attempt + 1))
if [ $attempt -gt $max_attempts ]; then
echo "Tests failed after $max_attempts attempts"
Expand Down
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightprotocol/zk-compression-cli",
"version": "0.27.1-alpha.2",
"version": "0.27.1-alpha.5",
"description": "ZK Compression: Secure Scaling on Solana",
"maintainers": [
{
Expand Down
2 changes: 1 addition & 1 deletion cli/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const PHOTON_VERSION = "0.51.2";
// Set these to override Photon requirements with a specific git commit:
export const USE_PHOTON_FROM_GIT = true; // If true, will show git install command instead of crates.io.
export const PHOTON_GIT_REPO = "https://github.com/lightprotocol/photon.git";
export const PHOTON_GIT_COMMIT = "711c47b20330c6bb78feb0a2c15e8292fcd0a7b0"; // If empty, will use main branch.
export const PHOTON_GIT_COMMIT = "ac7df6c388db847b7693a7a1cb766a7c9d7809b5"; // If empty, will use main branch.
export const LIGHT_PROTOCOL_PROGRAMS_DIR_ENV = "LIGHT_PROTOCOL_PROGRAMS_DIR";
export const BASE_PATH = "../../bin/";

Expand Down
7 changes: 4 additions & 3 deletions cli/src/utils/downloadProverBinary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import https from "https";
import http from "http";
import { pipeline } from "stream/promises";

const PROVER_VERSION = "2.0.6";
const GITHUB_RELEASES_BASE_URL = `https://github.com/Lightprotocol/light-protocol/releases/download/light-prover-v${PROVER_VERSION}`;
const PROVER_RELEASE_TAG = "2.0.6";
const PROVER_BINARY_VERSION = "2.0.0"; // Version string the binary actually reports
const GITHUB_RELEASES_BASE_URL = `https://github.com/Lightprotocol/light-protocol/releases/download/light-prover-v${PROVER_RELEASE_TAG}`;
const MAX_REDIRECTS = 10;

interface DownloadOptions {
Expand Down Expand Up @@ -151,5 +152,5 @@ async function downloadFile(
}

export function getProverVersion(): string {
return PROVER_VERSION;
return PROVER_BINARY_VERSION;
}
6 changes: 6 additions & 0 deletions cli/src/utils/initTestEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import path from "path";
import { downloadBinIfNotExists } from "../psp-utils";
import {
confirmRpcReadiness,
confirmServerStability,
executeCommand,
killProcess,
Expand Down Expand Up @@ -172,16 +173,21 @@ export async function initTestEnv({
});
await waitForServers([{ port: rpcPort, path: "/health" }]);
await confirmServerStability(`http://127.0.0.1:${rpcPort}/health`);
await confirmRpcReadiness(`http://127.0.0.1:${rpcPort}`);

if (indexer) {
const config = getConfig();
config.indexerUrl = `http://127.0.0.1:${indexerPort}`;
setConfig(config);
const proverUrlForIndexer = prover
? `http://127.0.0.1:${proverPort}`
: undefined;
await startIndexer(
`http://127.0.0.1:${rpcPort}`,
indexerPort,
checkPhotonVersion,
photonDatabaseUrl,
proverUrlForIndexer,
);
}

Expand Down
70 changes: 70 additions & 0 deletions cli/src/utils/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,73 @@ export async function confirmServerStability(
throw error;
}
}

/**
* Confirms that the Solana RPC is fully ready to process requests.
* This goes beyond HTTP availability and verifies the RPC can handle actual Solana requests.
*
* @param rpcUrl - The RPC endpoint URL
* @param maxAttempts - Maximum number of attempts (default: 30)
* @param delayMs - Delay between attempts in milliseconds (default: 500ms)
* @throws Error if RPC doesn't become ready within maxAttempts
*/
export async function confirmRpcReadiness(
rpcUrl: string,
maxAttempts: number = 30,
delayMs: number = 500,
) {
let lastError: Error | unknown;

for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const response = await axios.post(
rpcUrl,
{
jsonrpc: "2.0",
id: 1,
method: "getHealth",
params: [],
},
{
headers: { "Content-Type": "application/json" },
timeout: 3000,
},
);

if (response.data?.result === "ok") {
console.log(
`RPC is ready after ${attempt} attempt${attempt > 1 ? "s" : ""}.`,
);
return;
}

// Response received but not "ok"
lastError = new Error(
`RPC returned unexpected result: ${JSON.stringify(response.data)}`,
);
} catch (error) {
lastError = error;

// Log connection errors only on later attempts to reduce noise
if (attempt > 5 && attempt % 5 === 0) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(
`RPC not ready yet (attempt ${attempt}/${maxAttempts}): ${errorMsg}`,
);
}
}

// Don't sleep after the last attempt
if (attempt < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}

// If we get here, all attempts failed
const errorMsg =
lastError instanceof Error ? lastError.message : String(lastError);
const totalTime = Math.round((maxAttempts * delayMs) / 1000);
throw new Error(
`RPC failed to become ready after ${maxAttempts} attempts (~${totalTime}s). Last error: ${errorMsg}`,
);
}
4 changes: 4 additions & 0 deletions cli/src/utils/processPhotonIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export async function startIndexer(
indexerPort: number,
checkPhotonVersion: boolean = true,
photonDatabaseUrl?: string,
proverUrl?: string,
) {
await killIndexer();
const resolvedOrNull = which.sync("photon", { nothrow: true });
Expand All @@ -61,6 +62,9 @@ export async function startIndexer(
if (photonDatabaseUrl) {
args.push("--db-url", photonDatabaseUrl);
}
if (proverUrl) {
args.push("--prover-url", proverUrl);
}

spawnBinary(INDEXER_PROCESS_NAME, args);
await waitForServers([{ port: indexerPort, path: "/getIndexerHealth" }]);
Expand Down
23 changes: 12 additions & 11 deletions cli/src/utils/processProverServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from "path";
import os from "os";
import fs from "fs";
import { execSync } from "child_process";
import {
Expand All @@ -7,13 +8,15 @@ import {
spawnBinary,
waitForServers,
} from "./process";
import { LIGHT_PROVER_PROCESS_NAME, BASE_PATH } from "./constants";
import { LIGHT_PROVER_PROCESS_NAME } from "./constants";
import {
downloadProverBinary,
getProverVersion as getExpectedProverVersion,
} from "./downloadProverBinary";

const KEYS_DIR = "proving-keys/";
const LIGHT_CONFIG_DIR = path.join(os.homedir(), ".config", "light");
const PROVER_BIN_DIR = path.join(LIGHT_CONFIG_DIR, "bin");
const KEYS_DIR = path.join(LIGHT_CONFIG_DIR, "proving-keys");

export async function killProver() {
await killProcess(getProverNameByArch());
Expand All @@ -32,11 +35,13 @@ function getInstalledProverVersion(): string | null {
}

try {
const version = execSync(`"${binaryPath}" version`, {
const output = execSync(`"${binaryPath}" version`, {
encoding: "utf-8",
timeout: 5000,
}).trim();
return version;
// Extract version number (handles "v2.0.6", "light-prover v2.0.6", "2.0.6", etc.)
const match = output.match(/(\d+\.\d+\.\d+)/);
return match ? match[1] : null;
} catch (error) {
return null;
}
Expand Down Expand Up @@ -85,10 +90,9 @@ export async function startProver(proverPort: number, redisUrl?: string) {
await killProver();
await killProcessByPort(proverPort);

const keysDir = path.join(path.resolve(__dirname, BASE_PATH), KEYS_DIR);
const args = ["start"];

args.push("--keys-dir", keysDir);
args.push("--keys-dir", KEYS_DIR + "/");
args.push("--prover-address", `0.0.0.0:${proverPort}`);
args.push("--auto-download", "true");

Expand Down Expand Up @@ -128,11 +132,8 @@ export function getProverNameByArch(): string {
}

export function getProverPathByArch(): string {
let binaryName = getProverNameByArch();
const binDir = path.resolve(__dirname, BASE_PATH);
binaryName = path.join(binDir, binaryName);

return binaryName;
const binaryName = getProverNameByArch();
return path.join(PROVER_BIN_DIR, binaryName);
}

export async function healthCheck(
Expand Down
17 changes: 17 additions & 0 deletions js/compressed-token/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ npm install --save \
@lightprotocol/stateless.js \
```

### Testing

```bash
# Start validator
pnpm test-validator

# Run all tests
pnpm test:e2e:all

# Run by category
pnpm test:e2e:legacy # V1-only
pnpm test:e2e:ctoken # V2-only

# Run single file
vitest run tests/e2e/ctoken/decompress2.test.ts
```

### Documentation and examples

- [Latest Source code](https://github.com/lightprotocol/light-protocol/tree/main/js/compressed-token)
Expand Down
80 changes: 25 additions & 55 deletions js/compressed-token/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightprotocol/compressed-token",
"version": "0.22.1-alpha.2",
"version": "0.22.1-alpha.3",
"description": "JS client to interact with the compressed-token program",
"sideEffects": false,
"main": "dist/cjs/node/index.cjs",
Expand Down Expand Up @@ -89,63 +89,33 @@
"vitest": "^2.1.1"
},
"scripts": {
"test": "pnpm test:e2e:legacy:all",
"test-ci": "pnpm test:v1 && pnpm test:v2 && LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:ctoken:all",
"test:v1": "pnpm build:v1 && LIGHT_PROTOCOL_VERSION=V1 vitest run tests/unit && LIGHT_PROTOCOL_VERSION=V1 pnpm test:e2e:legacy:all",
"test:v2": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 vitest run tests/unit && LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:legacy:all",
"test:v2:ctoken": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:ctoken:all",
"test-all": "vitest run",
"test:unit:all": "EXCLUDE_E2E=true vitest run",
"test:unit:all:v1": "LIGHT_PROTOCOL_VERSION=V1 vitest run tests/unit --reporter=verbose",
"test:unit:all:v2": "LIGHT_PROTOCOL_VERSION=V2 vitest run tests/unit --reporter=verbose",
"test-all:verbose": "vitest run --reporter=verbose",
"test": "vitest run tests/unit && pnpm test:e2e",
"test:e2e": "if [ \"$LIGHT_PROTOCOL_VERSION\" = \"V1\" ]; then pnpm test:e2e:legacy; else pnpm test:e2e:all; fi",

"test:v1": "pnpm build:v1 && LIGHT_PROTOCOL_VERSION=V1 pnpm test",
"test:v2": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 pnpm test",
"test-ci": "pnpm test:v1 && pnpm test:v2",

"test:unit": "vitest run tests/unit",
"test:unit:v1": "LIGHT_PROTOCOL_VERSION=V1 pnpm test:unit",
"test:unit:v2": "LIGHT_PROTOCOL_VERSION=V2 pnpm test:unit",

"test:e2e:legacy": "pnpm test-validator && vitest run tests/e2e/legacy",
"test:e2e:ctoken": "pnpm test-validator && vitest run tests/e2e/ctoken",
"test:e2e:all": "pnpm test:e2e:legacy && pnpm test:e2e:ctoken",

"test-validator": "./../../cli/test_bin/run test-validator",
"test-validator-skip-prover": "./../../cli/test_bin/run test-validator --skip-prover",
"test:e2e:create-mint": "pnpm test-validator && NODE_OPTIONS='--trace-deprecation' vitest run tests/e2e/create-mint.test.ts --reporter=verbose",
"test:e2e:create-compressed-mint": "pnpm test-validator && vitest run tests/e2e/create-compressed-mint.test.ts --reporter=verbose --bail=1",
"test:e2e:create-associated-ctoken": "pnpm test-validator && vitest run tests/e2e/create-associated-ctoken.test.ts --reporter=verbose",
"test:e2e:mint-to-ctoken": "pnpm test-validator && vitest run tests/e2e/mint-to-ctoken.test.ts --reporter=verbose",
"test:e2e:mint-to-compressed": "pnpm test-validator && vitest run tests/e2e/mint-to-compressed.test.ts --reporter=verbose",
"test:e2e:mint-to-interface": "pnpm test-validator && vitest run tests/e2e/mint-to-interface.test.ts --reporter=verbose",
"test:e2e:mint-workflow": "pnpm test-validator && vitest run tests/e2e/mint-workflow.test.ts --reporter=verbose",
"test:e2e:update-mint": "pnpm test-validator && vitest run tests/e2e/update-mint.test.ts --reporter=verbose",
"test:e2e:update-metadata": "pnpm test-validator && vitest run tests/e2e/update-metadata.test.ts --reporter=verbose",
"test:e2e:layout": "vitest run tests/e2e/layout.test.ts --reporter=verbose --bail=1",
"test:e2e:select-accounts": "vitest run tests/e2e/select-accounts.test.ts --reporter=verbose",
"test:e2e:create-token-pool": "pnpm test-validator && vitest run tests/e2e/create-token-pool.test.ts",
"test:e2e:mint-to": "pnpm test-validator && vitest run tests/e2e/mint-to.test.ts --reporter=verbose --bail=1",
"test:e2e:approve-and-mint-to": "pnpm test-validator && vitest run tests/e2e/approve-and-mint-to.test.ts --reporter=verbose --bail=1",
"test:e2e:merge-token-accounts": "pnpm test-validator && vitest run tests/e2e/merge-token-accounts.test.ts --reporter=verbose",
"test:e2e:transfer": "pnpm test-validator && vitest run tests/e2e/transfer.test.ts --reporter=verbose --bail=1",
"test:e2e:delegate": "pnpm test-validator && vitest run tests/e2e/delegate.test.ts --reporter=verbose --bail=1",
"test:e2e:transfer-delegated": "pnpm test-validator && vitest run tests/e2e/transfer-delegated.test.ts --reporter=verbose --bail=1",
"test:e2e:compress": "pnpm test-validator && vitest run tests/e2e/compress.test.ts --reporter=verbose",
"test:e2e:compress-spl-token-account": "pnpm test-validator && vitest run tests/e2e/compress-spl-token-account.test.ts --reporter=verbose",
"test:e2e:decompress": "pnpm test-validator && vitest run tests/e2e/decompress.test.ts --reporter=verbose",
"test:e2e:decompress-delegated": "pnpm test-validator && vitest run tests/e2e/decompress-delegated.test.ts --reporter=verbose",
"test:e2e:decompress2": "pnpm test-validator && vitest run tests/e2e/decompress2.test.ts --reporter=verbose",
"test:e2e:rpc-token-interop": "pnpm test-validator && vitest run tests/e2e/rpc-token-interop.test.ts --reporter=verbose",
"test:e2e:rpc-multi-trees": "pnpm test-validator && vitest run tests/e2e/rpc-multi-trees.test.ts --reporter=verbose",
"test:e2e:multi-pool": "pnpm test-validator && vitest run tests/e2e/multi-pool.test.ts --reporter=verbose",
"test:e2e:legacy:all": "pnpm test-validator && vitest run tests/e2e/create-mint.test.ts && vitest run tests/e2e/mint-to.test.ts && vitest run tests/e2e/transfer.test.ts && vitest run tests/e2e/delegate.test.ts && vitest run tests/e2e/transfer-delegated.test.ts && vitest run tests/e2e/multi-pool.test.ts && vitest run tests/e2e/decompress-delegated.test.ts && pnpm test-validator-skip-prover && vitest run tests/e2e/compress.test.ts && vitest run tests/e2e/compress-spl-token-account.test.ts && vitest run tests/e2e/decompress.test.ts && vitest run tests/e2e/create-token-pool.test.ts && vitest run tests/e2e/approve-and-mint-to.test.ts && vitest run tests/e2e/rpc-token-interop.test.ts && vitest run tests/e2e/rpc-multi-trees.test.ts && vitest run tests/e2e/layout.test.ts && vitest run tests/e2e/select-accounts.test.ts",
"test:e2e:wrap": "pnpm test-validator && vitest run tests/e2e/wrap.test.ts --reporter=verbose",
"test:e2e:get-mint-interface": "pnpm test-validator && vitest run tests/e2e/get-mint-interface.test.ts --reporter=verbose",
"test:e2e:get-or-create-ata-interface": "pnpm test-validator && vitest run tests/e2e/get-or-create-ata-interface.test.ts --reporter=verbose",
"test:e2e:get-account-interface": "pnpm test-validator && vitest run tests/e2e/get-account-interface.test.ts --reporter=verbose",
"test:e2e:load-ata-standard": "pnpm test-validator && vitest run tests/e2e/load-ata-standard.test.ts --reporter=verbose",
"test:e2e:load-ata-unified": "pnpm test-validator && vitest run tests/e2e/load-ata-unified.test.ts --reporter=verbose",
"test:e2e:load-ata-combined": "pnpm test-validator && vitest run tests/e2e/load-ata-combined.test.ts --reporter=verbose",
"test:e2e:load-ata-spl-t22": "pnpm test-validator && vitest run tests/e2e/load-ata-spl-t22.test.ts --reporter=verbose",
"test:e2e:load-ata:all": "pnpm test-validator && vitest run tests/e2e/load-ata-standard.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-unified.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-combined.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-spl-t22.test.ts --bail=1",
"test:e2e:ctoken:all": "pnpm test-validator && vitest run tests/e2e/create-compressed-mint.test.ts --bail=1 && vitest run tests/e2e/create-associated-ctoken.test.ts --bail=1 && vitest run tests/e2e/mint-to-ctoken.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/mint-to-compressed.test.ts --bail=1 && vitest run tests/e2e/mint-to-interface.test.ts --bail=1 && vitest run tests/e2e/mint-workflow.test.ts --bail=1 && vitest run tests/e2e/update-mint.test.ts --bail=1 && vitest run tests/e2e/update-metadata.test.ts --bail=1 && vitest run tests/e2e/compressible-load.test.ts --bail=1 && vitest run tests/e2e/wrap.test.ts --bail=1 && vitest run tests/e2e/get-mint-interface.test.ts --bail=1 && vitest run tests/e2e/get-account-interface.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-standard.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-unified.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-combined.test.ts --bail=1 && pnpm test-validator && vitest run tests/e2e/load-ata-spl-t22.test.ts --bail=1",
"pull-idl": "../../scripts/push-compressed-token-idl.sh",
"build": "if [ \"$LIGHT_PROTOCOL_VERSION\" = \"V2\" ]; then LIGHT_PROTOCOL_VERSION=V2 pnpm build:bundle; else LIGHT_PROTOCOL_VERSION=V1 pnpm build:bundle; fi",
"build:bundle": "rimraf dist && rollup -c",
"build:v1": "LIGHT_PROTOCOL_VERSION=V1 pnpm build:stateless:v1 && LIGHT_PROTOCOL_VERSION=V1 pnpm build:bundle",
"build:v2": "LIGHT_PROTOCOL_VERSION=V2 pnpm build:stateless:v2 && LIGHT_PROTOCOL_VERSION=V2 pnpm build:bundle",

"build": "if [ \"$LIGHT_PROTOCOL_VERSION\" = \"V2\" ]; then pnpm build:v2:only; else pnpm build:v1:only; fi",
"build:v1": "pnpm build:stateless:v1 && pnpm build:v1:only",
"build:v2": "pnpm build:stateless:v2 && pnpm build:v2:only",
"build:v1:only": "LIGHT_PROTOCOL_VERSION=V1 rimraf dist && rollup -c",
"build:v2:only": "LIGHT_PROTOCOL_VERSION=V2 rimraf dist && rollup -c",
"build:stateless:v1": "cd ../stateless.js && pnpm build:v1",
"build:stateless:v2": "cd ../stateless.js && pnpm build:v2",
"build-ci": "if [ \"$LIGHT_PROTOCOL_VERSION\" = \"V2\" ]; then LIGHT_PROTOCOL_VERSION=V2 pnpm build:bundle; else LIGHT_PROTOCOL_VERSION=V1 pnpm build:bundle; fi",
"build-ci": "pnpm build",

"pull-idl": "../../scripts/push-compressed-token-idl.sh",
"format": "prettier --write .",
"lint": "eslint ."
},
Expand Down
Loading
Loading