diff --git a/.github/workflows/js-v2.yml b/.github/workflows/js-v2.yml index 077bb4f5a0..ad8933cf97 100644 --- a/.github/workflows/js-v2.yml +++ b/.github/workflows/js-v2.yml @@ -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" diff --git a/cli/package.json b/cli/package.json index 57a1e2518f..8656b9b5d9 100644 --- a/cli/package.json +++ b/cli/package.json @@ -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": [ { diff --git a/cli/src/utils/constants.ts b/cli/src/utils/constants.ts index e2f527654d..e3a9137737 100644 --- a/cli/src/utils/constants.ts +++ b/cli/src/utils/constants.ts @@ -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/"; diff --git a/cli/src/utils/downloadProverBinary.ts b/cli/src/utils/downloadProverBinary.ts index 7f565b389b..bf747e5cc9 100644 --- a/cli/src/utils/downloadProverBinary.ts +++ b/cli/src/utils/downloadProverBinary.ts @@ -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 { @@ -151,5 +152,5 @@ async function downloadFile( } export function getProverVersion(): string { - return PROVER_VERSION; + return PROVER_BINARY_VERSION; } diff --git a/cli/src/utils/initTestEnv.ts b/cli/src/utils/initTestEnv.ts index 6492414fa3..6438af9f41 100644 --- a/cli/src/utils/initTestEnv.ts +++ b/cli/src/utils/initTestEnv.ts @@ -11,6 +11,7 @@ import { import path from "path"; import { downloadBinIfNotExists } from "../psp-utils"; import { + confirmRpcReadiness, confirmServerStability, executeCommand, killProcess, @@ -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, ); } diff --git a/cli/src/utils/process.ts b/cli/src/utils/process.ts index ffdc4003e5..e3d1a241f5 100644 --- a/cli/src/utils/process.ts +++ b/cli/src/utils/process.ts @@ -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}`, + ); +} diff --git a/cli/src/utils/processPhotonIndexer.ts b/cli/src/utils/processPhotonIndexer.ts index a2407f82f3..1c883a121d 100644 --- a/cli/src/utils/processPhotonIndexer.ts +++ b/cli/src/utils/processPhotonIndexer.ts @@ -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 }); @@ -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" }]); diff --git a/cli/src/utils/processProverServer.ts b/cli/src/utils/processProverServer.ts index 3e91b30256..c163f07dd6 100644 --- a/cli/src/utils/processProverServer.ts +++ b/cli/src/utils/processProverServer.ts @@ -1,4 +1,5 @@ import path from "path"; +import os from "os"; import fs from "fs"; import { execSync } from "child_process"; import { @@ -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()); @@ -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; } @@ -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"); @@ -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( diff --git a/js/compressed-token/README.md b/js/compressed-token/README.md index 7b8bacc979..1d0e6295a0 100644 --- a/js/compressed-token/README.md +++ b/js/compressed-token/README.md @@ -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) diff --git a/js/compressed-token/package.json b/js/compressed-token/package.json index aea4e9cafc..c39ee16962 100644 --- a/js/compressed-token/package.json +++ b/js/compressed-token/package.json @@ -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", @@ -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 ." }, diff --git a/js/compressed-token/src/actions/merge-token-accounts.ts b/js/compressed-token/src/actions/merge-token-accounts.ts index 8ca4844c82..704232e4ba 100644 --- a/js/compressed-token/src/actions/merge-token-accounts.ts +++ b/js/compressed-token/src/actions/merge-token-accounts.ts @@ -11,12 +11,18 @@ import { buildAndSignTx, sendAndConfirmTx, bn, + featureFlags, } from '@lightprotocol/stateless.js'; import { CompressedTokenProgram } from '../program'; +/** Max input accounts per merge: V1 supports 4, V2 supports 8 */ +const getMaxMergeAccounts = () => (featureFlags.isV2() ? 8 : 4); + /** - * Merge multiple compressed token accounts for a given mint into a single - * account + * 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 @@ -44,33 +50,31 @@ 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); + const maxAccounts = getMaxMergeAccounts(); + // Take up to maxAccounts to merge in this transaction + const batch = compressedTokenAccounts.items.slice(0, maxAccounts); - 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]); diff --git a/js/compressed-token/src/program.ts b/js/compressed-token/src/program.ts index 5e9b7e5be3..c788912f07 100644 --- a/js/compressed-token/src/program.ts +++ b/js/compressed-token/src/program.ts @@ -1463,8 +1463,11 @@ export class CompressedTokenProgram { recentValidityProof, recentInputStateRootIndices, }: MergeTokenAccountsParams): Promise { - 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); diff --git a/js/compressed-token/src/v3/actions/create-mint-interface.ts b/js/compressed-token/src/v3/actions/create-mint-interface.ts index 81f7c0a0c2..3d87210068 100644 --- a/js/compressed-token/src/v3/actions/create-mint-interface.ts +++ b/js/compressed-token/src/v3/actions/create-mint-interface.ts @@ -17,13 +17,14 @@ import { getBatchAddressTreeInfo, DerivationMode, CTOKEN_PROGRAM_ID, + getDefaultAddressTreeInfo, } from '@lightprotocol/stateless.js'; import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { createMintInstruction, TokenMetadataInstructionData, } from '../instructions/create-mint'; -import { findMintAddress } from '../derivation'; +import { deriveCMintAddress, findMintAddress } from '../derivation'; import { createMint } from '../../actions/create-mint'; export { TokenMetadataInstructionData }; @@ -81,6 +82,14 @@ export async function createMintInterface( 'mintAuthority must be a Signer for compressed token mints', ); } + if ( + addressTreeInfo && + !addressTreeInfo.tree.equals(getDefaultAddressTreeInfo().tree) + ) { + throw new Error( + `addressTreeInfo ${addressTreeInfo?.tree.toString()} must be the default address tree info: ${getDefaultAddressTreeInfo().tree.toString()}`, + ); + } const resolvedFreezeAuthority = freezeAuthority && 'secretKey' in freezeAuthority @@ -92,15 +101,20 @@ export async function createMintInterface( outputStateTreeInfo ?? selectStateTreeInfo(await rpc.getStateTreeInfos()); + const compressedMintAddress = deriveCMintAddress( + keypair.publicKey, + addressTreeInfo, + ); + const validityProof = await rpc.getValidityProofV2( [], [ { - address: findMintAddress(keypair.publicKey)[0].toBytes(), + address: Uint8Array.from(compressedMintAddress), treeInfo: addressTreeInfo, }, ], - DerivationMode.compressible, + DerivationMode.standard, ); const ix = createMintInstruction( @@ -123,10 +137,8 @@ export async function createMintInterface( blockhash, additionalSigners, ); - const txId = await sendAndConfirmTx(rpc, tx, { - ...confirmOptions, - skipPreflight: true, - }); + const txId = await sendAndConfirmTx(rpc, tx); + console.log('txId', txId); const mint = findMintAddress(keypair.publicKey); return { mint: mint[0], transactionSignature: txId }; diff --git a/js/compressed-token/src/v3/actions/get-or-create-ata-interface.ts b/js/compressed-token/src/v3/actions/get-or-create-ata-interface.ts index 905400638d..efcba8c649 100644 --- a/js/compressed-token/src/v3/actions/get-or-create-ata-interface.ts +++ b/js/compressed-token/src/v3/actions/get-or-create-ata-interface.ts @@ -67,7 +67,7 @@ export async function getOrCreateAtaInterface( allowOwnerOffCurve = false, commitment?: Commitment, confirmOptions?: ConfirmOptions, - programId = TOKEN_PROGRAM_ID, + programId = CTOKEN_PROGRAM_ID, associatedTokenProgramId = getAtaProgramId(programId), ): Promise { return _getOrCreateAtaInterface( @@ -140,6 +140,7 @@ export async function _getOrCreateAtaInterface( commitment, confirmOptions, wrap, + allowOwnerOffCurve, ); } @@ -179,6 +180,7 @@ async function getOrCreateCTokenAta( commitment?: Commitment, confirmOptions?: ConfirmOptions, wrap = false, + allowOwnerOffCurve = false, ): Promise { const ownerPubkey = getOwnerPublicKey(owner); const ownerIsSigner = isSigner(owner); @@ -197,6 +199,7 @@ async function getOrCreateCTokenAta( commitment, CTOKEN_PROGRAM_ID, wrap, + allowOwnerOffCurve, ); // Check if we have a hot account @@ -228,6 +231,7 @@ async function getOrCreateCTokenAta( commitment, CTOKEN_PROGRAM_ID, wrap, + allowOwnerOffCurve, ); hasHotAccount = true; } else { @@ -300,6 +304,7 @@ async function getOrCreateCTokenAta( commitment, CTOKEN_PROGRAM_ID, wrap, + allowOwnerOffCurve, ); } } diff --git a/js/compressed-token/src/v3/actions/load-ata.ts b/js/compressed-token/src/v3/actions/load-ata.ts index d6a9f7868a..77338d2c26 100644 --- a/js/compressed-token/src/v3/actions/load-ata.ts +++ b/js/compressed-token/src/v3/actions/load-ata.ts @@ -18,6 +18,7 @@ import { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, + TokenAccountNotFoundError, } from '@solana/spl-token'; import { AccountInterface, @@ -77,23 +78,31 @@ export async function createLoadAtaInstructions( // Validation happens inside getAtaInterface via checkAtaAddress helper: // - Always validates ata matches mint+owner derivation // - For wrap=true, additionally requires c-token ATA - const ataInterface = await _getAtaInterface( - rpc, - ata, - owner, - mint, - undefined, - undefined, - wrap, - ); - return createLoadAtaInstructionsFromInterface( - rpc, - payer, - ataInterface, - options, - wrap, - ata, - ); + try { + const ataInterface = await _getAtaInterface( + rpc, + ata, + owner, + mint, + undefined, + undefined, + wrap, + ); + return createLoadAtaInstructionsFromInterface( + rpc, + payer, + ataInterface, + options, + wrap, + ata, + ); + } catch (error) { + // If account doesn't exist, there's nothing to load + if (error instanceof TokenAccountNotFoundError) { + return []; + } + throw error; + } } // Re-export AtaType for backwards compatibility diff --git a/js/compressed-token/src/v3/actions/transfer-interface.ts b/js/compressed-token/src/v3/actions/transfer-interface.ts index 4675f584ca..e16f5fb24d 100644 --- a/js/compressed-token/src/v3/actions/transfer-interface.ts +++ b/js/compressed-token/src/v3/actions/transfer-interface.ts @@ -68,7 +68,9 @@ function calculateComputeUnits( // SPL/T22 wrap operations cu += splWrapCount * 5_000; - return cu; + // TODO: dynamic + // return cu; + return 200_000; } /** @@ -327,7 +329,6 @@ export async function transferInterface( destination, owner.publicKey, amountBigInt, - payer.publicKey, ), ); diff --git a/js/compressed-token/src/v3/ata-utils.ts b/js/compressed-token/src/v3/ata-utils.ts index 482d75f5e2..1ffb3e758a 100644 --- a/js/compressed-token/src/v3/ata-utils.ts +++ b/js/compressed-token/src/v3/ata-utils.ts @@ -34,24 +34,26 @@ export interface AtaValidationResult { * * Pass programId for fast path. * - * @param ata ATA address to check - * @param mint Mint address - * @param owner Owner address - * @param programId Optional: if known, only check this program's ATA - * @returns Result with detected type, or throws on mismatch + * @param ata ATA address to check + * @param mint Mint address + * @param owner Owner address + * @param programId Optional: if known, only check this program's ATA + * @param allowOwnerOffCurve Allow the owner to be off-curve (PDA) + * @returns Result with detected type, or throws on mismatch */ export function checkAtaAddress( ata: PublicKey, mint: PublicKey, owner: PublicKey, programId?: PublicKey, + allowOwnerOffCurve = false, ): AtaValidationResult { // fast path if (programId) { const expected = getAssociatedTokenAddressSync( mint, owner, - false, + allowOwnerOffCurve, programId, getAtaProgramId(programId), ); @@ -76,7 +78,7 @@ export function checkAtaAddress( ctokenExpected = getAssociatedTokenAddressSync( mint, owner, - false, + allowOwnerOffCurve, CTOKEN_PROGRAM_ID, getAtaProgramId(CTOKEN_PROGRAM_ID), ); @@ -92,7 +94,7 @@ export function checkAtaAddress( splExpected = getAssociatedTokenAddressSync( mint, owner, - false, + allowOwnerOffCurve, TOKEN_PROGRAM_ID, getAtaProgramId(TOKEN_PROGRAM_ID), ); @@ -104,7 +106,7 @@ export function checkAtaAddress( t22Expected = getAssociatedTokenAddressSync( mint, owner, - false, + allowOwnerOffCurve, TOKEN_2022_PROGRAM_ID, getAtaProgramId(TOKEN_2022_PROGRAM_ID), ); diff --git a/js/compressed-token/src/v3/get-account-interface.ts b/js/compressed-token/src/v3/get-account-interface.ts index c54c437410..a2f7ddf757 100644 --- a/js/compressed-token/src/v3/get-account-interface.ts +++ b/js/compressed-token/src/v3/get-account-interface.ts @@ -220,13 +220,14 @@ export async function getAccountInterface( /** * Retrieve associated token account for a given owner and mint. * - * @param rpc RPC connection - * @param ata Associated token address - * @param owner Owner public key - * @param mint Mint public key - * @param commitment Optional commitment level - * @param programId Optional program ID - * @param wrap Include SPL/T22 balances (default: false) + * @param rpc RPC connection + * @param ata Associated token address + * @param owner Owner public key + * @param mint Mint public key + * @param commitment Optional commitment level + * @param programId Optional program ID + * @param wrap Include SPL/T22 balances (default: false) + * @param allowOwnerOffCurve Allow owner to be off-curve (PDA) * @returns AccountInterface with ATA metadata */ export async function getAtaInterface( @@ -237,11 +238,18 @@ export async function getAtaInterface( commitment?: Commitment, programId?: PublicKey, wrap = false, + allowOwnerOffCurve = false, ): Promise { // Invariant: ata MUST match a valid derivation from mint+owner. // Hot path: if programId provided, only validate against that program. // For wrap=true, additionally require c-token ATA. - const validation = checkAtaAddress(ata, mint, owner, programId); + const validation = checkAtaAddress( + ata, + mint, + owner, + programId, + allowOwnerOffCurve, + ); if (wrap && validation.type !== 'ctoken') { throw new Error( @@ -574,10 +582,7 @@ async function getUnifiedAccountInterface( // account not found if (sources.length === 0) { - const triedPrograms = wrap - ? 'TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, and CTOKEN_PROGRAM_ID (both onchain and compressed)' - : 'CTOKEN_PROGRAM_ID (both onchain and compressed)'; - throw new Error(`Token account not found. Tried ${triedPrograms}.`); + throw new TokenAccountNotFoundError(); } // priority order: c-token hot > c-token cold > SPL/T22 diff --git a/js/compressed-token/src/v3/instructions/transfer-interface.ts b/js/compressed-token/src/v3/instructions/transfer-interface.ts index 71aa2c57e1..8cefbae9ac 100644 --- a/js/compressed-token/src/v3/instructions/transfer-interface.ts +++ b/js/compressed-token/src/v3/instructions/transfer-interface.ts @@ -16,9 +16,8 @@ const CTOKEN_TRANSFER_DISCRIMINATOR = 3; * * @param source Source c-token account * @param destination Destination c-token account - * @param owner Owner of the source account (signer) + * @param owner Owner of the source account (signer, also pays for compressible extension top-ups) * @param amount Amount to transfer - * @param payer Payer for compressible extension top-up (optional) * @returns Transaction instruction for c-token transfer */ export function createCTokenTransferInstruction( @@ -26,29 +25,20 @@ export function createCTokenTransferInstruction( destination: PublicKey, owner: PublicKey, amount: number | bigint, - payer?: PublicKey, ): TransactionInstruction { - // Instruction data format (from CTOKEN_TRANSFER.md): + // Instruction data format: // byte 0: discriminator (3) - // byte 1: padding (0) - // bytes 2-9: amount (u64 LE) - SPL TokenInstruction::Transfer format - const data = Buffer.alloc(10); + // bytes 1-8: amount (u64 LE) + const data = Buffer.alloc(9); data.writeUInt8(CTOKEN_TRANSFER_DISCRIMINATOR, 0); - data.writeUInt8(0, 1); // padding - data.writeBigUInt64LE(BigInt(amount), 2); + data.writeBigUInt64LE(BigInt(amount), 1); const keys = [ { pubkey: source, isSigner: false, isWritable: true }, { pubkey: destination, isSigner: false, isWritable: true }, - { pubkey: owner, isSigner: true, isWritable: false }, + { pubkey: owner, isSigner: true, isWritable: true }, // owner is also payer for top-ups ]; - // Add payer as 4th account if provided and different from owner - // (for compressible extension top-up) - if (payer && !payer.equals(owner)) { - keys.push({ pubkey: payer, isSigner: true, isWritable: true }); - } - return new TransactionInstruction({ programId: CTOKEN_PROGRAM_ID, keys, @@ -64,7 +54,6 @@ export function createCTokenTransferInstruction( * @param destination Destination token account * @param owner Owner of the source account (signer) * @param amount Amount to transfer - * @param payer Payer for compressible top-up (optional) * @returns instruction for c-token transfer */ export function createTransferInterfaceInstruction( @@ -74,7 +63,6 @@ export function createTransferInterfaceInstruction( amount: number | bigint, multiSigners: (Signer | PublicKey)[] = [], programId: PublicKey = CTOKEN_PROGRAM_ID, - payer?: PublicKey, ): TransactionInstruction { if (programId.equals(CTOKEN_PROGRAM_ID)) { if (multiSigners.length > 0) { @@ -87,7 +75,6 @@ export function createTransferInterfaceInstruction( destination, owner, amount, - payer, ); } diff --git a/js/compressed-token/tests/e2e/compressible-load.test.ts b/js/compressed-token/tests/e2e/ctoken/compressible-load.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/compressible-load.test.ts rename to js/compressed-token/tests/e2e/ctoken/compressible-load.test.ts index c44e96bfd6..9a6985aeb9 100644 --- a/js/compressed-token/tests/e2e/compressible-load.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/compressible-load.test.ts @@ -13,12 +13,12 @@ import { CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; import { createLoadAccountsParams, createLoadAtaInstructionsFromInterface, @@ -26,9 +26,9 @@ import { CompressibleAccountInput, ParsedAccountInfoInterface, calculateCompressibleLoadComputeUnits, -} from '../../src/v3/actions/load-ata'; -import { getAtaInterface } from '../../src/v3/get-account-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; +} from '../../../src/v3/actions/load-ata'; +import { getAtaInterface } from '../../../src/v3/get-account-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/create-associated-ctoken.test.ts b/js/compressed-token/tests/e2e/ctoken/create-associated-ctoken.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/create-associated-ctoken.test.ts rename to js/compressed-token/tests/e2e/ctoken/create-associated-ctoken.test.ts index 312d43d4f4..70890d0705 100644 --- a/js/compressed-token/tests/e2e/create-associated-ctoken.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/create-associated-ctoken.test.ts @@ -8,14 +8,14 @@ import { featureFlags, getDefaultAddressTreeInfo, } from '@lightprotocol/stateless.js'; -import { createMintInterface } from '../../src/v3/actions'; +import { createMintInterface } from '../../../src/v3/actions'; import { createAssociatedCTokenAccount, createAssociatedCTokenAccountIdempotent, -} from '../../src/v3/actions/create-associated-ctoken'; -import { createTokenMetadata } from '../../src/v3/instructions'; -import { getAssociatedCTokenAddress } from '../../src/v3/derivation'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/actions/create-associated-ctoken'; +import { createTokenMetadata } from '../../../src/v3/instructions'; +import { getAssociatedCTokenAddress } from '../../../src/v3/derivation'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/create-ata-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/create-ata-interface.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/create-ata-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/create-ata-interface.test.ts index f923c10252..3de57e4bd3 100644 --- a/js/compressed-token/tests/e2e/create-ata-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/create-ata-interface.test.ts @@ -16,13 +16,13 @@ import { getAssociatedTokenAddressSync, ASSOCIATED_TOKEN_PROGRAM_ID, } from '@solana/spl-token'; -import { createMintInterface } from '../../src/v3/actions'; +import { createMintInterface } from '../../../src/v3/actions'; import { createAtaInterface, createAtaInterfaceIdempotent, -} from '../../src/v3/actions/create-ata-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/actions/create-ata-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/create-compressed-mint.test.ts b/js/compressed-token/tests/e2e/ctoken/create-compressed-mint.test.ts similarity index 96% rename from js/compressed-token/tests/e2e/create-compressed-mint.test.ts rename to js/compressed-token/tests/e2e/ctoken/create-compressed-mint.test.ts index 875f837458..07e4fa5102 100644 --- a/js/compressed-token/tests/e2e/create-compressed-mint.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/create-compressed-mint.test.ts @@ -20,10 +20,10 @@ import { import { createMintInstruction, createTokenMetadata, -} from '../../src/v3/instructions'; -import { createMintInterface } from '../../src/v3/actions'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/instructions'; +import { createMintInterface } from '../../../src/v3/actions'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/create-mint-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/create-mint-interface.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/create-mint-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/create-mint-interface.test.ts index d55eedea8c..040dd46915 100644 --- a/js/compressed-token/tests/e2e/create-mint-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/create-mint-interface.test.ts @@ -13,10 +13,10 @@ import { TOKEN_2022_PROGRAM_ID, getMint, } from '@solana/spl-token'; -import { createMintInterface } from '../../src/v3/actions/create-mint-interface'; -import { createTokenMetadata } from '../../src/v3/instructions'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +import { createMintInterface } from '../../../src/v3/actions/create-mint-interface'; +import { createTokenMetadata } from '../../../src/v3/instructions'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/decompress2.test.ts b/js/compressed-token/tests/e2e/ctoken/decompress2.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/decompress2.test.ts rename to js/compressed-token/tests/e2e/ctoken/decompress2.test.ts index ee918a83b3..0d5a78c9c4 100644 --- a/js/compressed-token/tests/e2e/decompress2.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/decompress2.test.ts @@ -12,16 +12,16 @@ import { } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, selectSplInterfaceInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; -import { getAssociatedTokenAddressInterface } from '../../src/'; -import { decompressInterface } from '../../src/v3/actions/decompress-interface'; -import { createDecompressInterfaceInstruction } from '../../src/v3/instructions/create-decompress-interface-instruction'; +} from '../../../src/utils/get-token-pool-infos'; +import { getAssociatedTokenAddressInterface } from '../../../src/'; +import { decompressInterface } from '../../../src/v3/actions/decompress-interface'; +import { createDecompressInterfaceInstruction } from '../../../src/v3/instructions/create-decompress-interface-instruction'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/get-account-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/get-account-interface.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/get-account-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/get-account-interface.test.ts index 5700919869..dd36cf0ae7 100644 --- a/js/compressed-token/tests/e2e/get-account-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/get-account-interface.test.ts @@ -21,21 +21,21 @@ import { getAssociatedTokenAddressSync, TokenAccountNotFoundError, } from '@solana/spl-token'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; import { getAccountInterface, getAtaInterface, TokenAccountSourceType, AccountInterface, -} from '../../src/v3/get-account-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; -import { createAtaInterfaceIdempotent } from '../../src/v3/actions/create-ata-interface'; -import { decompressInterface } from '../../src/v3/actions/decompress-interface'; +} from '../../../src/v3/get-account-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; +import { createAtaInterfaceIdempotent } from '../../../src/v3/actions/create-ata-interface'; +import { decompressInterface } from '../../../src/v3/actions/decompress-interface'; featureFlags.version = VERSION.V2; @@ -421,7 +421,7 @@ describe('get-account-interface', () => { // getAccountInterface auto-detect cannot find minted-compressed tokens await expect( getAccountInterface(rpc, ctokenAta, 'confirmed'), - ).rejects.toThrow(/Token account not found/); + ).rejects.toThrow(TokenAccountNotFoundError); // Use getAtaInterface for minted-compressed tokens const result = await getAtaInterface( @@ -464,7 +464,7 @@ describe('get-account-interface', () => { await expect( getAccountInterface(rpc, nonExistentAta, 'confirmed'), - ).rejects.toThrow(/Token account not found/); + ).rejects.toThrow(TokenAccountNotFoundError); }); }); }); @@ -774,7 +774,7 @@ describe('get-account-interface', () => { owner.publicKey, ctokenMint, ), - ).rejects.toThrow(/Token account not found/); + ).rejects.toThrow(TokenAccountNotFoundError); }); }); diff --git a/js/compressed-token/tests/e2e/get-mint-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/get-mint-interface.test.ts similarity index 99% rename from js/compressed-token/tests/e2e/get-mint-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/get-mint-interface.test.ts index c06745e8ca..e7dbf43995 100644 --- a/js/compressed-token/tests/e2e/get-mint-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/get-mint-interface.test.ts @@ -22,10 +22,10 @@ import { unpackMintInterface, unpackMintData, MintInterface, -} from '../../src/v3/get-mint-interface'; -import { createMintInterface } from '../../src/v3/actions'; -import { createTokenMetadata } from '../../src/v3/instructions'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/get-mint-interface'; +import { createMintInterface } from '../../../src/v3/actions'; +import { createTokenMetadata } from '../../../src/v3/instructions'; +import { findMintAddress } from '../../../src/v3/derivation'; import { serializeMint, encodeTokenMetadata, @@ -34,7 +34,7 @@ import { TokenMetadata, ExtensionType, MINT_CONTEXT_SIZE, -} from '../../src/v3/layout/layout-mint'; +} from '../../../src/v3/layout/layout-mint'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/get-or-create-ata-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/get-or-create-ata-interface.test.ts similarity index 96% rename from js/compressed-token/tests/e2e/get-or-create-ata-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/get-or-create-ata-interface.test.ts index 93c7c218f4..d8b01484e8 100644 --- a/js/compressed-token/tests/e2e/get-or-create-ata-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/get-or-create-ata-interface.test.ts @@ -19,13 +19,13 @@ import { TokenInvalidMintError, TokenInvalidOwnerError, } from '@solana/spl-token'; -import { createMintInterface } from '../../src/v3/actions/create-mint-interface'; -import { createAtaInterfaceIdempotent } from '../../src/v3/actions/create-ata-interface'; -import { getOrCreateAtaInterface } from '../../src/v3/actions/get-or-create-ata-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; -import { findMintAddress } from '../../src/v3/derivation'; -import { getAtaProgramId } from '../../src/v3/ata-utils'; -import { mintToCompressed } from '../../src/v3/actions/mint-to-compressed'; +import { createMintInterface } from '../../../src/v3/actions/create-mint-interface'; +import { createAtaInterfaceIdempotent } from '../../../src/v3/actions/create-ata-interface'; +import { getOrCreateAtaInterface } from '../../../src/v3/actions/get-or-create-ata-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; +import { getAtaProgramId } from '../../../src/v3/ata-utils'; +import { mintToCompressed } from '../../../src/v3/actions/mint-to-compressed'; featureFlags.version = VERSION.V2; @@ -806,39 +806,37 @@ describe('getOrCreateAtaInterface', () => { }); }); - describe('default programId (TOKEN_PROGRAM_ID)', () => { - let splMint: PublicKey; + describe('default programId (CTOKEN_PROGRAM_ID)', () => { + let ctokenMint: PublicKey; beforeAll(async () => { const mintAuthority = Keypair.generate(); - splMint = await createMint( + const result = await createMintInterface( rpc, payer, - mintAuthority.publicKey, + mintAuthority, null, 9, - undefined, - undefined, - TOKEN_PROGRAM_ID, ); + ctokenMint = result.mint; }); - it('should default to TOKEN_PROGRAM_ID when programId not specified', async () => { + it('should default to CTOKEN_PROGRAM_ID when programId not specified', async () => { const owner = Keypair.generate(); const expectedAddress = getAssociatedTokenAddressSync( - splMint, + ctokenMint, owner.publicKey, false, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID, + CTOKEN_PROGRAM_ID, + CTOKEN_PROGRAM_ID, // c-token uses CTOKEN_PROGRAM_ID as ATA program ); // Call without specifying programId const account = await getOrCreateAtaInterface( rpc, payer, - splMint, + ctokenMint, owner.publicKey, ); @@ -846,9 +844,9 @@ describe('getOrCreateAtaInterface', () => { expectedAddress.toBase58(), ); - // Verify it's owned by TOKEN_PROGRAM_ID + // Verify it's owned by CTOKEN_PROGRAM_ID const info = await rpc.getAccountInfo(expectedAddress); - expect(info?.owner.toBase58()).toBe(TOKEN_PROGRAM_ID.toBase58()); + expect(info?.owner.toBase58()).toBe(CTOKEN_PROGRAM_ID.toBase58()); }); }); diff --git a/js/compressed-token/tests/e2e/load-ata-combined.test.ts b/js/compressed-token/tests/e2e/ctoken/load-ata-combined.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/load-ata-combined.test.ts rename to js/compressed-token/tests/e2e/ctoken/load-ata-combined.test.ts index 5230e6ad9d..30a3d1f6b6 100644 --- a/js/compressed-token/tests/e2e/load-ata-combined.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/load-ata-combined.test.ts @@ -17,21 +17,21 @@ import { } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; import { createAssociatedTokenAccount, getAccount } from '@solana/spl-token'; -import { createMint, mintTo, decompress } from '../../src/actions'; +import { createMint, mintTo, decompress } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, selectTokenPoolInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; -import { loadAta as loadAtaStandard } from '../../src/v3/actions/load-ata'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; +import { loadAta as loadAtaStandard } from '../../../src/v3/actions/load-ata'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; import { loadAta as loadAtaUnified, getAssociatedTokenAddressInterface as getAssociatedTokenAddressInterfaceUnified, -} from '../../src/v3/unified'; +} from '../../../src/v3/unified'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/load-ata-spl-t22.test.ts b/js/compressed-token/tests/e2e/ctoken/load-ata-spl-t22.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/load-ata-spl-t22.test.ts rename to js/compressed-token/tests/e2e/ctoken/load-ata-spl-t22.test.ts index 14198e3a5b..3c390eb5b6 100644 --- a/js/compressed-token/tests/e2e/load-ata-spl-t22.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/load-ata-spl-t22.test.ts @@ -26,20 +26,20 @@ import { createAssociatedTokenAccount, getOrCreateAssociatedTokenAccount, } from '@solana/spl-token'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; -import { getAtaProgramId } from '../../src/v3/ata-utils'; +} from '../../../src/utils/get-token-pool-infos'; +import { getAtaProgramId } from '../../../src/v3/ata-utils'; import { loadAta, createLoadAtaInstructions, -} from '../../src/v3/actions/load-ata'; -import { checkAtaAddress } from '../../src/v3/ata-utils'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; +} from '../../../src/v3/actions/load-ata'; +import { checkAtaAddress } from '../../../src/v3/ata-utils'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/load-ata-standard.test.ts b/js/compressed-token/tests/e2e/ctoken/load-ata-standard.test.ts similarity index 95% rename from js/compressed-token/tests/e2e/load-ata-standard.test.ts rename to js/compressed-token/tests/e2e/ctoken/load-ata-standard.test.ts index 0f8791cc24..3d1cfed81d 100644 --- a/js/compressed-token/tests/e2e/load-ata-standard.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/load-ata-standard.test.ts @@ -22,23 +22,24 @@ import { TOKEN_PROGRAM_ID, createAssociatedTokenAccount, getAccount, + TokenAccountNotFoundError, } from '@solana/spl-token'; -import { createMint, mintTo, decompress } from '../../src/actions'; +import { createMint, mintTo, decompress } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, selectTokenPoolInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; import { loadAta, createLoadAtaInstructions, createLoadAtaInstructionsFromInterface, -} from '../../src/v3/actions/load-ata'; -import { getAtaInterface } from '../../src/v3/get-account-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; -import { createAtaInterfaceIdempotent } from '../../src/v3/actions/create-ata-interface'; +} from '../../../src/v3/actions/load-ata'; +import { getAtaInterface } from '../../../src/v3/get-account-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; +import { createAtaInterfaceIdempotent } from '../../../src/v3/actions/create-ata-interface'; featureFlags.version = VERSION.V2; @@ -301,7 +302,7 @@ describe('loadAta - Standard Path (wrap=false)', () => { mint, payer.publicKey, ), - ).rejects.toThrow('Token account not found'); + ).rejects.toThrow(TokenAccountNotFoundError); }); it('should return empty when hot exists but no cold', async () => { diff --git a/js/compressed-token/tests/e2e/load-ata-unified.test.ts b/js/compressed-token/tests/e2e/ctoken/load-ata-unified.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/load-ata-unified.test.ts rename to js/compressed-token/tests/e2e/ctoken/load-ata-unified.test.ts index 1cb71d3fd4..d64f2c4a79 100644 --- a/js/compressed-token/tests/e2e/load-ata-unified.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/load-ata-unified.test.ts @@ -22,20 +22,21 @@ import { createAssociatedTokenAccount, getOrCreateAssociatedTokenAccount, getAccount, + TokenAccountNotFoundError, } from '@solana/spl-token'; -import { createMint, mintTo, decompress } from '../../src/actions'; +import { createMint, mintTo, decompress } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, selectTokenPoolInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; import { loadAta as loadAtaUnified, createLoadAtaInstructions as createLoadAtaInstructionsUnified, getAssociatedTokenAddressInterface as getAssociatedTokenAddressInterfaceUnified, -} from '../../src/v3/unified'; +} from '../../../src/v3/unified'; featureFlags.version = VERSION.V2; @@ -361,7 +362,7 @@ describe('loadAta - Unified Path (wrap=true)', () => { owner as unknown as Signer, mint, ), - ).rejects.toThrow('Token account not found'); + ).rejects.toThrow(TokenAccountNotFoundError); }); it('should return null when only hot balance exists', async () => { diff --git a/js/compressed-token/tests/e2e/mint-to-compressed.test.ts b/js/compressed-token/tests/e2e/ctoken/mint-to-compressed.test.ts similarity index 93% rename from js/compressed-token/tests/e2e/mint-to-compressed.test.ts rename to js/compressed-token/tests/e2e/ctoken/mint-to-compressed.test.ts index da0fcba554..30a651fc32 100644 --- a/js/compressed-token/tests/e2e/mint-to-compressed.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/mint-to-compressed.test.ts @@ -14,10 +14,10 @@ import { CTOKEN_PROGRAM_ID, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { createMintInterface } from '../../src/v3/actions/create-mint-interface'; -import { mintToCompressed } from '../../src/v3/actions/mint-to-compressed'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +import { createMintInterface } from '../../../src/v3/actions/create-mint-interface'; +import { mintToCompressed } from '../../../src/v3/actions/mint-to-compressed'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/mint-to-ctoken.test.ts b/js/compressed-token/tests/e2e/ctoken/mint-to-ctoken.test.ts similarity index 89% rename from js/compressed-token/tests/e2e/mint-to-ctoken.test.ts rename to js/compressed-token/tests/e2e/ctoken/mint-to-ctoken.test.ts index 18dca890f5..9e10c4eb04 100644 --- a/js/compressed-token/tests/e2e/mint-to-ctoken.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/mint-to-ctoken.test.ts @@ -13,14 +13,14 @@ import { featureFlags, CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; -import { createMintInterface } from '../../src/v3/actions/create-mint-interface'; -import { mintTo } from '../../src/v3/actions/mint-to'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { createAssociatedCTokenAccount } from '../../src/v3/actions/create-associated-ctoken'; +import { createMintInterface } from '../../../src/v3/actions/create-mint-interface'; +import { mintTo } from '../../../src/v3/actions/mint-to'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { createAssociatedCTokenAccount } from '../../../src/v3/actions/create-associated-ctoken'; import { getAssociatedCTokenAddress, findMintAddress, -} from '../../src/v3/derivation'; +} from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/mint-to-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/mint-to-interface.test.ts similarity index 96% rename from js/compressed-token/tests/e2e/mint-to-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/mint-to-interface.test.ts index 697a09cea2..f36bccd71b 100644 --- a/js/compressed-token/tests/e2e/mint-to-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/mint-to-interface.test.ts @@ -16,12 +16,12 @@ import { TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMintInterface } from '../../src/v3/actions/create-mint-interface'; -import { mintToInterface } from '../../src/v3/actions/mint-to-interface'; -import { createMint } from '../../src/actions/create-mint'; -import { createAssociatedCTokenAccount } from '../../src/v3/actions/create-associated-ctoken'; -import { getAssociatedCTokenAddress } from '../../src/v3/derivation'; -import { getAccountInterface } from '../../src/v3/get-account-interface'; +import { createMintInterface } from '../../../src/v3/actions/create-mint-interface'; +import { mintToInterface } from '../../../src/v3/actions/mint-to-interface'; +import { createMint } from '../../../src/actions/create-mint'; +import { createAssociatedCTokenAccount } from '../../../src/v3/actions/create-associated-ctoken'; +import { getAssociatedCTokenAddress } from '../../../src/v3/derivation'; +import { getAccountInterface } from '../../../src/v3/get-account-interface'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/mint-workflow.test.ts b/js/compressed-token/tests/e2e/ctoken/mint-workflow.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/mint-workflow.test.ts rename to js/compressed-token/tests/e2e/ctoken/mint-workflow.test.ts index 45e8adc48d..db5328abee 100644 --- a/js/compressed-token/tests/e2e/mint-workflow.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/mint-workflow.test.ts @@ -9,20 +9,20 @@ import { getDefaultAddressTreeInfo, CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; -import { createMintInterface } from '../../src/v3/actions'; -import { createTokenMetadata } from '../../src/v3/instructions'; +import { createMintInterface } from '../../../src/v3/actions'; +import { createTokenMetadata } from '../../../src/v3/instructions'; import { updateMintAuthority, updateFreezeAuthority, -} from '../../src/v3/actions/update-mint'; +} from '../../../src/v3/actions/update-mint'; import { updateMetadataField, updateMetadataAuthority, -} from '../../src/v3/actions/update-metadata'; -import { createAtaInterfaceIdempotent } from '../../src/v3/actions/create-ata-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/actions/update-metadata'; +import { createAtaInterfaceIdempotent } from '../../../src/v3/actions/create-ata-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/payment-flows.test.ts b/js/compressed-token/tests/e2e/ctoken/payment-flows.test.ts similarity index 96% rename from js/compressed-token/tests/e2e/payment-flows.test.ts rename to js/compressed-token/tests/e2e/ctoken/payment-flows.test.ts index 5f962c95b8..aa99d8ed87 100644 --- a/js/compressed-token/tests/e2e/payment-flows.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/payment-flows.test.ts @@ -25,22 +25,22 @@ import { sendAndConfirmTx, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; -import { getAtaInterface } from '../../src/v3/get-account-interface'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; -import { getOrCreateAtaInterface } from '../../src/v3/actions/get-or-create-ata-interface'; -import { transferInterface } from '../../src/v3/actions/transfer-interface'; +} from '../../../src/utils/get-token-pool-infos'; +import { getAtaInterface } from '../../../src/v3/get-account-interface'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; +import { getOrCreateAtaInterface } from '../../../src/v3/actions/get-or-create-ata-interface'; +import { transferInterface } from '../../../src/v3/actions/transfer-interface'; import { createLoadAccountsParams, loadAta, -} from '../../src/v3/actions/load-ata'; -import { createTransferInterfaceInstruction } from '../../src/v3/instructions/transfer-interface'; -import { createAssociatedTokenAccountInterfaceIdempotentInstruction } from '../../src/v3/instructions/create-ata-interface'; +} from '../../../src/v3/actions/load-ata'; +import { createTransferInterfaceInstruction } from '../../../src/v3/instructions/transfer-interface'; +import { createAssociatedTokenAccountInterfaceIdempotentInstruction } from '../../../src/v3/instructions/create-ata-interface'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/transfer-interface.test.ts b/js/compressed-token/tests/e2e/ctoken/transfer-interface.test.ts similarity index 92% rename from js/compressed-token/tests/e2e/transfer-interface.test.ts rename to js/compressed-token/tests/e2e/ctoken/transfer-interface.test.ts index 90e03a5266..1089b2b734 100644 --- a/js/compressed-token/tests/e2e/transfer-interface.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/transfer-interface.test.ts @@ -12,23 +12,23 @@ import { featureFlags, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; -import { getAssociatedTokenAddressInterface } from '../../src/v3/get-associated-token-address-interface'; -import { getOrCreateAtaInterface } from '../../src/v3/actions/get-or-create-ata-interface'; -import { transferInterface } from '../../src/v3/actions/transfer-interface'; +} from '../../../src/utils/get-token-pool-infos'; +import { getAssociatedTokenAddressInterface } from '../../../src/v3/get-associated-token-address-interface'; +import { getOrCreateAtaInterface } from '../../../src/v3/actions/get-or-create-ata-interface'; +import { transferInterface } from '../../../src/v3/actions/transfer-interface'; import { loadAta, createLoadAtaInstructions, -} from '../../src/v3/actions/load-ata'; +} from '../../../src/v3/actions/load-ata'; import { createTransferInterfaceInstruction, createCTokenTransferInstruction, -} from '../../src/v3/instructions/transfer-interface'; +} from '../../../src/v3/instructions/transfer-interface'; featureFlags.version = VERSION.V2; @@ -84,11 +84,10 @@ describe('transfer-interface', () => { expect(ix.keys[2].pubkey.equals(owner)).toBe(true); }); - it('should add payer as 4th account when different from owner', () => { + it('should have owner as writable (pays for top-ups)', () => { const source = Keypair.generate().publicKey; const destination = Keypair.generate().publicKey; const owner = Keypair.generate().publicKey; - const payerPk = Keypair.generate().publicKey; const amount = BigInt(1000); const ix = createCTokenTransferInstruction( @@ -96,28 +95,12 @@ describe('transfer-interface', () => { destination, owner, amount, - payerPk, - ); - - expect(ix.keys.length).toBe(4); - expect(ix.keys[3].pubkey.equals(payerPk)).toBe(true); - }); - - it('should not add payer when same as owner', () => { - const source = Keypair.generate().publicKey; - const destination = Keypair.generate().publicKey; - const owner = Keypair.generate().publicKey; - const amount = BigInt(1000); - - const ix = createCTokenTransferInstruction( - source, - destination, - owner, - amount, - owner, // payer same as owner ); expect(ix.keys.length).toBe(3); + expect(ix.keys[2].pubkey.equals(owner)).toBe(true); + expect(ix.keys[2].isSigner).toBe(true); + expect(ix.keys[2].isWritable).toBe(true); // owner pays for top-ups }); }); @@ -162,7 +145,7 @@ describe('transfer-interface', () => { const ixs = await createLoadAtaInstructions( rpc, ata, - payer.publicKey, + owner.publicKey, mint, ); @@ -201,7 +184,7 @@ describe('transfer-interface', () => { const ixs = await createLoadAtaInstructions( rpc, ata, - payer.publicKey, + owner.publicKey, mint, ); diff --git a/js/compressed-token/tests/e2e/unwrap.test.ts b/js/compressed-token/tests/e2e/ctoken/unwrap.test.ts similarity index 96% rename from js/compressed-token/tests/e2e/unwrap.test.ts rename to js/compressed-token/tests/e2e/ctoken/unwrap.test.ts index 7819948e5f..11dbdd62da 100644 --- a/js/compressed-token/tests/e2e/unwrap.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/unwrap.test.ts @@ -11,7 +11,7 @@ import { featureFlags, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo } from '../../src/actions'; +import { createMint, mintTo } from '../../../src/actions'; import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, @@ -23,12 +23,12 @@ import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; -import { createUnwrapInstruction } from '../../src/v3/instructions/unwrap'; -import { unwrap } from '../../src/v3/actions/unwrap'; -import { getAssociatedTokenAddressInterface } from '../../src'; -import { createAtaInterfaceIdempotent } from '../../src/v3/actions/create-ata-interface'; -import { getAtaProgramId } from '../../src/v3/ata-utils'; +} from '../../../src/utils/get-token-pool-infos'; +import { createUnwrapInstruction } from '../../../src/v3/instructions/unwrap'; +import { unwrap } from '../../../src/v3/actions/unwrap'; +import { getAssociatedTokenAddressInterface } from '../../../src'; +import { createAtaInterfaceIdempotent } from '../../../src/v3/actions/create-ata-interface'; +import { getAtaProgramId } from '../../../src/v3/ata-utils'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/update-metadata.test.ts b/js/compressed-token/tests/e2e/ctoken/update-metadata.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/update-metadata.test.ts rename to js/compressed-token/tests/e2e/ctoken/update-metadata.test.ts index 53d7192000..4e091bc4fc 100644 --- a/js/compressed-token/tests/e2e/update-metadata.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/update-metadata.test.ts @@ -9,15 +9,15 @@ import { getDefaultAddressTreeInfo, CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; -import { createMintInterface, updateMintAuthority } from '../../src/v3/actions'; -import { createTokenMetadata } from '../../src/v3/instructions'; +import { createMintInterface, updateMintAuthority } from '../../../src/v3/actions'; +import { createTokenMetadata } from '../../../src/v3/instructions'; import { updateMetadataField, updateMetadataAuthority, removeMetadataKey, -} from '../../src/v3/actions/update-metadata'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/actions/update-metadata'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/update-mint.test.ts b/js/compressed-token/tests/e2e/ctoken/update-mint.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/update-mint.test.ts rename to js/compressed-token/tests/e2e/ctoken/update-mint.test.ts index 27bc970a17..e080514b07 100644 --- a/js/compressed-token/tests/e2e/update-mint.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/update-mint.test.ts @@ -9,13 +9,13 @@ import { getDefaultAddressTreeInfo, CTOKEN_PROGRAM_ID, } from '@lightprotocol/stateless.js'; -import { createMintInterface } from '../../src/v3/actions'; +import { createMintInterface } from '../../../src/v3/actions'; import { updateMintAuthority, updateFreezeAuthority, -} from '../../src/v3/actions/update-mint'; -import { getMintInterface } from '../../src/v3/get-mint-interface'; -import { findMintAddress } from '../../src/v3/derivation'; +} from '../../../src/v3/actions/update-mint'; +import { getMintInterface } from '../../../src/v3/get-mint-interface'; +import { findMintAddress } from '../../../src/v3/derivation'; featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/wrap.test.ts b/js/compressed-token/tests/e2e/ctoken/wrap.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/wrap.test.ts rename to js/compressed-token/tests/e2e/ctoken/wrap.test.ts index 03638dfab4..208733ed6b 100644 --- a/js/compressed-token/tests/e2e/wrap.test.ts +++ b/js/compressed-token/tests/e2e/ctoken/wrap.test.ts @@ -12,7 +12,7 @@ import { featureFlags, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo, decompress } from '../../src/actions'; +import { createMint, mintTo, decompress } from '../../../src/actions'; import { createAssociatedTokenAccount, getOrCreateAssociatedTokenAccount, @@ -36,11 +36,11 @@ import { selectTokenPoolInfo, selectTokenPoolInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; -import { createWrapInstruction } from '../../src/v3/instructions/wrap'; -import { wrap } from '../../src/v3/actions/wrap'; -import { getAssociatedTokenAddressInterface } from '../../src'; -import { createAtaInterfaceIdempotent } from '../../src/v3/actions/create-ata-interface'; +} from '../../../src/utils/get-token-pool-infos'; +import { createWrapInstruction } from '../../../src/v3/instructions/wrap'; +import { wrap } from '../../../src/v3/actions/wrap'; +import { getAssociatedTokenAddressInterface } from '../../../src'; +import { createAtaInterfaceIdempotent } from '../../../src/v3/actions/create-ata-interface'; // Force V2 for CToken tests featureFlags.version = VERSION.V2; diff --git a/js/compressed-token/tests/e2e/approve-and-mint-to.test.ts b/js/compressed-token/tests/e2e/legacy/approve-and-mint-to.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/approve-and-mint-to.test.ts rename to js/compressed-token/tests/e2e/legacy/approve-and-mint-to.test.ts index fe52f5d701..6a93e0b066 100644 --- a/js/compressed-token/tests/e2e/approve-and-mint-to.test.ts +++ b/js/compressed-token/tests/e2e/legacy/approve-and-mint-to.test.ts @@ -6,7 +6,7 @@ import { TOKEN_PROGRAM_ID, createInitializeMint2Instruction, } from '@solana/spl-token'; -import { approveAndMintTo, createTokenPool } from '../../src/actions'; +import { approveAndMintTo, createTokenPool } from '../../../src/actions'; import { Rpc, bn, @@ -25,7 +25,7 @@ import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; async function createTestSplMint( rpc: Rpc, diff --git a/js/compressed-token/tests/e2e/compress-spl-token-account.test.ts b/js/compressed-token/tests/e2e/legacy/compress-spl-token-account.test.ts similarity index 99% rename from js/compressed-token/tests/e2e/compress-spl-token-account.test.ts rename to js/compressed-token/tests/e2e/legacy/compress-spl-token-account.test.ts index 34ac9a55ce..cfc816a166 100644 --- a/js/compressed-token/tests/e2e/compress-spl-token-account.test.ts +++ b/js/compressed-token/tests/e2e/legacy/compress-spl-token-account.test.ts @@ -14,7 +14,7 @@ import { decompress, mintTo, compressSplTokenAccount, -} from '../../src/actions'; +} from '../../../src/actions'; import { createAssociatedTokenAccount, mintToChecked, @@ -25,7 +25,7 @@ import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; const TEST_TOKEN_DECIMALS = 2; diff --git a/js/compressed-token/tests/e2e/compress.test.ts b/js/compressed-token/tests/e2e/legacy/compress.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/compress.test.ts rename to js/compressed-token/tests/e2e/legacy/compress.test.ts index e72c6ec640..fbaaab09ba 100644 --- a/js/compressed-token/tests/e2e/compress.test.ts +++ b/js/compressed-token/tests/e2e/legacy/compress.test.ts @@ -23,19 +23,19 @@ import { createMint, createTokenProgramLookupTable, decompress, -} from '../../src/actions'; -import { mintTo } from '../../src'; +} from '../../../src/actions'; +import { mintTo } from '../../../src'; import { createAssociatedTokenAccount, TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token'; -import { CompressedTokenProgram } from '../../src/program'; +import { CompressedTokenProgram } from '../../../src/program'; import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; /** * Assert that we created recipient and change ctokens for the sender, with all diff --git a/js/compressed-token/tests/e2e/create-mint.test.ts b/js/compressed-token/tests/e2e/legacy/create-mint.test.ts similarity index 96% rename from js/compressed-token/tests/e2e/create-mint.test.ts rename to js/compressed-token/tests/e2e/legacy/create-mint.test.ts index c43e46af96..638478271b 100644 --- a/js/compressed-token/tests/e2e/create-mint.test.ts +++ b/js/compressed-token/tests/e2e/legacy/create-mint.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, beforeAll, assert } from 'vitest'; -import { CompressedTokenProgram } from '../../src/program'; +import { CompressedTokenProgram } from '../../../src/program'; import { PublicKey, Signer, Keypair } from '@solana/web3.js'; import { unpackMint, unpackAccount } from '@solana/spl-token'; -import { createMint } from '../../src/actions'; +import { createMint } from '../../../src/actions'; import { Rpc, newAccountWithLamports, diff --git a/js/compressed-token/tests/e2e/create-token-pool.test.ts b/js/compressed-token/tests/e2e/legacy/create-token-pool.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/create-token-pool.test.ts rename to js/compressed-token/tests/e2e/legacy/create-token-pool.test.ts index a0ff075550..a411bc2cfb 100644 --- a/js/compressed-token/tests/e2e/create-token-pool.test.ts +++ b/js/compressed-token/tests/e2e/legacy/create-token-pool.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, assert } from 'vitest'; -import { CompressedTokenProgram } from '../../src/program'; +import { CompressedTokenProgram } from '../../../src/program'; import { PublicKey, Signer, Keypair, SystemProgram } from '@solana/web3.js'; import { unpackMint, @@ -8,7 +8,7 @@ import { TOKEN_PROGRAM_ID, createInitializeMint2Instruction, } from '@solana/spl-token'; -import { addTokenPools, createMint, createTokenPool } from '../../src/actions'; +import { addTokenPools, createMint, createTokenPool } from '../../../src/actions'; import { Rpc, buildAndSignTx, @@ -19,7 +19,7 @@ import { } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; -import { getTokenPoolInfos } from '../../src/utils'; +import { getTokenPoolInfos } from '../../../src/utils'; /** * Assert that createTokenPool() creates system-pool account for external mint, diff --git a/js/compressed-token/tests/e2e/decompress-delegated.test.ts b/js/compressed-token/tests/e2e/legacy/decompress-delegated.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/decompress-delegated.test.ts rename to js/compressed-token/tests/e2e/legacy/decompress-delegated.test.ts index f1b62f65e2..43277cc754 100644 --- a/js/compressed-token/tests/e2e/decompress-delegated.test.ts +++ b/js/compressed-token/tests/e2e/legacy/decompress-delegated.test.ts @@ -16,14 +16,14 @@ import { mintTo, approve, decompressDelegated, -} from '../../src/actions'; +} from '../../../src/actions'; import { createAssociatedTokenAccount } from '@solana/spl-token'; import { getTokenPoolInfos, selectTokenPoolInfo, selectTokenPoolInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; interface BalanceInfo { delegate: ParsedTokenAccount[]; diff --git a/js/compressed-token/tests/e2e/decompress.test.ts b/js/compressed-token/tests/e2e/legacy/decompress.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/decompress.test.ts rename to js/compressed-token/tests/e2e/legacy/decompress.test.ts index b3ec1400ca..59dd590b74 100644 --- a/js/compressed-token/tests/e2e/decompress.test.ts +++ b/js/compressed-token/tests/e2e/legacy/decompress.test.ts @@ -12,14 +12,14 @@ import { TreeInfo, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo, decompress } from '../../src/actions'; +import { createMint, mintTo, decompress } from '../../../src/actions'; import { createAssociatedTokenAccount } from '@solana/spl-token'; import { getTokenPoolInfos, selectTokenPoolInfo, selectTokenPoolInfosForDecompression, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; /** * Assert that we created recipient and change ctokens for the sender, with all diff --git a/js/compressed-token/tests/e2e/delegate.test.ts b/js/compressed-token/tests/e2e/legacy/delegate.test.ts similarity index 99% rename from js/compressed-token/tests/e2e/delegate.test.ts rename to js/compressed-token/tests/e2e/legacy/delegate.test.ts index 7505b16bc0..9fcfd6255f 100644 --- a/js/compressed-token/tests/e2e/delegate.test.ts +++ b/js/compressed-token/tests/e2e/legacy/delegate.test.ts @@ -18,12 +18,12 @@ import { revoke, transfer, transferDelegated, -} from '../../src/actions'; +} from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; /** * Verifies token delegation by checking pre and post account counts and balances. diff --git a/js/compressed-token/tests/e2e/layout.test.ts b/js/compressed-token/tests/e2e/legacy/layout.test.ts similarity index 99% rename from js/compressed-token/tests/e2e/layout.test.ts rename to js/compressed-token/tests/e2e/legacy/layout.test.ts index a2f6f14c27..5f1478204c 100644 --- a/js/compressed-token/tests/e2e/layout.test.ts +++ b/js/compressed-token/tests/e2e/legacy/layout.test.ts @@ -38,7 +38,7 @@ import { selectSplInterfaceInfosForDecompression, TokenPoolInfo, toTokenPoolInfo, -} from '../../src/'; +} from '../../../src/'; import { Keypair } from '@solana/web3.js'; import { Connection } from '@solana/web3.js'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; diff --git a/js/compressed-token/tests/e2e/merge-token-accounts.test.ts b/js/compressed-token/tests/e2e/legacy/merge-token-accounts.test.ts similarity index 63% rename from js/compressed-token/tests/e2e/merge-token-accounts.test.ts rename to js/compressed-token/tests/e2e/legacy/merge-token-accounts.test.ts index e63a4a7434..d0a83e27d9 100644 --- a/js/compressed-token/tests/e2e/merge-token-accounts.test.ts +++ b/js/compressed-token/tests/e2e/legacy/merge-token-accounts.test.ts @@ -11,7 +11,7 @@ import { } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo, mergeTokenAccounts } from '../../src/actions'; +import { createMint, mintTo, mergeTokenAccounts } from '../../../src/actions'; describe('mergeTokenAccounts', () => { let rpc: Rpc; @@ -57,7 +57,7 @@ describe('mergeTokenAccounts', () => { } }); - it.only('should merge all token accounts', async () => { + it('should merge all token accounts', async () => { const preAccounts = await rpc.getCompressedTokenAccountsByOwner( owner.publicKey, { mint }, @@ -79,36 +79,4 @@ describe('mergeTokenAccounts', () => { ); expect(totalBalance.toNumber()).to.equal(500); // 5 accounts * 100 tokens each }); - - // TODO: add coverage for this apparent edge case. not required for now though. - it('should handle merging when there is only one account', async () => { - try { - await mergeTokenAccounts(rpc, payer, mint, owner); - console.log('First merge succeeded'); - - const postFirstMergeAccounts = - await rpc.getCompressedTokenAccountsByOwner(owner.publicKey, { - mint, - }); - console.log('Accounts after first merge:', postFirstMergeAccounts); - } catch (error) { - console.error('First merge failed:', error); - throw error; - } - - // Second merge attempt - try { - await mergeTokenAccounts(rpc, payer, mint, owner); - console.log('Second merge succeeded'); - } catch (error) { - console.error('Second merge failed:', error); - } - - const finalAccounts = await rpc.getCompressedTokenAccountsByOwner( - owner.publicKey, - { mint }, - ); - console.log('Final accounts:', finalAccounts); - expect(finalAccounts.items.length).to.equal(1); - }); }); diff --git a/js/compressed-token/tests/e2e/mint-to.test.ts b/js/compressed-token/tests/e2e/legacy/mint-to.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/mint-to.test.ts rename to js/compressed-token/tests/e2e/legacy/mint-to.test.ts index 57e7b80483..33a340166a 100644 --- a/js/compressed-token/tests/e2e/mint-to.test.ts +++ b/js/compressed-token/tests/e2e/legacy/mint-to.test.ts @@ -10,7 +10,7 @@ import { createMint, createTokenProgramLookupTable, mintTo, -} from '../../src/actions'; +} from '../../../src/actions'; import { getTestKeypair, newAccountWithLamports, @@ -24,13 +24,13 @@ import { selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { CompressedTokenProgram } from '../../src/program'; +import { CompressedTokenProgram } from '../../../src/program'; import { WasmFactory } from '@lightprotocol/hasher.rs'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; /** * Asserts that mintTo() creates a new compressed token account for the diff --git a/js/compressed-token/tests/e2e/multi-pool.test.ts b/js/compressed-token/tests/e2e/legacy/multi-pool.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/multi-pool.test.ts rename to js/compressed-token/tests/e2e/legacy/multi-pool.test.ts index e25a52538e..a84a1583a6 100644 --- a/js/compressed-token/tests/e2e/multi-pool.test.ts +++ b/js/compressed-token/tests/e2e/legacy/multi-pool.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, assert } from 'vitest'; -import { CompressedTokenProgram } from '../../src/program'; +import { CompressedTokenProgram } from '../../../src/program'; import { PublicKey, Signer, Keypair, SystemProgram } from '@solana/web3.js'; import { MINT_SIZE, @@ -14,7 +14,7 @@ import { createMint, createTokenPool, decompress, -} from '../../src/actions'; +} from '../../../src/actions'; import { Rpc, buildAndSignTx, @@ -30,7 +30,7 @@ import { getTokenPoolInfos, selectTokenPoolInfo, selectTokenPoolInfosForDecompression, -} from '../../src/utils'; +} from '../../../src/utils'; async function createTestSplMint( rpc: Rpc, diff --git a/js/compressed-token/tests/e2e/rpc-multi-trees.test.ts b/js/compressed-token/tests/e2e/legacy/rpc-multi-trees.test.ts similarity index 97% rename from js/compressed-token/tests/e2e/rpc-multi-trees.test.ts rename to js/compressed-token/tests/e2e/legacy/rpc-multi-trees.test.ts index 254b777cfd..8fe9c623db 100644 --- a/js/compressed-token/tests/e2e/rpc-multi-trees.test.ts +++ b/js/compressed-token/tests/e2e/legacy/rpc-multi-trees.test.ts @@ -9,12 +9,12 @@ import { featureFlags, selectStateTreeInfo, } from '@lightprotocol/stateless.js'; -import { createMint, mintTo, transfer } from '../../src/actions'; +import { createMint, mintTo, transfer } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; const TEST_TOKEN_DECIMALS = 2; diff --git a/js/compressed-token/tests/e2e/rpc-token-interop.test.ts b/js/compressed-token/tests/e2e/legacy/rpc-token-interop.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/rpc-token-interop.test.ts rename to js/compressed-token/tests/e2e/legacy/rpc-token-interop.test.ts index 6bdcdfc7c1..5a060f35fe 100644 --- a/js/compressed-token/tests/e2e/rpc-token-interop.test.ts +++ b/js/compressed-token/tests/e2e/legacy/rpc-token-interop.test.ts @@ -11,12 +11,12 @@ import { selectStateTreeInfo, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo, transfer } from '../../src/actions'; +import { createMint, mintTo, transfer } from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; const TEST_TOKEN_DECIMALS = 2; diff --git a/js/compressed-token/tests/e2e/select-accounts.test.ts b/js/compressed-token/tests/e2e/legacy/select-accounts.test.ts similarity index 99% rename from js/compressed-token/tests/e2e/select-accounts.test.ts rename to js/compressed-token/tests/e2e/legacy/select-accounts.test.ts index 4a989c3639..1d03ab677f 100644 --- a/js/compressed-token/tests/e2e/select-accounts.test.ts +++ b/js/compressed-token/tests/e2e/legacy/select-accounts.test.ts @@ -7,8 +7,8 @@ import { selectMinCompressedTokenAccountsForTransferOrPartial, selectSmartCompressedTokenAccountsForTransfer, selectSmartCompressedTokenAccountsForTransferOrPartial, -} from '../../src'; -import { ERROR_NO_ACCOUNTS_FOUND } from '../../src/utils/select-input-accounts'; +} from '../../../src'; +import { ERROR_NO_ACCOUNTS_FOUND } from '../../../src/utils/select-input-accounts'; describe('selectMinCompressedTokenAccountsForTransfer', () => { it('min: should select the largest account for a valid transfer where 1 account is enough', () => { diff --git a/js/compressed-token/tests/e2e/transfer-delegated.test.ts b/js/compressed-token/tests/e2e/legacy/transfer-delegated.test.ts similarity index 99% rename from js/compressed-token/tests/e2e/transfer-delegated.test.ts rename to js/compressed-token/tests/e2e/legacy/transfer-delegated.test.ts index be2d66d2da..fd8eb88bb3 100644 --- a/js/compressed-token/tests/e2e/transfer-delegated.test.ts +++ b/js/compressed-token/tests/e2e/legacy/transfer-delegated.test.ts @@ -16,12 +16,12 @@ import { mintTo, approve, transferDelegated, -} from '../../src/actions'; +} from '../../../src/actions'; import { getTokenPoolInfos, selectTokenPoolInfo, TokenPoolInfo, -} from '../../src/utils/get-token-pool-infos'; +} from '../../../src/utils/get-token-pool-infos'; const assertPostTransfer = async ( rpc: Rpc, diff --git a/js/compressed-token/tests/e2e/transfer.test.ts b/js/compressed-token/tests/e2e/legacy/transfer.test.ts similarity index 98% rename from js/compressed-token/tests/e2e/transfer.test.ts rename to js/compressed-token/tests/e2e/legacy/transfer.test.ts index 98379e5174..4dce822948 100644 --- a/js/compressed-token/tests/e2e/transfer.test.ts +++ b/js/compressed-token/tests/e2e/legacy/transfer.test.ts @@ -20,10 +20,10 @@ import { selectStateTreeInfo, } from '@lightprotocol/stateless.js'; import { WasmFactory } from '@lightprotocol/hasher.rs'; -import { createMint, mintTo, transfer } from '../../src/actions'; +import { createMint, mintTo, transfer } from '../../../src/actions'; import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; -import { CompressedTokenProgram } from '../../src/program'; -import { selectMinCompressedTokenAccountsForTransfer } from '../../src/utils/select-input-accounts'; +import { CompressedTokenProgram } from '../../../src/program'; +import { selectMinCompressedTokenAccountsForTransfer } from '../../../src/utils/select-input-accounts'; /** * Assert that we created recipient and change-account for the sender, with all diff --git a/js/compressed-token/tests/setup/version-check.ts b/js/compressed-token/tests/unit/setup/version-check.ts similarity index 85% rename from js/compressed-token/tests/setup/version-check.ts rename to js/compressed-token/tests/unit/setup/version-check.ts index 9c1fdaedba..ac61d684ff 100644 --- a/js/compressed-token/tests/setup/version-check.ts +++ b/js/compressed-token/tests/unit/setup/version-check.ts @@ -1,4 +1,4 @@ -import { validateVersionConsistency } from '../../src/utils/version-check'; +import { validateVersionConsistency } from '../../../src/utils/version-check'; // Only used in tests. export default function setup() { diff --git a/js/compressed-token/tests/unit/unified-guards.test.ts b/js/compressed-token/tests/unit/unified-guards.test.ts index da122aca45..76fb19a81d 100644 --- a/js/compressed-token/tests/unit/unified-guards.test.ts +++ b/js/compressed-token/tests/unit/unified-guards.test.ts @@ -60,7 +60,7 @@ describe('unified guards', () => { await expect( unifiedCreateLoadAtaInstructions(rpc, wrongAta, owner, mint, owner), ).rejects.toThrow( - 'Unified loadAta expects ATA to be derived from c-token program. Derive it with getAssociatedTokenAddressInterface.', + 'For wrap=true, ata must be the c-token ATA. Got spl ATA instead.', ); }); }); diff --git a/js/stateless.js/package.json b/js/stateless.js/package.json index 5cfdb89e4f..b7a3e88bff 100644 --- a/js/stateless.js/package.json +++ b/js/stateless.js/package.json @@ -1,6 +1,6 @@ { "name": "@lightprotocol/stateless.js", - "version": "0.22.1-alpha.1", + "version": "0.22.1-alpha.2", "description": "JavaScript API for Light & ZK Compression", "sideEffects": false, "main": "dist/cjs/node/index.cjs", @@ -103,7 +103,7 @@ "test:e2e:rpc-interop": "pnpm test-validator && vitest run tests/e2e/rpc-interop.test.ts --reporter=verbose --bail=1", "test:e2e:rpc-multi-trees": "pnpm test-validator && vitest run tests/e2e/rpc-multi-trees.test.ts --reporter=verbose --bail=1", "test:e2e:browser": "pnpm playwright test", - "test:e2e:all": "pnpm test-validator && vitest run tests/e2e/test-rpc.test.ts && vitest run tests/e2e/compress.test.ts && vitest run tests/e2e/transfer.test.ts && vitest run tests/e2e/rpc-interop.test.ts && pnpm test-validator-skip-prover && vitest run tests/e2e/rpc-multi-trees.test.ts && vitest run tests/e2e/layout.test.ts && vitest run tests/e2e/safe-conversion.test.ts", + "test:e2e:all": "pnpm test-validator && vitest run tests/e2e/test-rpc.test.ts && vitest run tests/e2e/compress.test.ts && vitest run tests/e2e/transfer.test.ts && vitest run tests/e2e/rpc-interop.test.ts && vitest run tests/e2e/interface-methods.test.ts && pnpm test-validator-skip-prover && vitest run tests/e2e/rpc-multi-trees.test.ts && vitest run tests/e2e/layout.test.ts && vitest run tests/e2e/safe-conversion.test.ts", "test:index": "vitest run tests/e2e/program.test.ts", "test:e2e:layout": "vitest run tests/e2e/layout.test.ts --reporter=verbose", "test:e2e:safe-conversion": "vitest run tests/e2e/safe-conversion.test.ts --reporter=verbose", diff --git a/js/stateless.js/src/constants.ts b/js/stateless.js/src/constants.ts index 1b26e9c243..cd73f8be24 100644 --- a/js/stateless.js/src/constants.ts +++ b/js/stateless.js/src/constants.ts @@ -27,8 +27,8 @@ export const featureFlags = { ) { return process.env.LIGHT_PROTOCOL_VERSION as VERSION; } - // Default to V1 - return VERSION.V1; + // Default to V2 + return VERSION.V2; })(), isV2: () => featureFlags.version.replace(/['"]/g, '').toUpperCase() === 'V2', @@ -145,79 +145,6 @@ export const isLocalTest = (url: string) => { return url.includes('localhost') || url.includes('127.0.0.1'); }; -/** - * @internal - */ -export const localTestActiveStateTreeInfos = (): TreeInfo[] => { - return [ - { - tree: new PublicKey(merkletreePubkey), - queue: new PublicKey(nullifierQueuePubkey), - cpiContext: new PublicKey(cpiContextPubkey), - treeType: TreeType.StateV1, - nextTreeInfo: null, - }, - { - tree: new PublicKey(merkleTree2Pubkey), - queue: new PublicKey(nullifierQueue2Pubkey), - cpiContext: new PublicKey(cpiContext2Pubkey), - treeType: TreeType.StateV1, - nextTreeInfo: null, - }, - { - tree: new PublicKey(batchMerkleTree1), - queue: new PublicKey(batchQueue1), - cpiContext: new PublicKey(batchCpiContext1), - treeType: TreeType.StateV2, - nextTreeInfo: null, - }, - { - tree: new PublicKey(batchMerkleTree2), - queue: new PublicKey(batchQueue2), - cpiContext: new PublicKey(batchCpiContext2), - treeType: TreeType.StateV2, - nextTreeInfo: null, - }, - { - tree: new PublicKey(batchMerkleTree3), - queue: new PublicKey(batchQueue3), - cpiContext: new PublicKey(batchCpiContext3), - treeType: TreeType.StateV2, - nextTreeInfo: null, - }, - { - tree: new PublicKey(batchMerkleTree4), - queue: new PublicKey(batchQueue4), - cpiContext: new PublicKey(batchCpiContext4), - treeType: TreeType.StateV2, - nextTreeInfo: null, - }, - { - tree: new PublicKey(batchMerkleTree5), - queue: new PublicKey(batchQueue5), - cpiContext: new PublicKey(batchCpiContext5), - treeType: TreeType.StateV2, - nextTreeInfo: null, - }, - { - tree: new PublicKey(batchAddressTree), - queue: new PublicKey(batchAddressTree), // v2 address queue is part of the tree account. - cpiContext: PublicKey.default, - treeType: TreeType.AddressV2, - nextTreeInfo: null, - }, - { - tree: new PublicKey(testBatchAddressTree), - queue: new PublicKey(testBatchAddressTree), // v2 address queue is part of the tree account. - cpiContext: PublicKey.default, - treeType: TreeType.AddressV2, - nextTreeInfo: null, - }, - ].filter(info => - featureFlags.isV2() ? true : info.treeType === TreeType.StateV1, - ); -}; - export const getDefaultAddressSpace = () => { return getBatchAddressTreeInfo(); }; @@ -299,7 +226,7 @@ export const nullifierQueue2Pubkey = 'nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X'; export const cpiContext2Pubkey = 'cpi2cdhkH5roePvcudTgUL8ppEBfTay1desGh8G8QxK'; -// V2 testing - State Trees (5 triples) +// V2 export const batchMerkleTree1 = 'bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU'; export const batchQueue1 = 'oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto'; export const batchCpiContext1 = 'cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y'; @@ -321,14 +248,66 @@ export const batchQueue5 = 'oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P'; export const batchCpiContext5 = 'cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6'; // V2 Address Trees -export const batchAddressTree = 'amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx'; // v2 address tree (queue is part of the tree account) -export const testBatchAddressTree = - 'EzKE84aVTkCUhDHLELqyJaq1Y7UVVmqxXqZjVHwHY3rK'; // v2 address tree (queue is part of the tree account) +export const batchAddressTree = 'amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx'; // Deprecated: Use batchMerkleTree1, batchQueue1, batchCpiContext1 instead export const batchMerkleTree = batchMerkleTree1; export const batchQueue = batchQueue1; +/** + * @internal + * Returns local test tree infos. + * V1: 2 state trees (smt/nfq/cpi pairs) + * V2: 5 batched state trees (bmt/oq/cpi triplets) + 1 address tree (amt2) + */ +export const localTestActiveStateTreeInfos = (): TreeInfo[] => { + // V1 State Trees: [tree, queue, cpi] + const V1_STATE_TREES: [string, string, string][] = [ + [merkletreePubkey, nullifierQueuePubkey, cpiContextPubkey], // smt1, nfq1, cpi1 + [merkleTree2Pubkey, nullifierQueue2Pubkey, cpiContext2Pubkey], // smt2, nfq2, cpi2 + ]; + + // V2 State Trees (batched): [bmt, oq, cpi] triplets + const V2_STATE_TREES: [string, string, string][] = [ + [batchMerkleTree1, batchQueue1, batchCpiContext1], // bmt1, oq1, cpi1 + [batchMerkleTree2, batchQueue2, batchCpiContext2], // bmt2, oq2, cpi2 + [batchMerkleTree3, batchQueue3, batchCpiContext3], // bmt3, oq3, cpi3 + [batchMerkleTree4, batchQueue4, batchCpiContext4], // bmt4, oq4, cpi4 + [batchMerkleTree5, batchQueue5, batchCpiContext5], // bmt5, oq5, cpi5 + ]; + + const V2_ADDRESS_TREE = batchAddressTree; // amt2 + + const v1Trees: TreeInfo[] = V1_STATE_TREES.map(([tree, queue, cpi]) => ({ + tree: new PublicKey(tree), + queue: new PublicKey(queue), + cpiContext: new PublicKey(cpi), + treeType: TreeType.StateV1, + nextTreeInfo: null, + })); + + const v2Trees: TreeInfo[] = V2_STATE_TREES.map(([tree, queue, cpi]) => ({ + tree: new PublicKey(tree), + queue: new PublicKey(queue), + cpiContext: new PublicKey(cpi), + treeType: TreeType.StateV2, + nextTreeInfo: null, + })); + + const v2AddressTree: TreeInfo = { + tree: new PublicKey(V2_ADDRESS_TREE), + queue: new PublicKey(V2_ADDRESS_TREE), // queue is part of the tree account + cpiContext: PublicKey.default, + treeType: TreeType.AddressV2, + nextTreeInfo: null, + }; + + if (featureFlags.isV2()) { + return [...v1Trees, ...v2Trees, v2AddressTree]; + } + return v1Trees; +}; + export const confirmConfig: ConfirmOptions = { commitment: 'confirmed', preflightCommitment: 'confirmed', diff --git a/js/stateless.js/src/rpc.ts b/js/stateless.js/src/rpc.ts index 0acbb0b1bd..ee25b9d862 100644 --- a/js/stateless.js/src/rpc.ts +++ b/js/stateless.js/src/rpc.ts @@ -715,6 +715,7 @@ export class Rpc extends Connection implements CompressionApiInterface { /** * Get a list of all state tree infos. If not already cached, fetches from * the cluster. + * if featureFlags.isV2(), returns v2 trees too. */ async getStateTreeInfos(): Promise { if (isLocalTest(this.rpcEndpoint)) { diff --git a/scripts/devenv/versions.sh b/scripts/devenv/versions.sh index 394405759e..e4deec350d 100755 --- a/scripts/devenv/versions.sh +++ b/scripts/devenv/versions.sh @@ -13,7 +13,8 @@ export SOLANA_VERSION="2.2.15" export ANCHOR_VERSION="0.31.1" export JQ_VERSION="1.8.0" export PHOTON_VERSION="0.51.2" -export PHOTON_COMMIT="3dbfb8e6772779fc89c640b5b0823b95d1958efc" +# current main (ci fails): 3dbfb8e6772779fc89c640b5b0823b95d1958efc +export PHOTON_COMMIT="ac7df6c388db847b7693a7a1cb766a7c9d7809b5" export REDIS_VERSION="8.0.1" export ANCHOR_TAG="anchor-v${ANCHOR_VERSION}"