Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
afa5f81
fix prover dl
SwenSchaeferjohann Dec 13, 2025
47298b6
bump cli alpha.4
SwenSchaeferjohann Dec 14, 2025
142920e
bump
SwenSchaeferjohann Dec 17, 2025
0bb11d4
fix cli startup
SwenSchaeferjohann Dec 17, 2025
f0290bf
bumps
SwenSchaeferjohann Dec 17, 2025
9ee7025
fix and run unit tests on ci
SwenSchaeferjohann Dec 19, 2025
37e25b2
default to v2 for alpha release
SwenSchaeferjohann Dec 19, 2025
4adc027
remove broken merge-token-accounts test from CI (has TODO: not required)
SwenSchaeferjohann Dec 19, 2025
c1f1654
fix: mergeTokenAccounts - process single batch per tx to avoid proof …
SwenSchaeferjohann Dec 19, 2025
b5e8583
feat: mergeTokenAccounts supports up to 8 accounts for V2 (4 for V1)
SwenSchaeferjohann Dec 19, 2025
75560f1
pass allowOwnerOffCurve along call chain
SwenSchaeferjohann Dec 19, 2025
d8ead5e
wip
SwenSchaeferjohann Dec 19, 2025
d3395ae
createLoadAtaInstructions shouldnt err on accountnotfound
SwenSchaeferjohann Dec 19, 2025
e9cedfa
add rpc readiness check
SwenSchaeferjohann Dec 19, 2025
78756bd
fix ixdata and default to right token program on getOrCreateAtaInterface
SwenSchaeferjohann Dec 19, 2025
625b9cd
wip
SwenSchaeferjohann Dec 19, 2025
161ba7a
fix getOrCreateAtaInterface
SwenSchaeferjohann Dec 19, 2025
9160e9d
static 200k cu meter
SwenSchaeferjohann Dec 19, 2025
d0feeb0
scope v1 correctly
SwenSchaeferjohann Dec 20, 2025
d6811e4
test
SwenSchaeferjohann Dec 20, 2025
96e9d3f
fixes
SwenSchaeferjohann Dec 20, 2025
8a0bfe9
wip
SwenSchaeferjohann Dec 20, 2025
bcfaada
sequential e2e
SwenSchaeferjohann Dec 20, 2025
ab2367f
bump to alpha.6
SwenSchaeferjohann Dec 21, 2025
d26791a
fix getstatetreeinfos versioning
SwenSchaeferjohann Dec 22, 2025
d278cce
bump lockfile
SwenSchaeferjohann Dec 22, 2025
c723dc4
add temp devnet backward compat helpers for mintaction
SwenSchaeferjohann Dec 22, 2025
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
6 changes: 3 additions & 3 deletions .github/workflows/js-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ jobs:
done
echo "Tests passed on attempt $attempt"

