Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ output1.txt

expand.rs
output.rs

**/light-token.md
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.11",
"version": "0.28.0-beta.3",
"description": "ZK Compression: Secure Scaling on Solana",
"maintainers": [
{
Expand Down
11 changes: 6 additions & 5 deletions cli/src/utils/initTestEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { downloadBinIfNotExists } from "../psp-utils";
import {
confirmRpcReadiness,
confirmServerStability,
executeCommand,
killProcess,
spawnBinary,
waitForServers,
} from "./process";
import { killProver, startProver } from "./processProverServer";
Expand Down Expand Up @@ -456,10 +456,11 @@ export async function startTestValidator({
solanaArgs.push(...validatorArgs.split(" "));
}
console.log("Starting test validator...");
await executeCommand({
command,
args: [...solanaArgs],
});
// Use spawnBinary instead of executeCommand to properly detach the process.
// This ensures the validator survives when the CLI exits (executeCommand uses
// piped stdio which causes SIGPIPE when parent exits).
// Pass process.env directly to maintain same env behavior as before.
spawnBinary(command, solanaArgs, process.env);
}

export async function killTestValidator() {
Expand Down
8 changes: 6 additions & 2 deletions cli/src/utils/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,11 @@ export async function execute(command: string): Promise<string> {
}
}

export function spawnBinary(command: string, args: string[] = []) {
export function spawnBinary(
command: string,
args: string[] = [],
env?: NodeJS.ProcessEnv,
) {
const logDir = "test-ledger";
const binaryName = path.basename(command);

Expand All @@ -212,7 +216,7 @@ export function spawnBinary(command: string, args: string[] = []) {
stdio: ["ignore", out, err],
shell: false,
detached: true,
env: {
env: env ?? {
...process.env,
RUST_LOG: process.env.RUST_LOG || "debug",
},
Expand Down
29 changes: 15 additions & 14 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.9",
"version": "0.23.0-beta.3",
"description": "JS client to interact with the compressed-token program",
"sideEffects": false,
"main": "dist/cjs/node/index.cjs",
Expand Down Expand Up @@ -92,8 +92,8 @@
"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:ctoken:all",
"test:v2:ctoken": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 pnpm test:e2e:ctoken:all",
"test:v2": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 vitest run tests/unit && LIGHT_PROTOCOL_VERSION=V2 LIGHT_PROTOCOL_BETA=true pnpm test:e2e:ctoken:all",
"test:v2:ctoken": "pnpm build:v2 && LIGHT_PROTOCOL_VERSION=V2 LIGHT_PROTOCOL_BETA=true 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",
Expand All @@ -106,7 +106,7 @@
"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-to-interface": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true 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",
Expand All @@ -124,20 +124,21 @@
"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:v1-v2-migration": "pnpm test-validator && LIGHT_PROTOCOL_VERSION=V2 vitest run tests/e2e/v1-v2-migration.test.ts --reporter=verbose --bail=1",
"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 && 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",
"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 && 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:wrap": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/wrap.test.ts --reporter=verbose",
"test:e2e:get-mint-interface": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/get-mint-interface.test.ts --reporter=verbose",
"test:e2e:get-or-create-ata-interface": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/get-or-create-ata-interface.test.ts --reporter=verbose",
"test:e2e:get-account-interface": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/get-account-interface.test.ts --reporter=verbose",
"test:e2e:load-ata-standard": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-standard.test.ts --reporter=verbose",
"test:e2e:load-ata-unified": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-unified.test.ts --reporter=verbose",
"test:e2e:load-ata-combined": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-combined.test.ts --reporter=verbose",
"test:e2e:load-ata-spl-t22": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-spl-t22.test.ts --reporter=verbose",
"test:e2e:load-ata:all": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-standard.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-unified.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-combined.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-spl-t22.test.ts --bail=1",
"test:e2e:ctoken:all": "pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/create-compressed-mint.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/create-associated-ctoken.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/mint-to-ctoken.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/mint-to-compressed.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/mint-to-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/mint-workflow.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/update-mint.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/update-metadata.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/compressible-load.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/wrap.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/get-mint-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/get-account-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/create-mint-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/create-ata-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/get-or-create-ata-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/transfer-interface.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/unwrap.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/decompress2.test.ts --bail=1 && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/payment-flows.test.ts --bail=1 && vitest run tests/e2e/v1-v2-migration.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-standard.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-unified.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true vitest run tests/e2e/load-ata-combined.test.ts --bail=1 && pnpm test-validator && LIGHT_PROTOCOL_BETA=true 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",
Expand Down
10 changes: 8 additions & 2 deletions js/compressed-token/src/actions/approve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
import BN from 'bn.js';
import { CompressedTokenProgram } from '../program';
import {
selectMinCompressedTokenAccountsForTransfer,
selectTokenAccountsForApprove,
selectAccountsByPreferredTreeType,
} from '../utils';

/**
Expand Down Expand Up @@ -49,11 +49,17 @@ export async function approve(
},
);

const [inputAccounts] = selectTokenAccountsForApprove(
// Select accounts from preferred tree type (V2 in V2 mode) with fallback
const { accounts: accountsToUse } = selectAccountsByPreferredTreeType(
compressedTokenAccounts.items,
amount,
);

const [inputAccounts] = selectTokenAccountsForApprove(
accountsToUse,
amount,
);

const proof = await rpc.getValidityProofV0(
inputAccounts.map(account => ({
hash: account.compressedAccount.hash,
Expand Down
13 changes: 11 additions & 2 deletions js/compressed-token/src/actions/decompress-delegated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
import BN from 'bn.js';

import { CompressedTokenProgram } from '../program';
import { selectMinCompressedTokenAccountsForTransfer } from '../utils';
import {
selectMinCompressedTokenAccountsForTransfer,
selectAccountsByPreferredTreeType,
} from '../utils';
import {
selectSplInterfaceInfosForDecompression,
SplInterfaceInfo,
Expand Down Expand Up @@ -56,11 +59,17 @@ export async function decompressDelegated(
mint,
});

const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
// Select accounts from preferred tree type (V2 in V2 mode) with fallback
const { accounts: accountsToUse } = selectAccountsByPreferredTreeType(
compressedTokenAccounts.items,
amount,
);

const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
accountsToUse,
amount,
);

const proof = await rpc.getValidityProofV0(
inputAccounts.map(account => ({
hash: account.compressedAccount.hash,
Expand Down
17 changes: 12 additions & 5 deletions js/compressed-token/src/actions/decompress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
} from '@lightprotocol/stateless.js';
import BN from 'bn.js';
import { CompressedTokenProgram } from '../program';
import { selectMinCompressedTokenAccountsForTransfer } from '../utils';
import {
selectMinCompressedTokenAccountsForTransfer,
selectAccountsByPreferredTreeType,
} from '../utils';
import {
selectSplInterfaceInfosForDecompression,
SplInterfaceInfo,
Expand Down Expand Up @@ -50,16 +53,20 @@ export async function decompress(

const compressedTokenAccounts = await rpc.getCompressedTokenAccountsByOwner(
owner.publicKey,
{
mint,
},
{ mint },
);

const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
// Select accounts from preferred tree type (V2 in V2 mode) with fallback
const { accounts: accountsToUse } = selectAccountsByPreferredTreeType(
compressedTokenAccounts.items,
amount,
);

const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
accountsToUse,
amount,
);

const proof = await rpc.getValidityProofV0(
inputAccounts.map(account => ({
hash: account.compressedAccount.hash,
Expand Down
53 changes: 49 additions & 4 deletions js/compressed-token/src/actions/merge-token-accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import {
buildAndSignTx,
sendAndConfirmTx,
bn,
TreeType,
} from '@lightprotocol/stateless.js';
import { CompressedTokenProgram } from '../program';
import { selectAccountsByPreferredTreeType } from '../utils';

/**
* Max input accounts per merge.
Expand All @@ -24,9 +26,13 @@ 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.
* accounts. Each call merges up to 4 accounts at a time.
*
* Supports automatic V1 -> V2 migration: when running in V2 mode,
* merging V1 token accounts will produce a V2 output.
*
* IMPORTANT: Only accounts from the same tree type can be merged in one
* transaction. If you have mixed V1+V2 accounts, merge them separately.
*
* @param rpc RPC connection to use
* @param payer Fee payer
Expand Down Expand Up @@ -58,13 +64,52 @@ export async function mergeTokenAccounts(
throw new Error('Only one token account exists, nothing to merge');
}

// Select accounts from preferred tree type (V2 in V2 mode) - for merge need at least 2
const { accounts: preferredAccounts, treeType: preferredTreeType } =
selectAccountsByPreferredTreeType(compressedTokenAccounts.items);

let selectedAccounts = preferredAccounts;
let selectedTreeType = preferredTreeType;

// For merge, need at least 2 accounts of the same type
// If preferred type has < 2, try fallback type
if (selectedAccounts.length < 2) {
const fallbackType =
preferredTreeType === TreeType.StateV2
? TreeType.StateV1
: TreeType.StateV2;
const fallbackAccounts = compressedTokenAccounts.items.filter(
acc => acc.compressedAccount.treeInfo.treeType === fallbackType,
);

if (fallbackAccounts.length >= 2) {
selectedAccounts = fallbackAccounts;
selectedTreeType = fallbackType;
} else if (
selectedAccounts.length === 1 &&
fallbackAccounts.length === 1
) {
// Have 1 V1 and 1 V2 - can't merge mixed types
throw new Error(
'Cannot merge accounts from different tree types (V1/V2). ' +
'You have 1 V1 and 1 V2 account - nothing to merge within same type.',
);
} else {
throw new Error(
`Not enough accounts of the same tree type to merge. ` +
`Found: ${selectedAccounts.length} ${selectedTreeType === TreeType.StateV1 ? 'V1' : 'V2'} accounts.`,
);
}
}

// Take up to MAX_MERGE_ACCOUNTS to merge in this transaction
const batch = compressedTokenAccounts.items.slice(0, MAX_MERGE_ACCOUNTS);
const batch = selectedAccounts.slice(0, MAX_MERGE_ACCOUNTS);

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

// V1→V2 migration handled inside CompressedTokenProgram.mergeTokenAccounts
const mergeInstructions = await CompressedTokenProgram.mergeTokenAccounts({
payer: payer.publicKey,
owner: owner.publicKey,
Expand Down
Loading