- name: Run compressed-token legacy tests with V2
- name: Run compressed-token unit tests with V2
run: |
echo "Running compressed-token legacy tests with retry logic (max 2 attempts)..."
echo "Running compressed-token unit tests with retry logic (max 2 attempts)..."
attempt=1
max_attempts=2
until npx nx test @lightprotocol/compressed-token; do
until npx nx run @lightprotocol/compressed-token:test:unit:all:v2; 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.7",
"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
13 changes: 7 additions & 6 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.5",
"description": "JS client to interact with the compressed-token program",
"sideEffects": false,
"main": "dist/cjs/node/index.cjs",
Expand Down Expand Up @@ -89,10 +89,10 @@
"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": "vitest run tests/unit && if [ \"$LIGHT_PROTOCOL_VERSION\" = \"V1\" ]; then pnpm test:e2e:legacy:all; else pnpm test:e2e:ctoken:all; fi",
"test-ci": "pnpm test:v1 && pnpm test:v2",
"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": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 vitest run tests/unit && LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:ctoken: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",
Expand Down Expand Up @@ -127,7 +127,7 @@
"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: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 && vitest run tests/e2e/merge-token-accounts.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",
Expand All @@ -137,7 +137,8 @@
"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",
"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 && vitest run tests/e2e/create-mint-interface.test.ts --bail=1 && vitest run tests/e2e/create-ata-interface.test.ts --bail=1 && vitest run tests/e2e/get-or-create-ata-interface.test.ts --bail=1 && vitest run tests/e2e/transfer-interface.test.ts --bail=1 && vitest run tests/e2e/unwrap.test.ts --bail=1 && vitest run tests/e2e/decompress2.test.ts --bail=1 && vitest run tests/e2e/payment-flows.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",
"test:e2e:all": "pnpm test:e2e:legacy:all && pnpm test:e2e:ctoken:all",
"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",
Expand Down
57 changes: 32 additions & 25 deletions js/compressed-token/src/actions/merge-token-accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ import {
import { CompressedTokenProgram } from '../program';

/**
* Merge multiple compressed token accounts for a given mint into a single
* account
* Max input accounts per merge.
*
* Even though V2 supports larger merges, we keep this at 4 to avoid oversized
* transactions / RPC payload limits under heavy test load.
*/
const MAX_MERGE_ACCOUNTS = 4;

/**
* Merge multiple compressed token accounts for a given mint into fewer
* accounts. Each call merges up to 4 accounts (V1) or 8 accounts (V2) at a
* time. Call repeatedly until only 1 account remains if full consolidation
* is needed.
*
* @param rpc RPC connection to use
* @param payer Fee payer
Expand Down Expand Up @@ -44,33 +54,30 @@ export async function mergeTokenAccounts(
);
}

const instructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
];
if (compressedTokenAccounts.items.length === 1) {
throw new Error('Only one token account exists, nothing to merge');
}

for (
let i = 0;
i < compressedTokenAccounts.items.slice(0, 8).length;
i += 4
) {
const batch = compressedTokenAccounts.items.slice(i, i + 4);
// Take up to MAX_MERGE_ACCOUNTS to merge in this transaction
const batch = compressedTokenAccounts.items.slice(0, MAX_MERGE_ACCOUNTS);

const proof = await rpc.getValidityProof(
batch.map(account => bn(account.compressedAccount.hash)),
);
const proof = await rpc.getValidityProof(
batch.map(account => bn(account.compressedAccount.hash)),
);

const batchInstructions =
await CompressedTokenProgram.mergeTokenAccounts({
payer: payer.publicKey,
owner: owner.publicKey,
inputCompressedTokenAccounts: batch,
mint,
recentValidityProof: proof.compressedProof,
recentInputStateRootIndices: proof.rootIndices,
});
const mergeInstructions = await CompressedTokenProgram.mergeTokenAccounts({
payer: payer.publicKey,
owner: owner.publicKey,
inputCompressedTokenAccounts: batch,
mint,
recentValidityProof: proof.compressedProof,
recentInputStateRootIndices: proof.rootIndices,
});

instructions.push(...batchInstructions);
}
const instructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
...mergeInstructions,
];

const { blockhash } = await rpc.getLatestBlockhash();
const additionalSigners = dedupeSigner(payer, [owner]);
Expand Down
7 changes: 5 additions & 2 deletions js/compressed-token/src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,8 +1463,11 @@ export class CompressedTokenProgram {
recentValidityProof,
recentInputStateRootIndices,
}: MergeTokenAccountsParams): Promise<TransactionInstruction[]> {
if (inputCompressedTokenAccounts.length > 4) {
throw new Error('Cannot merge more than 4 token accounts at once');
const maxAccounts = featureFlags.isV2() ? 8 : 4;
if (inputCompressedTokenAccounts.length > maxAccounts) {
throw new Error(
`Cannot merge more than ${maxAccounts} token accounts at once`,
);
}

checkMint(inputCompressedTokenAccounts, mint);
Expand Down
Loading
Loading