Skip to content

Conversation

ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Oct 8, 2025

Issue:

  • litesvm node likely runs out of memory and clashes with other workers
  • vitest single thread solves litesvm memory issues (this is reproducable locally if you)
  • hasher.rs clashes with litesvm and produces string to biguint parsing errors
    • only in V1 tests V2 works
  • replaced hasher.rs with noble/hashes poseidon
  • noble/hashes poseidon also produces issues (different errors in V1 and V2 tests)

Summary by CodeRabbit

  • New Features
    • Added a standalone program-test package providing a LiteSVM-backed local runtime, SPL token helpers, merkle/hash utilities, and developer test tools.
  • Tests
    • Migrated and expanded unit/integration tests to the new runtime (compression, proofs, transfers, Poseidon/hash comparisons); consolidated test targets and simplified e2e orchestration.
  • Bug Fixes
    • More robust transaction confirmation and error handling in test flows.
  • Documentation
    • Added README and usage docs for the new testing package.
  • Chores
    • CI/workspace updated to include program-test steps, runner image changed, and Node version bumped.

Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Walkthrough

Adds a new workspace package js/program-test (LiteSVM RPC, hasher, merkle-tree, SPL helpers, tests); migrates many test helpers and tests from stateless.js to @lightprotocol/program-test; updates compressed-token scripts/tests, CI workflows, and removes legacy stateless.js test-helpers and several tests.

Changes

Cohort / File(s) Summary
CI workflows
.github/workflows/js.yml, .github/workflows/js-v2.yml
Change runner to warp-ubuntu-latest-x64-4x; add Build program-test and Run program-test test steps in v1/v2 jobs.
Workspace / env
pnpm-workspace.yaml, scripts/devenv/versions.sh
Add js/program-test/** to workspace; bump NODE_VERSION to 24.4.0.
New package: program-test config & docs
js/program-test/package.json, js/program-test/rollup.config.js, js/program-test/tsconfig.json, js/program-test/vitest.config.ts, js/program-test/eslint.config.cjs, js/program-test/.prettierignore, js/program-test/README.md
Add package metadata, build/test/lint configs, README, and CI/test scripts for @lightprotocol/program-test.
program-test public API (barrel & types)
js/program-test/src/index.ts, js/program-test/src/types.ts
New barrel exports: createLiteSVMRpc, LiteSVMRpc, test utils (newAccountWithLamports, sleep, getOrCreateKeypair), merkle-tree, spl-token-utils, hasher exports and types.
LiteSVM RPC implementation
js/program-test/src/litesvm-rpc.ts
New LiteSVMRpc class and createLiteSVMRpc factory implementing an in-process LiteSVM-backed Rpc with extensive query/proof/LUT support.
Test-rpc & event parsing helpers
js/program-test/src/test-rpc/*.ts, js/program-test/src/test-rpc/index.ts
Add test-rpc, parsed-event, compressed-account/token-account parsing helpers and index re-exports for program-test.
Merkle utilities
js/program-test/src/merkle-tree/*.ts
Add IndexedArray/IndexedElement and MerkleTree implementations and re-exports.
Hasher (Noble) & constants
js/program-test/src/hasher/*
Add Poseidon constants, noble-hasher implementation and factory, and hasher index re-exports.
SPL token helpers
js/program-test/src/spl-token-utils.ts
Add splCreateMint, splCreateAssociatedTokenAccount, splMintTo, splTransfer, balance/mint info, and get-or-create ATA helpers.
General test utilities
js/program-test/src/test-utils.ts
Add newAccountWithLamports, sleep, getOrCreateKeypair helpers.
program-test tests
js/program-test/tests/*
Add unit/integration tests: LiteSVM, MerkleTree, hasher comparisons, TestRpc interop, multi-tree flows, etc.
compressed-token package updates
js/compressed-token/package.json, js/compressed-token/vitest.config.ts
Add devDependency @lightprotocol/program-test, restructure scripts to run program-test before e2e, update NX CI targets, set Vitest pool to single-thread.
compressed-token tests (e2e → program-test)
js/compressed-token/tests/e2e/*.test.ts, js/compressed-token/tests/program-test/*.test.ts
Migrate many tests to program-test-driven variants; replace WasmFactory with NobleHasherFactory; switch imports from @lightprotocol/stateless.js to @lightprotocol/program-test; add local helpers (e.g., splApproveAndMintTo); remove some e2e scenarios.
CLI & SDK test import changes
cli/test/*, sdk-tests/sdk-anchor-test/*, cli/package.json, sdk-tests/sdk-anchor-test/package.json
Switch tests to use @lightprotocol/program-test helpers (createLiteSVMRpc, newAccountWithLamports); adjust dependencies and scripts.
stateless.js build & package adjustments
js/stateless.js/package.json, js/stateless.js/rollup.config.js
Add ESM import entry, simplify e2e test orchestration scripts, add es/node rollup build and apply terser conditionally for CJS.
stateless.js runtime & utilities
js/stateless.js/src/rpc.ts, js/stateless.js/src/utils/send-and-confirm.ts
Remove LightWasm-dependent public-input/hash-chain helpers; enhance send-and-confirm with explicit error handling and commitment-threshold logic.
Remove legacy stateless.js test-helpers & tests
js/stateless.js/src/test-helpers/**, js/stateless.js/tests/**
Delete legacy test-helpers (merkle-tree, test-rpc, get-parsed-events, compressed-token helpers) and many unit/e2e tests (migrated to program-test).
Misc CI/packaging & scripts
/.github/actions/setup-and-build/action.yml, other package.json edits
Remove proving-keys caching steps; add @lightprotocol/program-test build steps to CI dependency graphs; replace some @lightprotocol/programs references with program-test.

Sequence Diagram(s)

%%{init: {"themeVariables": {"actorBkg":"#E6F4EA","actorBorder":"#2D6A4F","noteBkg":"#FFF3BF"}}}%%
sequenceDiagram
  autonumber
  actor Dev
  participant CI as GitHub Actions
  participant WS as pnpm Workspace
  participant PT as @lightprotocol/program-test
  participant CT as js/compressed-token

  Dev->>CI: push branch
  CI->>WS: install workspace (includes js/program-test)
  CI->>PT: build (pnpm run build)
  CI->>PT: run tests (pnpm test)
  CI->>CT: run `pnpm test:program-test` then e2e
  CI-->>Dev: report status
Loading
%%{init: {"themeVariables": {"actorBkg":"#F0F7FF","actorBorder":"#0B5FFF","noteBkg":"#F8F9FA"}}}%%
sequenceDiagram
  autonumber
  participant Test as Test Suite
  participant Hasher as NobleHasherFactory
  participant Lite as createLiteSVMRpc
  participant SPL as spl-token-utils
  participant Prog as CompressedTokenProgram

  Test->>Hasher: NobleHasherFactory.getInstance()
  Test->>Lite: createLiteSVMRpc(lightWasm)
  Test->>SPL: splCreateMint / splCreateAssociatedTokenAccount
  Test->>SPL: splMintTo
  Test->>Prog: invoke compress/mintTo/transfer actions
  Prog-->>Lite: emit events & update state
  Test->>Lite: query compressed accounts / proofs / balances
  Lite-->>Test: return results
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • sergeytimoshin

Poem

I nibble logs and pack the test,
LiteSVM hums—no more unrest.
Merkle roots sprout, leaves all aligned,
Tokens hop paths the rabbit designed.
CI winks green; the warren's complete. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly states that a TypeScript program test feature is being added, aligning with the main changes that introduce the program-test TypeScript package and associated tests.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/chore-ts-tests-litesvm

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82f0ed4 and 0384fbe.

📒 Files selected for processing (6)
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (15 hunks)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (5 hunks)
  • js/program-test/src/hasher/constants.ts (1 hunks)
  • js/program-test/src/hasher/noble-hasher.ts (1 hunks)
  • js/program-test/tests/poseidon-comparison.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • js/program-test/src/hasher/constants.ts
🧰 Additional context used
🧬 Code graph analysis (5)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (4)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (178-202)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (78-115)
js/compressed-token/tests/program-test/compress.test.ts (8)
js/stateless.js/src/rpc-interface.ts (1)
  • ParsedTokenAccount (236-239)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (178-202)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (78-115)
js/program-test/src/hasher/noble-hasher.ts (3)
js/program-test/src/hasher/constants.ts (3)
  • CONSTANTS_3_FLAT (96-292)
  • MDS_3 (2-36)
  • MDS_4 (38-93)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-67)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/tests/poseidon-comparison.test.ts (3)
js/program-test/src/hasher/constants.ts (2)
  • MDS_3 (2-36)
  • CONSTANTS_3_FLAT (96-292)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-67)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (178-202)
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (4)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (178-202)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/spl-token-utils.ts (2)
  • splCreateAssociatedTokenAccount (78-115)
  • splMintTo (120-149)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: cli-v1
  • GitHub Check: lint
  • GitHub Check: stateless-js-v2
  • GitHub Check: Forester e2e test
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v2
🔇 Additional comments (15)
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (15)

16-23: LGTM! Migration to program-test imports is clean.

The new imports from @lightprotocol/program-test (NobleHasherFactory, createLiteSVMRpc, newAccountWithLamports, splCreateAssociatedTokenAccount, splMintTo) and TOKEN_2022_PROGRAM_ID for token-2022 testing are appropriate and correctly replace the previous stateless.js test helpers.


43-44: LGTM! LiteSVM RPC initialization follows the correct pattern.

The test correctly initializes the LiteSVM RPC using the NobleHasherFactory singleton and passes it to createLiteSVMRpc.


64-69: LGTM! Correct usage of splCreateAssociatedTokenAccount.

The function call correctly passes all required parameters for creating an associated token account.


336-342: LGTM! Correct Token-2022 account creation.

The test correctly uses TOKEN_2022_PROGRAM_ID as the programId parameter for creating a Token-2022 associated token account.


141-148: LGTM! Correct usage of splMintTo.

The function correctly converts BN amounts to numbers using .toNumber() before passing to splMintTo, which expects number | bigint.


226-233: LGTM! Consistent splMintTo usage.


271-271: LGTM! Simplified splMintTo call.


295-295: LGTM! Consistent splMintTo usage.


115-115: LGTM! Defensive string conversion is appropriate.

The String() wrapper ensures type safety when passing RPC response amounts (which can be string | bigint) to the bn() constructor.


127-134: LGTM! Defensive type handling with clear documentation.

The inline comment explains the rationale for the string conversion pattern, which is consistently applied throughout the test.


198-200: LGTM! Consistent defensive type conversion.


216-216: LGTM! Consistent defensive type conversion.


247-247: LGTM! Consistent defensive type conversion.


384-384: LGTM! Consistent defensive type conversion.


401-407: LGTM! Well-documented defensive type conversion.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from c9b431d to 61e7512 Compare October 10, 2025 00:49
@ananas-block ananas-block marked this pull request as ready for review October 10, 2025 03:28
@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch 2 times, most recently from 7c0c42d to 52ee14d Compare October 10, 2025 03:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (8)
js/stateless.js/src/utils/send-and-confirm.ts (1)

109-113: Consider removing or replacing debug console.log.

This debug statement logs transaction status on every polling iteration. While useful during development, production code should either remove this or replace it with a proper logging framework that can be configured/disabled.

If this utility is only used in tests, consider adding a comment clarifying that. Otherwise, apply this diff to remove the debug log:

-            console.log('[confirmTx] Comparing:', {
-                statusValue,
-                expectedCommitment: commitment,
-                meetsCommitment,
-            });
-
             if (meetsCommitment) {
js/program-test/tests/compress.test.ts (1)

24-24: Consider removing or controlling console.log statements in production tests.

The test includes multiple console.log statements for debugging. While useful during development, consider either removing them or using a test logger/debug flag to control output verbosity in CI environments.

Apply this diff to remove console output:

-    console.log("Pre-compress balance:", preBalance);
+    // Pre-compress balance tracked for assertion
-    console.log("Compress signature:", signature);
-    console.log("Post-compress balance:", postBalance);
-    console.log("Compressed accounts:", compressedAccounts);
-    console.log("Compressed balance:", compressedBalance.toString());

Also applies to: 33-33, 37-37, 43-43, 52-52

js/program-test/tests/test-rpc.test.ts (3)

64-71: Avoid magic numbers; use network fee constant

Replace literal 5000s with STATE_MERKLE_TREE_NETWORK_FEE to avoid drift if constants change.

-      postCompressBalance,
-      preCompressBalance -
-        compressLamportsAmount -
-        5000 -
-        5000 -
-        STATE_MERKLE_TREE_ROLLOVER_FEE.toNumber(),
+      postCompressBalance,
+      preCompressBalance -
+        compressLamportsAmount -
+        2 * STATE_MERKLE_TREE_NETWORK_FEE.toNumber() -
+        STATE_MERKLE_TREE_ROLLOVER_FEE.toNumber(),

75-75: Remove unused variable

slot is never used.

-    const slot = await rpc.getSlot();
+    // const slot = await rpc.getSlot();

92-97: Prefer BN equality check over structural equality

Use .eq() to avoid brittleness across BN object instances.

-    expect(compressedAccountProof.hash).toStrictEqual(refHash);
-    expect(compressedAccountProof.leafIndex).toStrictEqual(
-      compressedAccounts.items[0].leafIndex,
-    );
+    expect(bn(compressedAccountProof.hash).eq(bn(refHash))).toBe(true);
+    expect(compressedAccountProof.leafIndex)
+      .toStrictEqual(compressedAccounts.items[0].leafIndex);
sdk-tests/sdk-anchor-test/tests/test_v1.ts (1)

181-185: Use getLatestBlockhash for consistency with LiteSVM RPC

Aligns with other tests and typical web3.js usage.

-  tx.recentBlockhash = (await rpc.getRecentBlockhash()).blockhash;
+  {
+    const { blockhash } = await rpc.getLatestBlockhash();
+    tx.recentBlockhash = blockhash;
+  }

If LiteSVMRpc supports both, fine; otherwise this avoids runtime mismatches.

Also applies to: 257-259, 328-331

js/program-test/src/test-rpc/get-compressed-accounts.ts (1)

41-96: Optional: speed up unspent filtering with a Set

Current O(n·m). Convert input hashes to a Set of strings first.

-  const allInputAccountHashes: BN[] = [];
+  const allInputAccountHashes: BN[] = [];
@@
-  const unspentAccounts = allOutputAccounts.filter(
-    (account) =>
-      !allInputAccountHashes.some((hash) => hash.eq(bn(account.hash))),
-  );
+  const spentSet = new Set(allInputAccountHashes.map(h => h.toString(10)));
+  const unspentAccounts = allOutputAccounts.filter(
+    (account) => !spentSet.has(bn(account.hash).toString(10)),
+  );
js/compressed-token/tests/program-test/compress.test.ts (1)

232-242: Remove redundant loop; assert once

The loop repeats the same assertion 15 times. Call assertCompress once.

-        for (let i = 0; i < recipients.length; i++) {
-            await assertCompress(
-                rpc,
-                bn(senderAtaBalanceBefore.value.amount),
-                bobAta,
-                mint,
-                amounts.slice(0, 11),
-                recipients.slice(0, 11),
-                recipientCompressedTokenBalancesBefore.map(x => x.items),
-            );
-        }
+        await assertCompress(
+            rpc,
+            bn(senderAtaBalanceBefore.value.amount),
+            bobAta,
+            mint,
+            amounts.slice(0, 11),
+            recipients.slice(0, 11),
+            recipientCompressedTokenBalancesBefore.map(x => x.items),
+        );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aeb921 and 52ee14d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (65)
  • .github/workflows/js-v2.yml (2 hunks)
  • .github/workflows/js.yml (2 hunks)
  • js/compressed-token/package.json (3 hunks)
  • js/compressed-token/tests/e2e/compress.test.ts (1 hunks)
  • js/compressed-token/tests/e2e/mint-to.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (4 hunks)
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (8 hunks)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/create-mint.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/create-token-pool.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (3 hunks)
  • js/compressed-token/tests/program-test/decompress.test.ts (3 hunks)
  • js/compressed-token/tests/program-test/delegate.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/mint-to.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/multi-pool.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/transfer-delegated.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/transfer.test.ts (4 hunks)
  • js/program-test/.prettierignore (1 hunks)
  • js/program-test/README.md (1 hunks)
  • js/program-test/package.json (1 hunks)
  • js/program-test/rollup.config.js (1 hunks)
  • js/program-test/src/index.ts (1 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/merkle-tree/index.ts (1 hunks)
  • js/program-test/src/merkle-tree/indexed-array.ts (1 hunks)
  • js/program-test/src/merkle-tree/merkle-tree.ts (1 hunks)
  • js/program-test/src/spl-token-utils.ts (1 hunks)
  • js/program-test/src/test-rpc/get-compressed-accounts.ts (1 hunks)
  • js/program-test/src/test-rpc/get-compressed-token-accounts.ts (1 hunks)
  • js/program-test/src/test-rpc/get-parsed-events.ts (1 hunks)
  • js/program-test/src/test-rpc/index.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
  • js/program-test/src/test-utils.ts (1 hunks)
  • js/program-test/src/types.ts (1 hunks)
  • js/program-test/tests/compress.test.ts (1 hunks)
  • js/program-test/tests/merkle-tree.test.ts (1 hunks)
  • js/program-test/tests/rpc-interop.test.ts (1 hunks)
  • js/program-test/tests/rpc-multi-trees.test.ts (1 hunks)
  • js/program-test/tests/test-rpc.test.ts (1 hunks)
  • js/program-test/tests/transfer.test.ts (1 hunks)
  • js/program-test/tsconfig.json (1 hunks)
  • js/program-test/vitest.config.ts (1 hunks)
  • js/stateless.js/package.json (2 hunks)
  • js/stateless.js/rollup.config.js (2 hunks)
  • js/stateless.js/src/rpc.ts (0 hunks)
  • js/stateless.js/src/test-helpers/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/indexed-array.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/merkle-tree.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-accounts.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-token-accounts.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts (0 hunks)
  • js/stateless.js/src/utils/send-and-confirm.ts (1 hunks)
  • js/stateless.js/tests/e2e/compress.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/rpc-interop.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/rpc-multi-trees.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/test-rpc.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/transfer.test.ts (0 hunks)
  • js/stateless.js/tests/unit/merkle-tree/merkle-tree.test.ts (0 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • sdk-tests/sdk-anchor-test/package.json (1 hunks)
  • sdk-tests/sdk-anchor-test/tests/test_v1.ts (4 hunks)
  • sdk-tests/sdk-anchor-test/tests/test_v2.ts (5 hunks)
💤 Files with no reviewable changes (16)
  • js/stateless.js/src/test-helpers/index.ts
  • js/stateless.js/tests/e2e/rpc-multi-trees.test.ts
  • js/stateless.js/src/test-helpers/merkle-tree/index.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-token-accounts.ts
  • js/stateless.js/src/test-helpers/test-rpc/index.ts
  • js/stateless.js/tests/e2e/transfer.test.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts
  • js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts
  • js/stateless.js/tests/e2e/test-rpc.test.ts
  • js/stateless.js/src/test-helpers/merkle-tree/indexed-array.ts
  • js/stateless.js/src/test-helpers/merkle-tree/merkle-tree.ts
  • js/stateless.js/tests/e2e/compress.test.ts
  • js/stateless.js/tests/unit/merkle-tree/merkle-tree.test.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-accounts.ts
  • js/stateless.js/tests/e2e/rpc-interop.test.ts
  • js/stateless.js/src/rpc.ts
🧰 Additional context used
🧬 Code graph analysis (29)
js/program-test/src/types.ts (1)
js/program-test/src/index.ts (2)
  • CustomProgram (14-14)
  • LiteSVMConfig (14-14)
js/stateless.js/rollup.config.js (2)
js/program-test/rollup.config.js (1)
  • rolls (7-42)
js/compressed-token/rollup.config.js (1)
  • rolls (12-83)
js/program-test/src/test-utils.ts (2)
js/program-test/src/index.ts (3)
  • newAccountWithLamports (10-10)
  • sleep (11-11)
  • getOrCreateKeypair (12-12)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/compressed-token/tests/program-test/create-token-pool.test.ts (1)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/tests/test-rpc.test.ts (5)
js/stateless.js/src/constants.ts (4)
  • defaultTestStateTreeAccounts (236-244)
  • STATE_MERKLE_TREE_ROLLOVER_FEE (338-340)
  • featureFlags (16-35)
  • STATE_MERKLE_TREE_NETWORK_FEE (356-356)
js/stateless.js/src/state/compressed-account.ts (1)
  • CompressedAccountWithMerkleContext (14-17)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/tests/compress.test.ts (3)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/compressed-token/tests/program-test/decompress.test.ts (1)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/program-test/src/merkle-tree/indexed-array.ts (3)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-68)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/constants.ts (1)
  • HIGHEST_ADDRESS_PLUS_ONE (48-50)
js/program-test/src/test-rpc/get-parsed-events.ts (5)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/stateless.js/src/state/types.ts (2)
  • PublicTransactionEvent (231-272)
  • InstructionDataInvoke (277-306)
js/stateless.js/src/constants.ts (8)
  • defaultStaticAccountsStruct (95-105)
  • accountCompressionProgram (76-77)
  • COMPUTE_BUDGET_PATTERN (52-52)
  • noopProgram (74-74)
  • INVOKE_DISCRIMINATOR (54-56)
  • INVOKE_CPI_DISCRIMINATOR (58-60)
  • INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR (62-64)
  • INSERT_INTO_QUEUES_DISCRIMINATOR (70-72)
js/stateless.js/src/programs/system/layout.ts (6)
  • decodePublicTransactionEvent (392-396)
  • decodeInstructionDataInvoke (240-246)
  • decodeInstructionDataInvokeCpi (248-254)
  • decodeInstructionDataInvokeCpiWithReadOnly (234-238)
  • deserializeAppendNullifyCreateAddressInputsIndexer (441-514)
  • convertToPublicTransactionEvent (516-598)
js/stateless.js/src/utils/conversion.ts (1)
  • convertInvokeCpiWithReadOnlyToInvoke (121-191)
js/program-test/tests/rpc-multi-trees.test.ts (6)
js/stateless.js/src/rpc.ts (2)
  • Rpc (551-1892)
  • createRpc (231-264)
js/program-test/src/test-rpc/test-rpc.ts (2)
  • TestRpc (114-928)
  • getTestRpc (85-104)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/stateless.js/src/utils/address.ts (2)
  • deriveAddressSeed (11-18)
  • deriveAddress (29-46)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/actions/create-account.ts (1)
  • createAccountWithLamports (113-175)
js/program-test/tests/merkle-tree.test.ts (4)
js/program-test/src/merkle-tree/indexed-array.ts (3)
  • IndexedElement (5-36)
  • IndexedArray (58-307)
  • newElement (300-306)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/constants.ts (1)
  • HIGHEST_ADDRESS_PLUS_ONE (48-50)
js/program-test/src/merkle-tree/merkle-tree.ts (1)
  • MerkleTree (13-214)
js/program-test/src/merkle-tree/merkle-tree.ts (1)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-68)
js/program-test/tests/rpc-interop.test.ts (9)
js/stateless.js/src/rpc.ts (2)
  • Rpc (551-1892)
  • createRpc (231-264)
js/program-test/src/test-rpc/test-rpc.ts (2)
  • TestRpc (114-928)
  • getTestRpc (85-104)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/program-test/src/test-utils.ts (2)
  • newAccountWithLamports (7-14)
  • sleep (19-21)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/utils/address.ts (2)
  • deriveAddressSeed (11-18)
  • deriveAddress (29-46)
js/stateless.js/src/actions/create-account.ts (1)
  • createAccountWithLamports (113-175)
js/stateless.js/src/constants.ts (2)
  • featureFlags (16-35)
  • getDefaultAddressTreeInfo (221-229)
js/stateless.js/src/utils/send-and-confirm.ts (1)
forester-utils/src/rpc_pool.rs (1)
  • commitment (152-155)
js/compressed-token/tests/program-test/multi-pool.test.ts (3)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
cli/test/helpers/helpers.ts (1)
  • createTestSplMint (71-103)
js/program-test/src/spl-token-utils.ts (2)
  • splGetOrCreateAssociatedTokenAccount (246-294)
  • splMintTo (115-142)
js/program-test/rollup.config.js (1)
js/stateless.js/rollup.config.js (2)
  • rolls (11-68)
  • typesConfig (70-74)
js/program-test/src/litesvm-rpc.ts (4)
js/program-test/src/index.ts (3)
  • LiteSVMRpc (8-8)
  • LiteSVMConfig (14-14)
  • createLiteSVMRpc (8-8)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (114-928)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (1)
  • defaultTestStateTreeAccounts (236-244)
sdk-tests/sdk-anchor-test/tests/test_v2.ts (3)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/index.ts (2)
  • createLiteSVMRpc (8-8)
  • newAccountWithLamports (10-10)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/program-test/src/test-rpc/get-compressed-token-accounts.ts (8)
js/stateless.js/src/rpc-interface.ts (2)
  • ParsedTokenAccount (236-239)
  • WithCursor (250-255)
js/stateless.js/src/state/types.ts (2)
  • CompressedAccountLegacy (146-163)
  • PublicTransactionEvent (231-272)
js/stateless.js/src/constants.ts (2)
  • COMPRESSED_TOKEN_PROGRAM_ID (256-258)
  • featureFlags (16-35)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/stateless.js/src/state/compressed-account.ts (2)
  • MerkleContext (80-97)
  • createCompressedAccountWithMerkleContextLegacy (240-253)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-140)
js/program-test/src/test-rpc/get-compressed-accounts.ts (5)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/stateless.js/src/state/compressed-account.ts (3)
  • CompressedAccountWithMerkleContext (14-17)
  • MerkleContext (80-97)
  • createCompressedAccountWithMerkleContextLegacy (240-253)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-140)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/compressed-token/tests/program-test/compress.test.ts (10)
js/stateless.js/src/rpc-interface.ts (1)
  • ParsedTokenAccount (236-239)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/stateless.js/src/utils/dedupe-signer.ts (1)
  • dedupeSigner (4-11)
js/stateless.js/src/utils/send-and-confirm.ts (2)
  • buildAndSignTx (139-160)
  • sendAndConfirmTx (51-62)
js/program-test/tests/transfer.test.ts (3)
js/program-test/src/litesvm-rpc.ts (2)
  • LiteSVMRpc (22-889)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/test-rpc.ts (10)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (7)
  • CompressionApiInterface (762-867)
  • WithCursor (250-255)
  • LatestNonVotingSignatures (33-43)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • CompressedMintTokenHolders (52-55)
  • ValidityProofWithContext (161-190)
js/stateless.js/src/state/BN254.ts (1)
  • BN254 (12-12)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-140)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/spl-token-utils.ts (2)
  • splCreateAssociatedTokenAccount (75-110)
  • splMintTo (115-142)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/compressed-token/tests/program-test/mint-to.test.ts (6)
cli/test/helpers/helpers.ts (1)
  • TEST_TOKEN_DECIMALS (69-69)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/compressed-token/tests/program-test/create-mint.test.ts (1)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (9)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splGetOrCreateAssociatedTokenAccount (246-294)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/stateless.js/src/utils/dedupe-signer.ts (1)
  • dedupeSigner (4-11)
js/stateless.js/src/utils/send-and-confirm.ts (2)
  • buildAndSignTx (139-160)
  • sendAndConfirmTx (51-62)
js/stateless.js/src/utils/conversion.ts (1)
  • toArray (28-29)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
sdk-tests/sdk-anchor-test/tests/test_v1.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: lint
  • GitHub Check: cli-v1
🔇 Additional comments (19)
js/compressed-token/package.json (1)

82-99: Program-test coverage integration looks solid

Wiring the program-test suite ahead of the E2E runs (with dedicated entry points for each scenario) ensures the LiteSVM harness is exercised consistently before the heavier validator flow. The added Nx dependency on @lightprotocol/program-test:build also keeps the workspace build order sane. Nicely done.

.github/workflows/js.yml (1)

66-105: Program-test coverage integrated cleanly

Adding explicit build/test steps with the shared devenv bootstrap keeps the new package under CI and matches existing workflow structure. Nice alignment.

.github/workflows/js-v2.yml (1)

66-107: Parity for V2 pipeline looks great

Mirroring the program-test build/test stages here keeps both versions consistent and ensures LiteSVM regressions surface early.

js/compressed-token/tests/program-test/transfer.test.ts (1)

19-125: Great move to shared LiteSVM helpers

Switching the suite over to createLiteSVMRpc and newAccountWithLamports centralizes the setup logic and keeps it aligned with the new program-test package—this reads much cleaner.

js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)

18-139: Nice reuse of program-test SPL helpers

Adopting the packaged RPC and SPL helpers streamlines delegated decompression setup and keeps everything within the LiteSVM-aware toolchain.

js/compressed-token/tests/program-test/create-token-pool.test.ts (1)

19-118: Consistent LiteSVM bootstrapping

Bringing the token-pool tests onto the shared RPC/account helpers removes bespoke wiring and keeps coverage aligned with program-test expectations.

js/program-test/README.md (1)

1-177: Documentation is clear and actionable

The README gives a solid overview of LiteSVM usage, test commands, and exported APIs—great onboarding material for the new package.

js/compressed-token/tests/program-test/multi-pool.test.ts (1)

23-115: Solid consolidation around program-test helpers

Updating the multi-pool flow to rely on the shared RPC and SPL utilities removes ad-hoc wiring and keeps the scenarios aligned with the new testing stack.

js/compressed-token/tests/program-test/transfer-delegated.test.ts (1)

11-14: LGTM! Migration to program-test utilities is clean.

The import reorganization and RPC initialization changes align well with the broader migration to LiteSVM-based testing infrastructure.

Also applies to: 182-182

js/compressed-token/tests/program-test/decompress.test.ts (1)

14-18: LGTM! Consistent migration to program-test utilities.

The changes properly migrate to:

  • createLiteSVMRpc for RPC initialization
  • newAccountWithLamports for test account creation
  • splCreateAssociatedTokenAccount for token account setup

All signatures and usage patterns align with the program-test helper implementations shown in the relevant code snippets.

Also applies to: 83-83, 103-108

js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (2)

18-23: LGTM! Migration to program-test SPL token utilities.

The import changes and usage of createLiteSVMRpc, splCreateAssociatedTokenAccount, and splMintTo are consistent with the program-test package API surface.

Also applies to: 44-44, 64-69


138-145: Verify raw amount passed to splMintTo accounts for token decimals
splMintTo uses raw base units; bn(100).toNumber() = 100 (1 token at 2 decimals). Confirm tests intend to mint 1 token or multiply by 10**TEST_TOKEN_DECIMALS to mint 100 tokens.

js/program-test/package.json (2)

64-83: LGTM! Comprehensive test script setup.

The test scripts provide good coverage:

  • Separate unit and e2e test suites
  • V1/V2 protocol version testing
  • Individual test file execution options
  • Proper test validator management

31-40: All key dependencies are up-to-date and secure. @solana/[email protected], @lightprotocol/[email protected], and [email protected] match the latest registry versions.

js/compressed-token/tests/program-test/delegate.test.ts (1)

11-14: LGTM! Standard migration to program-test utilities.

The import reorganization and RPC initialization follow the established migration pattern across the test suite.

Also applies to: 119-119

js/compressed-token/tests/e2e/compress.test.ts (1)

38-38: LGTM! Import source updated for getTestRpc.

The import has been correctly relocated from @lightprotocol/stateless.js to @lightprotocol/program-test, consistent with the test utilities consolidation.

Note: The AI summary mentions several test cases were removed from this file (compress to multiple recipients, Token-2022 scenarios). While not visible in the provided diff, ensure these removals are intentional and that equivalent test coverage exists elsewhere in the test suite.

js/compressed-token/tests/program-test/create-mint.test.ts (1)

6-11: LGTM! Import reorganization and RPC initialization are correct.

The changes properly:

  • Import Rpc type from stateless.js
  • Import WasmFactory from hasher.rs (correct source)
  • Import test utilities (createLiteSVMRpc, newAccountWithLamports) from program-test
  • Initialize RPC with createLiteSVMRpc(lightWasm)

All imports and usage patterns are consistent with the migration to the program-test package.

Also applies to: 56-56

js/program-test/tests/merkle-tree.test.ts (1)

6-183: Solid coverage and deterministic root checks

Good end-to-end assertions for append flows and Merkle roots; hashing path via WasmFactory is exercised well. No issues spotted.

js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (1)

40-98: Custom approve-and-mint path looks good

Helper composes SPL + compress ixs correctly, handles ATA creation, blockhash, deduped signers, and compute budget. Calls are consistent.

Also applies to: 147-150, 168-177, 207-216

Comment on lines +35 to +38
rpc = createRpc();

testRpc = await getTestRpc(lightWasm);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use the LiteSVM RPC instead of the plain createRpc()

Right now the suite calls createRpc(), which points at localhost JSON‑RPC/compression hosts. In the program-test environment nothing is actually listening on those ports unless you also spin up a real validator/prover, so every airdrop/compress/transfer in this spec will fail in CI. All the other new specs bootstrap against LiteSVM via createLiteSVMRpc(lightWasm)—do the same here (and drop the unused testRpc if you don’t need it) so the tests run against the in-process LiteSVM harness this package provides.

Apply this diff:

-    rpc = createRpc();
-
-    testRpc = await getTestRpc(lightWasm);
+    rpc = await createLiteSVMRpc(lightWasm);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rpc = createRpc();
testRpc = await getTestRpc(lightWasm);
rpc = await createLiteSVMRpc(lightWasm);
🤖 Prompt for AI Agents
In js/program-test/tests/rpc-multi-trees.test.ts around lines 35 to 38, the test
currently calls createRpc() which targets external localhost RPC hosts not
present in the program-test harness; replace that call with
createLiteSVMRpc(lightWasm) so the suite uses the in-process LiteSVM harness,
and remove the unused testRpc variable (or stop awaiting getTestRpc(lightWasm))
if it isn’t needed.

@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from e320e1f to cf7f1f6 Compare October 10, 2025 03:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sdk-tests/sdk-anchor-test/tests/test_v1.ts (1)

127-136: Missing type import for SdkAnchorTest breaks TS compile

Program is used but SdkAnchorTest isn’t imported. Import the generated Anchor type or drop the generic.

Apply one of the following:

Option A — import the generated type (preferred; typical Anchor path):

@@
-import idl from "../target/idl/sdk_anchor_test.json";
+import idl from "../target/idl/sdk_anchor_test.json";
+import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";

Option B — relax the signature:

-  program: Program<SdkAnchorTest>,
+  program: Program,
sdk-tests/sdk-anchor-test/tests/test_v2.ts (1)

141-149: Missing type import for SdkAnchorTest (same as v1)

Ensure SdkAnchorTest is imported, or relax the signature.

-  program: Program<SdkAnchorTest>,
+  program: Program,

Or add:
import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";

🧹 Nitpick comments (30)
js/program-test/.prettierignore (1)

1-2: Drop the redundant photon.log pattern.

test-ledger/*.log already captures test-ledger/photon.log, so the dedicated entry is unnecessary noise. Trim the duplicate to keep the ignore list lean.

js/program-test/vitest.config.ts (1)

1-7: Minimal Vitest config is fine; consider adding env/coverage later

Looks good. Optionally set test.environment ('node' vs 'jsdom') and basic coverage thresholds to catch regressions as program-test grows.

js/compressed-token/tests/e2e/mint-to.test.ts (1)

108-133: Add post-assertions to validate mint results

Currently the test only sends the tx. Verify at least a couple recipients to prevent false positives.

-        const txId = await sendAndConfirmTx(rpc, tx);
+        const txId = await sendAndConfirmTx(rpc, tx);
+        // Spot-check first and last recipients
+        await assertMintTo(rpc, mint, amounts[0], recipients[0]);
+        await assertMintTo(rpc, mint, amounts[amounts.length - 1], recipients[recipients.length - 1]);
js/compressed-token/tests/e2e/compress.test.ts (1)

171-199: Add a minimal assertion to ensure compress had effect

Validate one recipient received the expected compressed amount.

-        await sendAndConfirmTx(rpc, tx);
+        await sendAndConfirmTx(rpc, tx);
+        const post = await rpc.getCompressedTokenAccountsByOwner(recipients[0], { mint });
+        const received = post.items.some((it) => it.parsed.amount.eq(bn(2)));
+        expect(received).toBe(true);
js/program-test/src/test-rpc/get-compressed-accounts.ts (2)

14-21: Add explicit return type

Annotate the return type for clarity and tooling.

-export async function getCompressedAccountsByOwnerTest(
+export async function getCompressedAccountsByOwnerTest(
   rpc: Rpc,
   owner: PublicKey,
-) {
+): Promise<CompressedAccountWithMerkleContext[]> {

41-96: Use Set/Map to dedupe and speed up unspent-account resolution

Avoid O(n^2) and keep the latest output per hash (events already reversed).

-export async function getCompressedAccountsForTest(rpc: Rpc) {
+export async function getCompressedAccountsForTest(
+  rpc: Rpc,
+): Promise<CompressedAccountWithMerkleContext[]> {
   const events = (await getParsedEvents(rpc)).reverse();
-  const allOutputAccounts: CompressedAccountWithMerkleContext[] = [];
-  const allInputAccountHashes: BN[] = [];
+  const outputByHash = new Map<string, CompressedAccountWithMerkleContext>();
+  const spentHashes = new Set<string>();
   const infos = await rpc.getStateTreeInfos();
@@
-      const merkleContext: MerkleContext = {
+      const merkleContext: MerkleContext = {
         treeInfo,
-        hash: bn(event.outputCompressedAccountHashes[index]),
+        hash: bn(event.outputCompressedAccountHashes[index]),
         leafIndex: event.outputLeafIndices[index],
         // V2 trees always have proveByIndex = true in test-rpc.
         proveByIndex: treeInfo.treeType === TreeType.StateV2,
       };
       const withCtx: CompressedAccountWithMerkleContext =
         createCompressedAccountWithMerkleContextLegacy(
           merkleContext,
           account.compressedAccount.owner,
           account.compressedAccount.lamports,
           account.compressedAccount.data ?? undefined,
           account.compressedAccount.address ?? undefined,
         );
-      allOutputAccounts.push(withCtx);
+      // Keep only the latest occurrence per hash (events processed newest-first)
+      const hashStr = bn(merkleContext.hash).toString(16);
+      if (!outputByHash.has(hashStr)) {
+        outputByHash.set(hashStr, withCtx);
+      }
@@
-      const hash = event.inputCompressedAccountHashes[index];
-      allInputAccountHashes.push(bn(hash));
+      const hash = event.inputCompressedAccountHashes[index];
+      spentHashes.add(bn(hash).toString(16));
@@
-  const unspentAccounts = allOutputAccounts.filter(
-    (account) =>
-      !allInputAccountHashes.some((hash) => hash.eq(bn(account.hash))),
-  );
-  unspentAccounts.sort((a, b) => b.leafIndex - a.leafIndex);
-
-  return unspentAccounts;
+  const unspentAccounts = Array.from(outputByHash.entries())
+    .filter(([hashStr]) => !spentHashes.has(hashStr))
+    .map(([, acc]) => acc)
+    .sort((a, b) => b.leafIndex - a.leafIndex);
+  return unspentAccounts;
 }
js/program-test/src/merkle-tree/merkle-tree.ts (1)

22-27: Tighten typing for zeroElement

Annotate zeroElement to avoid implicit any.

-  zeroElement;
+  zeroElement: string;
.github/workflows/js.yml (1)

103-106: Consider adding retry logic for consistency.

The program-test tests lack the retry logic present in the stateless.js and compressed-token test steps (lines 71-85, 87-101). If program-test tests exhibit similar flakiness, consider adding the same retry pattern for consistency.

Apply this diff to add retry logic:

       - name: Run program-test tests with V1
         run: |
-          source ./scripts/devenv.sh
-          npx nx test @lightprotocol/program-test
+          echo "Running program-test tests with retry logic (max 2 attempts)..."
+          attempt=1
+          max_attempts=2
+          until (source ./scripts/devenv.sh && npx nx test @lightprotocol/program-test); do
+            attempt=$((attempt + 1))
+            if [ $attempt -gt $max_attempts ]; then
+              echo "Tests failed after $max_attempts attempts"
+              exit 1
+            fi
+            echo "Attempt $attempt/$max_attempts failed, retrying..."
+            sleep 5
+          done
+          echo "Tests passed on attempt $attempt"
.github/workflows/js-v2.yml (1)

103-106: Consider adding retry logic for consistency.

The program-test tests lack the retry logic present in the stateless.js and compressed-token test steps (lines 71-85, 87-101). If program-test tests exhibit similar flakiness, consider adding the same retry pattern for consistency.

Apply this diff to add retry logic:

       - name: Run program-test tests with V2
         run: |
-          source ./scripts/devenv.sh
-          npx nx test @lightprotocol/program-test
+          echo "Running program-test tests with retry logic (max 2 attempts)..."
+          attempt=1
+          max_attempts=2
+          until (source ./scripts/devenv.sh && npx nx test @lightprotocol/program-test); do
+            attempt=$((attempt + 1))
+            if [ $attempt -gt $max_attempts ]; then
+              echo "Tests failed after $max_attempts attempts"
+              exit 1
+            fi
+            echo "Attempt $attempt/$max_attempts failed, retrying..."
+            sleep 5
+          done
+          echo "Tests passed on attempt $attempt"
js/program-test/tests/compress.test.ts (1)

24-52: Consider removing console.log statements.

While logging is helpful during development, consider removing or converting these console.log statements to conditional debug logging for cleaner test output in CI environments.

Example approach:

const DEBUG = process.env.DEBUG_TESTS === "true";

// Replace console.log with conditional logging
if (DEBUG) {
  console.log("Pre-compress balance:", preBalance);
}

Or remove the logs entirely if not needed for debugging.

js/compressed-token/tests/program-test/mint-to.test.ts (1)

139-140: Consider removing the commented code.

The commented line appears to be a previous value for maxRecipients. Consider removing it to reduce clutter, unless it serves as documentation for the test's evolution.

Apply this diff to clean up the commented code:

-    // const maxRecipients = 18;
     const maxRecipients = 22;
js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (1)

40-98: Consider exposing a public API instead of duplicating logic.

This local test helper duplicates functionality that could be part of the public API. Creating test-specific helpers increases maintenance burden when the underlying implementation changes.

Options:

  1. Preferred: Export this as part of @lightprotocol/program-test or @lightprotocol/compressed-token public API if other consumers need it
  2. Alternative: Add a comment explaining why this must remain test-local (e.g., LiteSVM-specific behavior)

Additionally, document the compute budget formula on line 87-88:

// Base: 150k units for transaction overhead
// Per-recipient: 20k units for compression operations
units: 150_000 + toArray(amount).length * 20_000,
js/program-test/tests/transfer.test.ts (1)

24-59: Consider accounting for transaction fees in assertions.

The balance assertions only verify the transfer amount delta but don't account for transaction fees that may be deducted from the sender's balance. This could cause false negatives if fees are charged.

If transaction fees are expected, consider adjusting the assertion to account for them:

assert(
  postSenderBalance.sub(preSenderBalance).lte(bn(-transferAmount)),
  `Iteration ${i + 1}: Sender balance should decrease by at least ${transferAmount}`,
);

Or fetch and verify the exact fee from the transaction metadata.

js/program-test/src/test-utils.ts (1)

26-31: Validate seed length in getOrCreateKeypair: ensure seed.length === 32 before calling Keypair.fromSeed, either by throwing a descriptive error or padding to 32 bytes.

js/program-test/tests/test-rpc.test.ts (4)

64-71: Replace magic numbers with constants for fee checks

Hardcoded 5000s risk drift. Use STATE_MERKLE_TREE_NETWORK_FEE to compute both network-fee deductions.

Apply this diff:

-    assert.equal(
-      postCompressBalance,
-      preCompressBalance -
-        compressLamportsAmount -
-        5000 -
-        5000 -
-        STATE_MERKLE_TREE_ROLLOVER_FEE.toNumber(),
-    );
+    assert.equal(
+      postCompressBalance,
+      preCompressBalance -
+        compressLamportsAmount -
+        2 * STATE_MERKLE_TREE_NETWORK_FEE.toNumber() -
+        STATE_MERKLE_TREE_ROLLOVER_FEE.toNumber(),
+    );

109-112: Use constant instead of 5000 for fee deduction

Keep fee expectations tied to constants.

-      preCompressBalance -
-        5000 -
-        STATE_MERKLE_TREE_ROLLOVER_FEE.toNumber() -
-        STATE_MERKLE_TREE_NETWORK_FEE.toNumber(),
+      preCompressBalance -
+        STATE_MERKLE_TREE_NETWORK_FEE.toNumber() -
+        STATE_MERKLE_TREE_ROLLOVER_FEE.toNumber() -
+        STATE_MERKLE_TREE_NETWORK_FEE.toNumber(),

79-83: Trim noisy console logs in tests

These prints add noise and slow CI. Prefer expect/asserts; log only on failure or behind a DEBUG flag.

Also applies to: 87-89, 217-223


18-18: Minor: remove unused and tighten types

  • merkleTree is never used.
  • Type rpc as Rpc for better IDE help.

Also applies to: 19-19

js/compressed-token/tests/program-test/compress.test.ts (2)

232-242: Remove redundant loop calling assertCompress N times

The body doesn’t depend on i; it repeats the same assertions and wastes time.

-        for (let i = 0; i < recipients.length; i++) {
-            await assertCompress(
-                rpc,
-                bn(senderAtaBalanceBefore.value.amount),
-                bobAta,
-                mint,
-                amounts.slice(0, 11),
-                recipients.slice(0, 11),
-                recipientCompressedTokenBalancesBefore.map(x => x.items),
-            );
-        }
+        await assertCompress(
+            rpc,
+            bn(senderAtaBalanceBefore.value.amount),
+            bobAta,
+            mint,
+            amounts.slice(0, 11),
+            recipients.slice(0, 11),
+            recipientCompressedTokenBalancesBefore.map(x => x.items),
+        );

127-137: Drop debug logs or guard behind DEBUG

Console noise slows CI and hides useful output. Keep only when diagnosing failures.

Also applies to: 145-146, 157-161, 176-176

js/program-test/tests/rpc-multi-trees.test.ts (2)

158-160: Meaningless self-equality check

Comparing an element to itself doesn’t validate anything. Assert against the requested hash or expected tree/queue.

-    compressedAccountProof.merkleProof.forEach((proof, index) => {
-      assert.isTrue(proof.eq(compressedAccountProof.merkleProof[index]));
-    });
+    assert.equal(
+      compressedAccountProof.hash.toString(),
+      hash.toString(),
+      'Proof hash should match requested hash',
+    );

217-226: Reduce console noise

Batch logs inside hot loops make CI output unwieldy. Consider removing or wrapping with a debug flag.

Also applies to: 237-243

js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (1)

138-145: Prefer passing bigint to splMintTo and keep calls consistent

You convert BN to number; safe here but brittle if amounts grow. Pass bigint (e.g., testAmount.toArrayLike(BigInt) is not straightforward) or a numeric literal where small. Also consider consolidating to a helper to avoid drift.

Also applies to: 221-228, 266-266, 290-290

sdk-tests/sdk-anchor-test/tests/test_v1.ts (1)

181-185: Guard debug access to instruction index

Accessing tx.instructions[1] assumes at least two instructions (compute budget + method). If ordering changes, this can throw.

-  console.log("tx ", tx.instructions[1].keys);
+  if (tx.instructions[1]) {
+    console.log("tx ", tx.instructions[1].keys);
+  }
js/program-test/src/litesvm-rpc.ts (2)

370-386: getSignaturesForAddress ignores the address param

Currently returns all stored txs, which can skew consumers expecting address-scoped results.

Filter by presence of the address in the transaction’s account keys:

-    return Array.from(this.storedTransactions.values())
+    const matches = Array.from(this.storedRawTransactions.entries()).filter(
+      ([, rawTx]) => {
+        const keys =
+          "message" in rawTx
+            ? (rawTx.message.staticAccountKeys ??
+                rawTx.message.accountKeys ??
+                rawTx.message.getAccountKeys?.().staticAccountKeys ??
+                [])
+            : (rawTx as Transaction).compileMessage().accountKeys;
+        return keys.some((k: PublicKey) => k.equals(address));
+      },
+    );
+    return matches
+      .map(([sig]) => this.storedTransactions.get(sig)!)
       .reverse()
       .map((tx) => ({
         signature: tx.signature,
         slot: tx.slot,
         err: null,
         memo: null,
         blockTime: tx.blockTime,
         confirmationStatus: tx.confirmationStatus,
       }));

239-274: Excessive console logging in hot paths

Verbose logs in sendTransaction and getSignatureStatuses will spam CI logs.

  • Gate logs behind a config flag (this.log) or environment variable.
  • Use a debug logger and default to silent in tests.

Also applies to: 739-769

js/program-test/tests/rpc-interop.test.ts (1)

656-662: Remove unused variable

allAccountsTestRpc is declared but never used.

-    const allAccountsTestRpc = await testRpc.getCompressedAccountsByOwner(
-      payer.publicKey,
-    );
-    const allAccountsRpc = await rpc.getCompressedAccountsByOwner(
+    const allAccountsRpc = await rpc.getCompressedAccountsByOwner(
       payer.publicKey,
     );
js/program-test/src/merkle-tree/indexed-array.ts (2)

24-35: Preserve original error details in error messages.

The catch block on line 32-33 throws a generic error message that discards the original error context. This makes debugging hash failures more difficult.

Apply this diff to preserve the original error:

   public hash(lightWasm: LightWasm, nextValue: BN): Uint8Array {
     try {
       const hash = lightWasm.poseidonHash([
         bn(this.value.toArray("be", 32)).toString(),
         bn(this.nextIndex).toString(),
         bn(nextValue.toArray("be", 32)).toString(),
       ]);
       return hash;
     } catch (error) {
-      throw new Error("Hashing failed");
+      throw new Error(`Hashing failed: ${error instanceof Error ? error.message : String(error)}`);
     }
   }

164-180: Consider consistent error handling for hashing.

The hashElement method lacks error handling, unlike IndexedElement.hash (lines 24-35) which wraps the hashing call in a try-catch block. If poseidonHash fails, this will throw an unhandled error.

Apply this diff to add consistent error handling:

   public hashElement(
     lightWasm: LightWasm,
     index: number,
   ): Uint8Array | undefined {
     const element = this.elements[index];
     if (!element) return undefined;
     const nextElement = this.elements[element.nextIndex];
     if (!nextElement) return undefined;
 
+    try {
       const hash = lightWasm.poseidonHash([
         bn(element.value.toArray("be", 32)).toString(),
         bn(element.nextIndex).toString(),
         bn(nextElement.value.toArray("be", 32)).toString(),
       ]);
 
       return hash;
+    } catch (error) {
+      throw new Error(`Hashing failed for element at index ${index}: ${error instanceof Error ? error.message : String(error)}`);
+    }
   }
js/program-test/src/spl-token-utils.ts (1)

246-256: Document or remove unused parameters.

The commitment and confirmOptions parameters (lines 252-253) are declared but never used in the function body. If these are included for API compatibility with @solana/spl-token's getOrCreateAssociatedTokenAccount, consider adding a JSDoc comment explaining this. Otherwise, remove them to avoid confusion.

If keeping for compatibility, add documentation:

 /**
  * Get or create an associated token account
  * Replicates the behavior of getOrCreateAssociatedTokenAccount from @solana/spl-token
+ * 
+ * @param commitment - Reserved for compatibility (not used with LiteSVM)
+ * @param confirmOptions - Reserved for compatibility (not used with LiteSVM)
  */
 export async function splGetOrCreateAssociatedTokenAccount(
   rpc: Rpc,
   payer: Signer,
   mint: PublicKey,
   owner: PublicKey,
   allowOwnerOffCurve = false,
   commitment?: any,
   confirmOptions?: any,
   programId = TOKEN_PROGRAM_ID,
   associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID,
 ): Promise<{ address: PublicKey; isNew: boolean }> {

Or remove them if not needed:

 export async function splGetOrCreateAssociatedTokenAccount(
   rpc: Rpc,
   payer: Signer,
   mint: PublicKey,
   owner: PublicKey,
   allowOwnerOffCurve = false,
-  commitment?: any,
-  confirmOptions?: any,
   programId = TOKEN_PROGRAM_ID,
   associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID,
 ): Promise<{ address: PublicKey; isNew: boolean }> {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aeb921 and 52ee14d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (65)
  • .github/workflows/js-v2.yml (2 hunks)
  • .github/workflows/js.yml (2 hunks)
  • js/compressed-token/package.json (3 hunks)
  • js/compressed-token/tests/e2e/compress.test.ts (1 hunks)
  • js/compressed-token/tests/e2e/mint-to.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (4 hunks)
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (8 hunks)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/create-mint.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/create-token-pool.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (3 hunks)
  • js/compressed-token/tests/program-test/decompress.test.ts (3 hunks)
  • js/compressed-token/tests/program-test/delegate.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/mint-to.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/multi-pool.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/transfer-delegated.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/transfer.test.ts (4 hunks)
  • js/program-test/.prettierignore (1 hunks)
  • js/program-test/README.md (1 hunks)
  • js/program-test/package.json (1 hunks)
  • js/program-test/rollup.config.js (1 hunks)
  • js/program-test/src/index.ts (1 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/merkle-tree/index.ts (1 hunks)
  • js/program-test/src/merkle-tree/indexed-array.ts (1 hunks)
  • js/program-test/src/merkle-tree/merkle-tree.ts (1 hunks)
  • js/program-test/src/spl-token-utils.ts (1 hunks)
  • js/program-test/src/test-rpc/get-compressed-accounts.ts (1 hunks)
  • js/program-test/src/test-rpc/get-compressed-token-accounts.ts (1 hunks)
  • js/program-test/src/test-rpc/get-parsed-events.ts (1 hunks)
  • js/program-test/src/test-rpc/index.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
  • js/program-test/src/test-utils.ts (1 hunks)
  • js/program-test/src/types.ts (1 hunks)
  • js/program-test/tests/compress.test.ts (1 hunks)
  • js/program-test/tests/merkle-tree.test.ts (1 hunks)
  • js/program-test/tests/rpc-interop.test.ts (1 hunks)
  • js/program-test/tests/rpc-multi-trees.test.ts (1 hunks)
  • js/program-test/tests/test-rpc.test.ts (1 hunks)
  • js/program-test/tests/transfer.test.ts (1 hunks)
  • js/program-test/tsconfig.json (1 hunks)
  • js/program-test/vitest.config.ts (1 hunks)
  • js/stateless.js/package.json (2 hunks)
  • js/stateless.js/rollup.config.js (2 hunks)
  • js/stateless.js/src/rpc.ts (0 hunks)
  • js/stateless.js/src/test-helpers/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/indexed-array.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/merkle-tree.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-accounts.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-token-accounts.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts (0 hunks)
  • js/stateless.js/src/utils/send-and-confirm.ts (1 hunks)
  • js/stateless.js/tests/e2e/compress.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/rpc-interop.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/rpc-multi-trees.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/test-rpc.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/transfer.test.ts (0 hunks)
  • js/stateless.js/tests/unit/merkle-tree/merkle-tree.test.ts (0 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • sdk-tests/sdk-anchor-test/package.json (1 hunks)
  • sdk-tests/sdk-anchor-test/tests/test_v1.ts (4 hunks)
  • sdk-tests/sdk-anchor-test/tests/test_v2.ts (5 hunks)
💤 Files with no reviewable changes (16)
  • js/stateless.js/src/test-helpers/test-rpc/index.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-accounts.ts
  • js/stateless.js/src/test-helpers/index.ts
  • js/stateless.js/src/test-helpers/merkle-tree/index.ts
  • js/stateless.js/src/test-helpers/merkle-tree/merkle-tree.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-token-accounts.ts
  • js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts
  • js/stateless.js/src/test-helpers/merkle-tree/indexed-array.ts
  • js/stateless.js/tests/unit/merkle-tree/merkle-tree.test.ts
  • js/stateless.js/tests/e2e/test-rpc.test.ts
  • js/stateless.js/src/rpc.ts
  • js/stateless.js/tests/e2e/transfer.test.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts
  • js/stateless.js/tests/e2e/rpc-interop.test.ts
  • js/stateless.js/tests/e2e/compress.test.ts
  • js/stateless.js/tests/e2e/rpc-multi-trees.test.ts
🧰 Additional context used
🧬 Code graph analysis (29)
js/stateless.js/src/utils/send-and-confirm.ts (1)
forester-utils/src/rpc_pool.rs (1)
  • commitment (152-155)
js/program-test/tests/merkle-tree.test.ts (5)
js/program-test/src/merkle-tree/indexed-array.ts (3)
  • IndexedElement (5-36)
  • IndexedArray (58-307)
  • newElement (300-306)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/constants.ts (1)
  • HIGHEST_ADDRESS_PLUS_ONE (48-50)
program-tests/merkle-tree/src/lib.rs (1)
  • leaf (293-298)
js/program-test/src/merkle-tree/merkle-tree.ts (1)
  • MerkleTree (13-214)
js/program-test/tests/test-rpc.test.ts (5)
js/stateless.js/src/constants.ts (4)
  • defaultTestStateTreeAccounts (236-244)
  • STATE_MERKLE_TREE_ROLLOVER_FEE (338-340)
  • featureFlags (16-35)
  • STATE_MERKLE_TREE_NETWORK_FEE (356-356)
js/stateless.js/src/state/compressed-account.ts (1)
  • CompressedAccountWithMerkleContext (14-17)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-utils.ts (1)
js/program-test/src/index.ts (3)
  • newAccountWithLamports (10-10)
  • sleep (11-11)
  • getOrCreateKeypair (12-12)
js/program-test/src/test-rpc/get-parsed-events.ts (4)
js/stateless.js/src/state/types.ts (2)
  • PublicTransactionEvent (231-272)
  • InstructionDataInvoke (277-306)
js/stateless.js/src/constants.ts (8)
  • defaultStaticAccountsStruct (95-105)
  • accountCompressionProgram (76-77)
  • COMPUTE_BUDGET_PATTERN (52-52)
  • noopProgram (74-74)
  • INVOKE_DISCRIMINATOR (54-56)
  • INVOKE_CPI_DISCRIMINATOR (58-60)
  • INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR (62-64)
  • INSERT_INTO_QUEUES_DISCRIMINATOR (70-72)
js/stateless.js/src/programs/system/layout.ts (6)
  • decodePublicTransactionEvent (392-396)
  • decodeInstructionDataInvoke (240-246)
  • decodeInstructionDataInvokeCpi (248-254)
  • decodeInstructionDataInvokeCpiWithReadOnly (234-238)
  • deserializeAppendNullifyCreateAddressInputsIndexer (441-514)
  • convertToPublicTransactionEvent (516-598)
js/stateless.js/src/utils/conversion.ts (1)
  • convertInvokeCpiWithReadOnlyToInvoke (121-191)
js/program-test/src/test-rpc/get-compressed-accounts.ts (5)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/stateless.js/src/state/compressed-account.ts (3)
  • CompressedAccountWithMerkleContext (14-17)
  • MerkleContext (80-97)
  • createCompressedAccountWithMerkleContextLegacy (240-253)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-140)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/compressed-token/tests/program-test/multi-pool.test.ts (3)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
cli/test/helpers/helpers.ts (1)
  • createTestSplMint (71-103)
js/program-test/src/spl-token-utils.ts (2)
  • splGetOrCreateAssociatedTokenAccount (246-294)
  • splMintTo (115-142)
js/stateless.js/rollup.config.js (2)
js/program-test/rollup.config.js (1)
  • rolls (7-42)
js/compressed-token/rollup.config.js (1)
  • rolls (12-83)
js/program-test/src/litesvm-rpc.ts (3)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (114-928)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (2)
  • defaultTestStateTreeAccounts (236-244)
  • COMPRESSED_TOKEN_PROGRAM_ID (256-258)
sdk-tests/sdk-anchor-test/tests/test_v1.ts (3)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/index.ts (2)
  • createLiteSVMRpc (8-8)
  • newAccountWithLamports (10-10)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/program-test/tests/compress.test.ts (3)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
sdk-tests/sdk-anchor-test/tests/test_v2.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/program-test/src/merkle-tree/merkle-tree.ts (1)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-68)
js/program-test/tests/transfer.test.ts (3)
js/program-test/src/litesvm-rpc.ts (2)
  • LiteSVMRpc (22-889)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-compressed-token-accounts.ts (7)
js/stateless.js/src/rpc-interface.ts (2)
  • ParsedTokenAccount (236-239)
  • WithCursor (250-255)
js/stateless.js/src/state/types.ts (2)
  • CompressedAccountLegacy (146-163)
  • PublicTransactionEvent (231-272)
js/stateless.js/src/constants.ts (2)
  • COMPRESSED_TOKEN_PROGRAM_ID (256-258)
  • featureFlags (16-35)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/stateless.js/src/state/compressed-account.ts (2)
  • MerkleContext (80-97)
  • createCompressedAccountWithMerkleContextLegacy (240-253)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-140)
js/compressed-token/tests/program-test/compress.test.ts (11)
js/stateless.js/src/rpc-interface.ts (1)
  • ParsedTokenAccount (236-239)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/stateless.js/src/utils/dedupe-signer.ts (1)
  • dedupeSigner (4-11)
js/stateless.js/src/utils/send-and-confirm.ts (2)
  • buildAndSignTx (139-160)
  • sendAndConfirmTx (51-62)
js/program-test/src/types.ts (1)
js/program-test/src/index.ts (2)
  • CustomProgram (14-14)
  • LiteSVMConfig (14-14)
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/src/spl-token-utils.ts (2)
  • splCreateAssociatedTokenAccount (75-110)
  • splMintTo (115-142)
js/compressed-token/tests/program-test/mint-to.test.ts (6)
cli/test/helpers/helpers.ts (1)
  • TEST_TOKEN_DECIMALS (69-69)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/rollup.config.js (1)
js/stateless.js/rollup.config.js (2)
  • rolls (11-68)
  • typesConfig (70-74)
js/program-test/tests/rpc-multi-trees.test.ts (7)
js/stateless.js/src/rpc.ts (2)
  • Rpc (551-1892)
  • createRpc (231-264)
js/program-test/src/test-rpc/test-rpc.ts (2)
  • TestRpc (114-928)
  • getTestRpc (85-104)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-14)
js/stateless.js/src/utils/address.ts (2)
  • deriveAddressSeed (11-18)
  • deriveAddress (29-46)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/merkle-tree/indexed-array.ts (3)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-68)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/constants.ts (1)
  • HIGHEST_ADDRESS_PLUS_ONE (48-50)
js/compressed-token/tests/program-test/create-token-pool.test.ts (1)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (9)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splGetOrCreateAssociatedTokenAccount (246-294)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/stateless.js/src/utils/dedupe-signer.ts (1)
  • dedupeSigner (4-11)
js/stateless.js/src/utils/send-and-confirm.ts (2)
  • buildAndSignTx (139-160)
  • sendAndConfirmTx (51-62)
js/stateless.js/src/utils/conversion.ts (1)
  • toArray (28-29)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
js/program-test/tests/rpc-interop.test.ts (8)
js/stateless.js/src/rpc.ts (2)
  • Rpc (551-1892)
  • createRpc (231-264)
js/program-test/src/test-rpc/test-rpc.ts (2)
  • TestRpc (114-928)
  • getTestRpc (85-104)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/program-test/src/test-utils.ts (2)
  • newAccountWithLamports (7-14)
  • sleep (19-21)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/utils/address.ts (2)
  • deriveAddressSeed (11-18)
  • deriveAddress (29-46)
js/stateless.js/src/constants.ts (2)
  • featureFlags (16-35)
  • getDefaultAddressTreeInfo (221-229)
js/program-test/src/test-rpc/test-rpc.ts (9)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (16)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • LatestNonVotingSignaturesPaginated (57-67)
  • LatestNonVotingSignatures (33-43)
  • GetCompressedTokenAccountsByOwnerOrDelegateOptions (205-209)
  • ParsedTokenAccount (236-239)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • SignatureWithMetadata (69-73)
  • CompressedTransaction (120-142)
  • PaginatedOptions (217-220)
  • CompressedMintTokenHolders (52-55)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/stateless.js/src/state/types.ts (2)
  • TreeInfo (77-99)
  • PublicTransactionEvent (231-272)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-140)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
js/compressed-token/tests/program-test/decompress.test.ts (1)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/compressed-token/tests/program-test/create-mint.test.ts (1)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (894-900)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: cli-v1
🔇 Additional comments (26)
js/stateless.js/rollup.config.js (2)

41-61: Ensure consistent minification strategy across Rollup configs.

Stateless applies terser only for CJS, compressed-token applies it for all formats, and program-test omits it entirely. Confirm whether this divergence is intentional; if not, align the configs or document the differences clearly.


80-80: Package.json exports updated: now include ES/node build at ./dist/es/node/index.js.

js/program-test/src/types.ts (1)

1-27: Interfaces look good

Type surface is clear and matches usage. No issues spotted.

js/program-test/README.md (1)

1-177: Excellent documentation for the new package!

The README provides comprehensive coverage of features, installation, usage patterns, testing workflows, API surface, and development commands. The clear distinction between unit tests (LiteSVM-based) and E2E tests (requiring test validator) helps users understand when and how to use each testing approach.

js/compressed-token/tests/program-test/create-mint.test.ts (1)

1-112: Clean migration to program-test infrastructure.

The test successfully migrates to the new program-test package, using createLiteSVMRpc and newAccountWithLamports from @lightprotocol/program-test. The test logic is sound, with proper setup in beforeAll and comprehensive assertions for mint creation and pool account validation.

js/program-test/src/test-rpc/index.ts (1)

1-3: LGTM!

Standard barrel export pattern that consolidates the test-rpc module's public API surface.

js/program-test/tsconfig.json (1)

1-21: LGTM!

Solid TypeScript configuration with strict mode enabled, declaration files, and appropriate module resolution for a Node.js library package.

js/program-test/package.json (2)

64-83: Well-organized test and build scripts.

The script organization clearly separates unit tests (LiteSVM-based), E2E tests (requiring validator), and version-specific test runs. The naming convention is consistent and intuitive.


31-40: All pinned dependency versions are valid and secure. @lightprotocol/hasher.rs 0.2.1 exists; @solana/web3.js 1.98.4 includes patches for all known advisories; litesvm ^0.3.3 is current and has no vulnerabilities.

js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)

18-22: LGTM! Clean migration to program-test utilities.

The changes correctly migrate from getTestRpc to createLiteSVMRpc and adopt the new splCreateAssociatedTokenAccount wrapper from the program-test package. The imports are well-organized and the test logic remains unchanged.

Also applies to: 113-113, 133-133

js/program-test/rollup.config.js (1)

1-50: LGTM! Appropriate Rollup configuration for test utilities.

The configuration is simpler than the stateless.js setup, which is suitable for a test utilities package. The external dependencies list is comprehensive, and the lack of minification/terser is appropriate for development tooling.

sdk-tests/sdk-anchor-test/package.json (1)

9-9: LGTM! Dependencies correctly updated for program-test migration.

The package correctly adds the new program-test dependency and hasher.rs, and simplifies the test script. The additions align with the broader PR migration to LiteSVM-backed testing.

Also applies to: 14-15, 23-23

js/compressed-token/tests/program-test/delegate.test.ts (1)

11-14: LGTM! Consistent migration to program-test.

The RPC initialization and imports are correctly updated to use the program-test utilities. The changes follow the same pattern as other migrated test files.

Also applies to: 119-119

js/compressed-token/tests/program-test/decompress.test.ts (1)

14-18: LGTM! Complete migration to program-test utilities.

All three aspects of the migration are correctly implemented: imports, RPC initialization, and token account creation helper. The changes maintain test functionality while adopting the new testing infrastructure.

Also applies to: 83-83, 103-103

js/compressed-token/tests/program-test/transfer-delegated.test.ts (1)

11-14: LGTM! Standard migration to program-test.

The changes correctly update the RPC initialization and imports. The migration follows the established pattern across all test files.

Also applies to: 182-182

js/compressed-token/tests/program-test/mint-to.test.ts (1)

1-188: LGTM! Well-structured test suite.

The new mint-to test comprehensively covers:

  • Single recipient minting
  • Multiple recipient scenarios (3 and 10 recipients)
  • Error handling for incorrect authority
  • Error handling for mismatched amounts/recipients arrays

The test correctly uses the program-test utilities and follows established patterns.

js/compressed-token/tests/program-test/transfer.test.ts (1)

19-22: LGTM! Consistent migration across both test suites.

Both test suites in this file correctly adopt the program-test utilities. The RPC initialization is properly updated in both beforeAll hooks, and the imports are clean and organized.

Also applies to: 105-105, 302-302

js/program-test/tests/merkle-tree.test.ts (1)

98-183: LGTM! Comprehensive integration test.

The integration test thoroughly validates the IndexedArray and MerkleTree interaction by:

  • Initializing an indexed array
  • Performing three sequential appends with different values (30, 42, 12)
  • Verifying the Merkle tree root matches expected reference values after each operation

This demonstrates proper ordering and hash computation in the indexed structure.

js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (1)

147-149: LGTM! Proper migration to LiteSVM.

The test correctly initializes the RPC using createLiteSVMRpc(lightWasm) for LiteSVM-backed testing, consistent with the PR's migration strategy.

js/compressed-token/tests/program-test/multi-pool.test.ts (2)

23-28: LGTM! Proper migration to program-test abstractions.

The import changes correctly replace direct SPL token calls with program-test equivalents, aligning with the PR's consolidation strategy for LiteSVM-based testing.


87-114: LGTM! Consistent use of program-test utilities.

The migration properly updates:

  • RPC initialization to use createLiteSVMRpc
  • Explicit lamport amounts (1e9) for account funding
  • Token operations to use spl* prefixed wrappers from program-test

This ensures consistency across the test suite.

js/program-test/src/index.ts (1)

8-17: Public surface looks coherent

Exports cover LiteSVM RPC, test utils, types, and helper modules. LGTM.

js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (2)

44-45: Good: moved to createLiteSVMRpc for local tests

Using LiteSVM-backed RPC here aligns with program-test approach.


340-348: Verify mintTo argument list consistency

In earlier calls mintTo includes tokenPoolInfo; here it’s omitted for Token 2022 path. If mintTo requires pool info for Token 2022 as well, add tokenPoolInfoT22.

Please confirm mintTo’s signature for Token 2022. If required, update to:

await mintTo(rpc, payer, mint, alice.publicKey, mintAuthority, bn(1000), stateTreeInfo, tokenPoolInfoT22);
js/compressed-token/package.json (1)

46-46: LGTM! Well-structured test integration.

The addition of the program-test dependency and comprehensive test scripts follows monorepo best practices. The nx configuration properly declares build and test dependencies.

Also applies to: 82-99, 144-152

js/program-test/src/spl-token-utils.ts (1)

67-67: Match rpc.sendTransaction calls to its declared signature instead of using as any. Remove all as any casts on rpc.sendTransaction (lines 67, 107, 141, 177, 291) and update each call to pass the correct parameters/return type as defined in the Rpc interface of @lightprotocol/stateless.js.

Comment on lines +444 to +513
return {
slot: tx.slot,
blockTime: tx.blockTime,
transaction: {
message: {
accountKeys,
compiledInstructions,
recentBlockhash:
message.recentBlockhash || message.header?.recentBlockhash || "",
addressTableLookups: message.addressTableLookups || [],
},
signatures:
"signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
},
meta: {
err: null,
logMessages: tx.logs,
innerInstructions: tx.innerInstructions || [],
preBalances: [],
postBalances: [],
preTokenBalances: [],
postTokenBalances: [],
rewards: [],
fee: 5000,
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

getTransaction returns non-spec “signatures”

You’re returning Transaction.signatures (objects) for legacy txs. JSON-RPC getTransaction expects base58 signature strings.

@@
-      transaction: {
+      transaction: {
         message: {
@@
         },
-        signatures:
-          "signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
+        signatures: [sigString],
       },

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +3 to +20
import {
Rpc,
createRpc,
LightSystemProgram,
TreeInfo,
bn,
compress,
createAccount,
createAccountWithLamports,
deriveAddress,
deriveAddressSeed,
featureFlags,
getDefaultAddressTreeInfo,
selectStateTreeInfo,
sleep,
transfer,
newAccountWithLamports,
} from "@lightprotocol/stateless.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Wrong source and arity for newAccountWithLamports (and sleep import)

newAccountWithLamports in program-test takes (rpc, lamports). Here it’s imported from stateless.js and called with 3 args, causing TS error.

Apply:

@@
-import {
-  Rpc,
-  createRpc,
-  LightSystemProgram,
-  TreeInfo,
-  bn,
-  compress,
-  createAccount,
-  createAccountWithLamports,
-  deriveAddress,
-  deriveAddressSeed,
-  featureFlags,
-  getDefaultAddressTreeInfo,
-  selectStateTreeInfo,
-  sleep,
-  transfer,
-  newAccountWithLamports,
-} from "@lightprotocol/stateless.js";
+import {
+  Rpc,
+  createRpc,
+  LightSystemProgram,
+  TreeInfo,
+  bn,
+  compress,
+  createAccount,
+  createAccountWithLamports,
+  deriveAddress,
+  deriveAddressSeed,
+  featureFlags,
+  getDefaultAddressTreeInfo,
+  selectStateTreeInfo,
+  transfer,
+} from "@lightprotocol/stateless.js";
+import { getTestRpc, TestRpc, newAccountWithLamports, sleep } from "../src";
@@
-import { getTestRpc, TestRpc } from "../src";
+// moved above

And fix calls:

@@
-    payer = await newAccountWithLamports(rpc, 10e9, 256);
-    bob = await newAccountWithLamports(rpc, 10e9, 256);
+    payer = await newAccountWithLamports(rpc, 10e9);
+    bob = await newAccountWithLamports(rpc, 10e9);

Also applies to: 21-24


🏁 Script executed:

#!/bin/bash
# Verify exported symbols and signatures
rg -n --type=ts "export (async )?function newAccountWithLamports" -C2
rg -n --type=ts "export async function sleep" -C2
rg -n --type=ts "from [\"']@lightprotocol/stateless.js[\"']" js | rg -n "newAccountWithLamports|sleep" -C2

Length of output: 1108


Import and call fixes for newAccountWithLamports and sleep

  • Remove newAccountWithLamports and sleep from the @lightprotocol/stateless.js import and instead import them from ../src alongside getTestRpc/TestRpc.
  • Update calls to drop the third argument:
    payer = await newAccountWithLamports(rpc, 10e9);
    bob   = await newAccountWithLamports(rpc, 10e9);

[js/program-test/tests/rpc-interop.test.ts:3–20]

🤖 Prompt for AI Agents
In js/program-test/tests/rpc-interop.test.ts around lines 3 to 20, the test
currently imports newAccountWithLamports and sleep from
"@lightprotocol/stateless.js" and calls newAccountWithLamports with three
arguments; update the imports to remove newAccountWithLamports and sleep from
the stateless package and instead import newAccountWithLamports and sleep from
../src alongside getTestRpc/TestRpc, then update all calls to
newAccountWithLamports to pass only (rpc, 10e9) (i.e., drop the third argument)
and ensure any usage of sleep is now using the relocated import.

Comment on lines 34 to 38
const lightWasm = await WasmFactory.getInstance();
rpc = createRpc();

testRpc = await getTestRpc(lightWasm);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use TestRpc as the Rpc under test (avoid external createRpc())

createRpc() targets external endpoints; getTestRpc(lightWasm) provides the in‑process LiteSVM harness these tests rely on. Currently testRpc is created but unused.

-    const lightWasm = await WasmFactory.getInstance();
-    rpc = createRpc();
-
-    testRpc = await getTestRpc(lightWasm);
+    const lightWasm = await WasmFactory.getInstance();
+    rpc = await getTestRpc(lightWasm);

Optionally remove testRpc variable if unused elsewhere.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const lightWasm = await WasmFactory.getInstance();
rpc = createRpc();
testRpc = await getTestRpc(lightWasm);
const lightWasm = await WasmFactory.getInstance();
rpc = await getTestRpc(lightWasm);
🤖 Prompt for AI Agents
In js/program-test/tests/rpc-multi-trees.test.ts around lines 34 to 38, the test
creates both rpc = createRpc() (which targets external endpoints) and testRpc =
await getTestRpc(lightWasm) (the in-process LiteSVM harness), but only
createRpc() is used; replace uses of the external rpc with the in-process
testRpc so the tests run against the local harness, or if the external rpc is
not needed remove the createRpc() call and use testRpc everywhere; ensure any
imports/typed variables refer to testRpc and delete the unused rpc variable.

});

const numberOfTransfers = 10;
it(`should send compressed lamports alice -> bob for ${numberOfTransfers} transfers in a loop`, async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test description refers to "alice" but uses "payer" variable.

The test description mentions "alice -> bob" but the actual variables are payer and bob. Update the description for consistency.

- it(`should send compressed lamports alice -> bob for ${numberOfTransfers} transfers in a loop`, async () => {
+ it(`should send compressed lamports payer -> bob for ${numberOfTransfers} transfers in a loop`, async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it(`should send compressed lamports alice -> bob for ${numberOfTransfers} transfers in a loop`, async () => {
it(`should send compressed lamports payer -> bob for ${numberOfTransfers} transfers in a loop`, async () => {
🤖 Prompt for AI Agents
In js/program-test/tests/transfer.test.ts around line 22, the test description
mentions "alice -> bob" but the test uses variables named `payer` and `bob`;
update the it(...) description string to reference "payer -> bob" (or change
`payer` to `alice` consistently) so the description matches the variables used;
keep the rest of the test unchanged.

Comment on lines 5 to 13
bn,
CompressedAccountWithMerkleContext,
createRpc,
type CompressedAccountWithMerkleContext,
defaultTestStateTreeAccounts,
deriveAddressSeedV2,
deriveAddressV2,
PackedAccounts,
Rpc,
sleep,
type Rpc,
SystemAccountMetaConfig,
} from "@lightprotocol/stateless.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Type import missing and magic numbers for TreeType

  • Program without importing SdkAnchorTest will not compile.
  • Using 2/3 for treeType is brittle; use TreeType enum.
@@
-import {
-  bn,
-  type CompressedAccountWithMerkleContext,
-  defaultTestStateTreeAccounts,
-  deriveAddressSeedV2,
-  deriveAddressV2,
-  PackedAccounts,
-  type Rpc,
-  SystemAccountMetaConfig,
-} from "@lightprotocol/stateless.js";
+import {
+  bn,
+  type CompressedAccountWithMerkleContext,
+  deriveAddressSeedV2,
+  deriveAddressV2,
+  PackedAccounts,
+  type Rpc,
+  SystemAccountMetaConfig,
+  TreeType,
+} from "@lightprotocol/stateless.js";
+import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";
@@
-  const stateTreeInfo = existingTreeInfos.find(
-      (info) => info.treeType === 2 || info.treeType === 3
-    ); // StateV1 or StateV2
+  const stateTreeInfo = existingTreeInfos.find(
+    (info) =>
+      info.treeType === TreeType.StateV1 || info.treeType === TreeType.StateV2,
+  ); // StateV1 or StateV2

Also consider importing SdkAnchorTest at the top and keep Program as-is.

Also applies to: 37-51

🤖 Prompt for AI Agents
In sdk-tests/sdk-anchor-test/tests/test_v2.ts around lines 5 to 13 (and also
check 37 to 51), the file is missing the SdkAnchorTest type import and uses
magic numbers (2/3) for treeType; add an import for SdkAnchorTest from the
appropriate module (e.g., "@lightprotocol/stateless.js") and replace literal 2
and 3 with the TreeType enum values (e.g., TreeType.SomethingA /
TreeType.SomethingB) so Program<SdkAnchorTest> compiles and treeType is robust;
update the other occurrences in lines 37–51 similarly.

@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from cf7f1f6 to f848318 Compare October 10, 2025 03:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52ee14d and f848318.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • cli/package.json (2 hunks)
  • cli/test/commands/approve-and-mint-to/index.test.ts (1 hunks)
  • cli/test/commands/create-token-pool/index.test.ts (1 hunks)
  • cli/test/helpers/helpers.ts (1 hunks)
  • js/program-test/eslint.config.cjs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: cli-v1
  • GitHub Check: lint
🔇 Additional comments (5)
cli/package.json (2)

74-74: LGTM: Dependency addition is correct.

The devDependency addition is appropriate since program-test is used only in test files.


173-174: LGTM: CI build dependency properly configured.

Adding @lightprotocol/program-test:build to the build-ci dependsOn list ensures the program-test package is built before CLI tests execute, preventing build-time errors.

cli/test/commands/approve-and-mint-to/index.test.ts (1)

8-8: LGTM: Consistent import migration.

The import change matches the pattern used in other test files and maintains the same usage of getTestRpc.

cli/test/helpers/helpers.ts (1)

16-16: LGTM: Shared helper migration is correct.

This helper file is used across multiple CLI tests (create-token-pool, approve-and-mint-to, etc.), so migrating the import here ensures consistency. All three usages of getTestRpc (lines 27, 35, 56) maintain the same signature.

cli/test/commands/create-token-pool/index.test.ts (1)

7-7: Approve import migration from stateless.js to program-test
getTestRpc is correctly exported by @lightprotocol/program-test (src/test-rpc/test-rpc.ts:85), and no legacy imports remain.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
js/program-test/src/test-rpc/test-rpc.ts (1)

288-404: Leaf ↔ index bookkeeping still breaks proofs for sparse histories

We still materialize each tree by pushing hashes into a dense array (Lines 309‑321) and then rebuilding the Merkle tree directly from that array (Lines 331‑338). Whenever outputLeafIndices jump—e.g. the indexer only fetched the latest ~1 000 txs but on-chain indices are already at 20 000—this drops every gap on the floor. The Merkle tree we rebuild no longer matches the on-chain layout, the root we hand back is wrong, and the computed path indices/path elements don’t line up with the real leaf index. Downstream inclusion proofs (and the prover request) are invalid in exactly the scenarios we use this helper for.

Please keep the hash ↔ on-chain index pairing intact and rebuild the tree at the correct positions (e.g. store a Map<leafIndex, hash>, instantiate an empty MerkleTree, then call update(leafIndex, value)/materialize a zero-filled array up to the highest index before hashing). Without that, getMultipleCompressedAccountProofs still returns bogus proofs for the common sparse-history case.

js/program-test/src/litesvm-rpc.ts (1)

452-466: Return JSON-RPC compliant transaction fields

We’re still returning the raw Transaction.signatures / VersionedTransaction.signatures objects and PublicKey instances here. JSON-RPC clients expect base58 signature strings plus address-key indices and instruction data serialized as base64. As written this breaks any consumer that deserializes according to the RPC spec (same blocker I pointed out earlier).

Tighten the payload to strings and plain numbers before storing:

       transaction: {
-        message: {
-          accountKeys,
-          compiledInstructions,
+        message: {
+          accountKeys: accountKeys.map((key) => key.toBase58()),
+          compiledInstructions: compiledInstructions.map((ix: any) => {
+            const accountIndexes =
+              "accountKeyIndexes" in ix ? ix.accountKeyIndexes : ix.accounts;
+            return {
+              programIdIndex: ix.programIdIndex,
+              accounts: Array.from(accountIndexes ?? []),
+              data: Buffer.from(ix.data ?? []).toString("base64"),
+            };
+          }),
           recentBlockhash:
             message.recentBlockhash || message.header?.recentBlockhash || "",
           addressTableLookups: message.addressTableLookups || [],
         },
-        signatures:
-          "signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
+        signatures: [tx.signature],
       },

This keeps the stored shape compatible with Solana’s RPC schema and avoids leaking Buffer objects.

js/program-test/src/merkle-tree/indexed-array.ts (1)

203-246: Preserve the original nextIndex when appending

newElementWithLowElementIndex mutates lowElement.nextIndex before we reach the if (lowElement.nextIndex === 0) check, so the branch never fires and highestElementIndex stays stale. On the very next insert of a larger value we attempt to wedge it between the old low element and its new successor, hit value.gte(nextElement.value), and throw. Capture the original nextIndex first and base both the ordering check and highest-element update on that snapshot:

-    const lowElement = this.elements[lowElementIndex];
-
-    if (lowElement.nextIndex === 0) {
-      if (value.lte(lowElement.value)) {
-        throw new Error(
-          "New element value must be greater than the low element value.",
-        );
-      }
-    } else {
-      const nextElement = this.elements[lowElement.nextIndex];
-
-      if (value.lte(lowElement.value)) {
-        throw new Error(
-          "New element value must be greater than the low element value.",
-        );
-      }
-
-      if (value.gte(nextElement.value)) {
-        throw new Error(
-          "New element value must be less than the next element value.",
-        );
-      }
-    }
+    const lowElement = this.elements[lowElementIndex];
+    const originalNextIndex = lowElement.nextIndex;
+
+    if (value.lte(lowElement.value)) {
+      throw new Error(
+        "New element value must be greater than the low element value.",
+      );
+    }
+
+    if (originalNextIndex !== 0) {
+      const nextElement = this.elements[originalNextIndex];
+
+      if (value.gte(nextElement.value)) {
+        throw new Error(
+          "New element value must be less than the next element value.",
+        );
+      }
+    }
@@
-    if (lowElement.nextIndex === 0) {
+    if (originalNextIndex === 0) {
       this.highestElementIndex = newElementBundle.newElement.index;
     }

This keeps highestElementIndex in sync and restores the Rust parity.

🧹 Nitpick comments (3)
js/compressed-token/vitest.config.ts (1)

7-15: LGTM! Consider making maxForks configurable.

The fork-based pool configuration is appropriate for handling native modules like LiteSVM, which are not thread-safe. The comments clearly explain the rationale, and the resource limits (maxForks: 4, minForks: 1) are reasonable defaults.

For improved flexibility across different environments (CI vs. local, varying CPU counts), consider making maxForks configurable via an environment variable:

         poolOptions: {
             forks: {
-                maxForks: 4,
+                maxForks: parseInt(process.env.MAX_FORKS || '4', 10),
                 minForks: 1,
             },
         },

This allows developers to adjust parallelism based on their system resources without modifying the config.

js/program-test/src/test-rpc/get-parsed-events.ts (2)

191-202: Remove unnecessary mapping operation.

Line 194 uses data.map((byte) => byte) which creates an identical copy of the data. Since Buffer.from() already accepts the input Buffer directly, the mapping is redundant.

Apply this diff:

 export const parsePublicTransactionEventWithIdl = (
   data: Buffer,
 ): PublicTransactionEvent | null => {
-  const numericData = Buffer.from(data.map((byte) => byte));
-
   try {
-    return decodePublicTransactionEvent(numericData);
+    return decodePublicTransactionEvent(data);
   } catch (error) {

204-266: Consider simplifying the final null check.

The logic correctly parses system instructions and extracts invoke/append data. However, the null check at line 257 is redundant since line 241 already returns null if no system instruction was found, guaranteeing invokeData is non-null beyond that point.

Optionally simplify by removing the redundant check:

   if (invokeData) {
     return convertToPublicTransactionEvent(
       appendInputsData,
       accountKeys[accountKeys.length - 1],
       invokeData,
     );
-  } else {
-    return null;
   }
+  return null;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc46281 and 0cb7034.

📒 Files selected for processing (9)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/vitest.config.ts (1 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/merkle-tree/indexed-array.ts (1 hunks)
  • js/program-test/src/spl-token-utils.ts (1 hunks)
  • js/program-test/src/test-rpc/get-parsed-events.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
  • js/program-test/src/test-utils.ts (1 hunks)
  • js/stateless.js/src/utils/send-and-confirm.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
js/stateless.js/src/utils/send-and-confirm.ts (1)
forester-utils/src/rpc_pool.rs (1)
  • commitment (152-155)
js/program-test/src/litesvm-rpc.ts (3)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (114-934)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (1)
  • defaultTestStateTreeAccounts (236-244)
js/program-test/src/test-utils.ts (1)
js/program-test/src/index.ts (3)
  • newAccountWithLamports (10-10)
  • sleep (11-11)
  • getOrCreateKeypair (12-12)
js/program-test/src/test-rpc/get-parsed-events.ts (3)
js/stateless.js/src/constants.ts (8)
  • defaultStaticAccountsStruct (95-105)
  • accountCompressionProgram (76-77)
  • COMPUTE_BUDGET_PATTERN (52-52)
  • noopProgram (74-74)
  • INVOKE_DISCRIMINATOR (54-56)
  • INVOKE_CPI_DISCRIMINATOR (58-60)
  • INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR (62-64)
  • INSERT_INTO_QUEUES_DISCRIMINATOR (70-72)
js/stateless.js/src/programs/system/layout.ts (5)
  • decodeInstructionDataInvoke (240-246)
  • decodeInstructionDataInvokeCpi (248-254)
  • decodeInstructionDataInvokeCpiWithReadOnly (234-238)
  • deserializeAppendNullifyCreateAddressInputsIndexer (441-514)
  • convertToPublicTransactionEvent (516-598)
js/stateless.js/src/utils/conversion.ts (1)
  • convertInvokeCpiWithReadOnlyToInvoke (121-191)
js/program-test/src/test-rpc/test-rpc.ts (11)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (7)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • WithContext (241-248)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/stateless.js/src/state/types.ts (2)
  • TreeInfo (77-99)
  • PublicTransactionEvent (231-272)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/stateless.js/src/state/compressed-account.ts (2)
  • CompressedAccountWithMerkleContext (14-17)
  • MerkleContextWithMerkleProof (102-115)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
js/compressed-token/tests/program-test/compress.test.ts (11)
js/stateless.js/src/rpc-interface.ts (1)
  • ParsedTokenAccount (236-239)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (890-896)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (75-110)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/stateless.js/src/utils/dedupe-signer.ts (1)
  • dedupeSigner (4-11)
js/stateless.js/src/utils/send-and-confirm.ts (2)
  • buildAndSignTx (145-166)
  • sendAndConfirmTx (51-62)
js/program-test/src/merkle-tree/indexed-array.ts (3)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-68)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/stateless.js/src/constants.ts (1)
  • HIGHEST_ADDRESS_PLUS_ONE (48-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: stateless-js-v1
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: lint
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: cli-v1
🔇 Additional comments (2)
js/program-test/src/test-rpc/get-parsed-events.ts (1)

146-188: LGTM!

The generic event parser correctly filters transactions, extracts NOOP program instructions, and applies the provided deserializer. The null checks and type guards are appropriate.

js/stateless.js/src/utils/send-and-confirm.ts (1)

100-119: Early failure on transaction errors looks solid.

Thanks for short‑circuiting on result.err and clarifying the commitment ladder—this prevents unnecessary polling and keeps the status check aligned with Solana’s confirmation hierarchy.

Comment on lines +96 to +111
txRaw!.meta!.innerInstructions &&
txRaw!.meta!.innerInstructions.length > 0
) {
for (const innerGroup of txRaw!.meta!.innerInstructions) {
for (const ix of innerGroup.instructions) {
const group = ix.accounts.map(
(accountIdx: number) => allAccounts[accountIdx],
);
groupedAccountVec.push(group);
if (ix.data && ix.data.length > 0) {
const decodedData = bs58.decode(ix.data);
dataVec.push(decodedData);
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add null safety check for txRaw.

Using non-null assertions (txRaw!.meta!) without verifying txRaw is not null/undefined is unsafe. The code at line 71 retrieves txRaw from an async call that can return null, and line 76 already uses optional chaining.

Apply this diff to add a safety check:

     const groupedAccountVec: PublicKey[][] = [];
 
+    if (!txRaw || !txRaw.meta) {
+      continue;
+    }
+
     if (
-      txRaw!.meta!.innerInstructions &&
-      txRaw!.meta!.innerInstructions.length > 0
+      txRaw.meta.innerInstructions &&
+      txRaw.meta.innerInstructions.length > 0
     ) {
-      for (const innerGroup of txRaw!.meta!.innerInstructions) {
+      for (const innerGroup of txRaw.meta.innerInstructions) {
         for (const ix of innerGroup.instructions) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
txRaw!.meta!.innerInstructions &&
txRaw!.meta!.innerInstructions.length > 0
) {
for (const innerGroup of txRaw!.meta!.innerInstructions) {
for (const ix of innerGroup.instructions) {
const group = ix.accounts.map(
(accountIdx: number) => allAccounts[accountIdx],
);
groupedAccountVec.push(group);
if (ix.data && ix.data.length > 0) {
const decodedData = bs58.decode(ix.data);
dataVec.push(decodedData);
}
}
}
}
const groupedAccountVec: PublicKey[][] = [];
if (!txRaw || !txRaw.meta) {
continue;
}
if (
txRaw.meta.innerInstructions &&
txRaw.meta.innerInstructions.length > 0
) {
for (const innerGroup of txRaw.meta.innerInstructions) {
for (const ix of innerGroup.instructions) {
const group = ix.accounts.map(
(accountIdx: number) => allAccounts[accountIdx],
);
groupedAccountVec.push(group);
if (ix.data && ix.data.length > 0) {
const decodedData = bs58.decode(ix.data);
dataVec.push(decodedData);
}
}
}
}
🤖 Prompt for AI Agents
In js/program-test/src/test-rpc/get-parsed-events.ts around lines 96 to 111, the
code uses non-null assertions txRaw! and txRaw!.meta! without verifying txRaw is
present; add an explicit null/undefined check for txRaw before accessing meta
(e.g., if (!txRaw) return or continue) and replace the forced non-null
assertions with safe optional chaining or guarded access so the
innerInstructions loop only runs when txRaw and txRaw.meta are defined.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
js/compressed-token/vitest.config.ts (1)

12-13: Document the performance trade-off of sequential test execution.

Setting both maxForks and minForks to 1 eliminates test parallelism, which will significantly increase total test execution time. While this may be necessary for LiteSVM's native module constraints, consider documenting:

  • The approximate performance impact (e.g., "tests now run sequentially, taking ~X minutes instead of ~Y minutes")
  • Whether this limitation applies to all test files or could be scoped to specific LiteSVM-dependent tests
  • Any plans to investigate parallel execution in the future
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cb7034 and a11f54e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • cli/package.json (2 hunks)
  • js/compressed-token/package.json (3 hunks)
  • js/compressed-token/vitest.config.ts (1 hunks)
  • js/program-test/.prettierignore (1 hunks)
  • js/stateless.js/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cli/package.json
  • js/stateless.js/package.json
  • js/program-test/.prettierignore
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v1
  • GitHub Check: lint
  • GitHub Check: stateless-js-v2
  • GitHub Check: cli-v1
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
🔇 Additional comments (1)
js/compressed-token/vitest.config.ts (1)

7-18: Re-examine memoryLimit and isolation strategy

  • memoryLimit: 1 is in megabytes, so tests will recycle immediately upon any allocation, not necessarily between files—causing unpredictable restarts and overhead. Remove it or set a realistic threshold.
  • For true per-file isolation, use Vitest’s isolate: true option or document why the LiteSVM workaround is needed.

@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from a11f54e to bff3d53 Compare October 10, 2025 05:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
js/program-test/src/test-rpc/test-rpc.ts (1)

527-530: Refactor type coercion to avoid as any cast.

The bigint-to-BN conversion uses as any which bypasses TypeScript's type safety. Consider creating a typed helper function for this common pattern.

Add a helper function at the top of the class or in a utility module:

function toBN(value: bigint | BN | number | string): BN {
  if (typeof value === 'bigint') {
    return bn(value.toString());
  }
  return bn(value);
}

Then replace the three occurrences:

-    const amountBN = typeof rawAmount === 'bigint'
-      ? bn((rawAmount as any).toString())
-      : bn(rawAmount);
+    const amountBN = toBN(rawAmount);
-        const balance = typeof rawAmount === 'bigint'
-          ? bn((rawAmount as any).toString())
-          : bn(rawAmount);
+        const balance = toBN(rawAmount);
-          const balance = typeof rawAmount === 'bigint'
-            ? bn((rawAmount as any).toString())
-            : bn(rawAmount);
+          const balance = toBN(rawAmount);

Also applies to: 552-554, 584-586

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 563c919 and 99542c4.

📒 Files selected for processing (3)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/spl-token-utils.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • js/program-test/src/litesvm-rpc.ts
🧰 Additional context used
🧬 Code graph analysis (1)
js/program-test/src/test-rpc/test-rpc.ts (9)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (16)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • LatestNonVotingSignaturesPaginated (57-67)
  • LatestNonVotingSignatures (33-43)
  • GetCompressedTokenAccountsByOwnerOrDelegateOptions (205-209)
  • ParsedTokenAccount (236-239)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • SignatureWithMetadata (69-73)
  • CompressedTransaction (120-142)
  • PaginatedOptions (217-220)
  • CompressedMintTokenHolders (52-55)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/stateless.js/src/state/types.ts (2)
  • TreeInfo (77-99)
  • PublicTransactionEvent (231-272)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: cli-v1
  • GitHub Check: cli-v2
  • GitHub Check: Forester e2e test
  • GitHub Check: stateless-js-v1
  • GitHub Check: stateless-js-v2
  • GitHub Check: lint
🔇 Additional comments (7)
js/program-test/src/test-rpc/test-rpc.ts (7)

1-104: LGTM!

The imports, interface definitions, and factory function are well-structured and follow TypeScript best practices.


114-188: LGTM!

The class structure and basic methods are appropriate for a test mock. Deprecated methods are properly marked.


190-266: LGTM!

The account query methods appropriately delegate to test helpers and include proper error handling for the test context.


376-389: LGTM! V2 leafIndex fix correctly implemented.

The fix properly addresses the previous concern about returning actual on-chain leaf indices. The code now:

  1. Finds the array position via findIndex
  2. Validates the position exists
  3. Retrieves the actual on-chain index from leafIndices[arrayPosition]

This ensures that proveByIndex consumers receive valid on-chain positions rather than local array indices.


597-674: LGTM!

Stub implementations for signature query and health methods are appropriate for a test mock. Throwing errors for unsupported operations provides clear feedback during testing.


684-740: LGTM! Documented limitation for test mock.

The empty allAddresses array and TODO comment at lines 690-692 document that cranked address tree support is intentionally not implemented. The Merkle tree root doesn't advance beyond init() unless the address queue is emptied, which is acceptable for this test-only mock.


742-955: LGTM! Validity proof implementation with documented limitations.

The validity proof methods implement complex logic for combining inclusion and new-address proofs. The TODO comments at lines 916, 920-922, and 950 document known limitations regarding:

  • Better solution needed for hasV1Accounts root handling
  • Dynamic root indices for forester support
  • Custom tree support in V0 method

These are reasonable constraints for a test mock focused on non-forested scenarios.

Comment on lines 194 to 198
console.log('[spl-token-utils.ts:195] Converting amount:', typeof data.amount, data.amount);
const amount =
typeof data.amount === "bigint"
? data.amount
: BigInt((data.amount as any).toString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug logging from the helpers.

The console.log statements left in these helpers will spam every caller and make tests noisy. Please drop them before shipping.

Apply this diff to clean them up:

-  console.log('[spl-token-utils.ts:195] Converting amount:', typeof data.amount, data.amount);
   const amount =
     typeof data.amount === "bigint"
       ? data.amount
       : BigInt((data.amount as any).toString());
@@
-  console.log('[spl-token-utils.ts:223] Converting supply:', typeof data.supply, data.supply);
-
   const supply =
     typeof data.supply === "bigint"
       ? data.supply
       : BigInt((data.supply as any).toString());

Also applies to: 222-227

🤖 Prompt for AI Agents
In js/program-test/src/spl-token-utils.ts around lines 194-198 (and similarly
222-227), remove the leftover console.log debugging statements so helpers no
longer print during normal operation; simply delete the console.log lines and
leave the amount conversion logic unchanged (i.e., keep the typeof check and
BigInt conversion) ensuring no other behavior is modified.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (7)
js/program-test/src/test-rpc/get-parsed-events.ts (1)

95-111: Add null safety check for txRaw.

The code uses non-null assertions (txRaw!.meta!) without verifying txRaw is not null. Line 71 retrieves txRaw from getTransaction, which can return null.

Apply this diff to add a safety check:

     const groupedAccountVec: PublicKey[][] = [];
 
+    if (!txRaw || !txRaw.meta) {
+      continue;
+    }
+
     if (
-      txRaw!.meta!.innerInstructions &&
-      txRaw!.meta!.innerInstructions.length > 0
+      txRaw.meta.innerInstructions &&
+      txRaw.meta.innerInstructions.length > 0
     ) {
-      for (const innerGroup of txRaw!.meta!.innerInstructions) {
+      for (const innerGroup of txRaw.meta.innerInstructions) {
         for (const ix of innerGroup.instructions) {
           const group = ix.accounts.map(
             (accountIdx: number) => allAccounts[accountIdx],
js/program-test/src/litesvm-rpc.ts (1)

459-484: Convert transaction signatures to base58 strings.

The signatures field at line 471 returns Transaction.signatures objects or raw signature arrays, but JSON-RPC getTransaction expects an array of base58-encoded signature strings. Legacy transactions have signatures: Array<{signature: Buffer | null, publicKey: PublicKey}>, while versioned transactions have signatures: Uint8Array[]. Both need conversion to base58 strings.

Apply this diff to convert signatures to the correct format:

+    // Convert signatures to base58 strings
+    let signatures: string[];
+    if ("signatures" in rawTx && Array.isArray(rawTx.signatures)) {
+      if (rawTx.signatures.length > 0) {
+        // Check if it's legacy (object with signature/publicKey) or versioned (Uint8Array)
+        const firstSig = rawTx.signatures[0];
+        if (firstSig && typeof firstSig === 'object' && 'signature' in firstSig) {
+          // Legacy transaction signatures
+          signatures = rawTx.signatures
+            .map((sig: any) => sig.signature ? bs58.encode(sig.signature) : '')
+            .filter((s: string) => s !== '');
+        } else {
+          // Versioned transaction signatures (Uint8Array[])
+          signatures = rawTx.signatures.map((sig: Uint8Array) => bs58.encode(sig));
+        }
+      } else {
+        signatures = [];
+      }
+    } else {
+      // Fallback to stored signature
+      signatures = [sigString];
+    }
+
     return {
       slot: tx.slot,
       blockTime: tx.blockTime,
       transaction: {
         message: {
           accountKeys,
           compiledInstructions,
           recentBlockhash:
             message.recentBlockhash || message.header?.recentBlockhash || "",
           addressTableLookups: message.addressTableLookups || [],
         },
-        signatures:
-          "signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
+        signatures,
       },
js/program-test/src/spl-token-utils.ts (2)

190-211: Remove debug console.log statement.

Lines 201-205 contain debug logging that should be removed before merging. This will spam logs during normal test execution.

Apply this diff:

   const data = AccountLayout.decode(accountInfo.data);
-  console.log(
-    "[spl-token-utils.ts:195] Converting amount:",
-    typeof data.amount,
-    data.amount,
-  );
   const amount =
     typeof data.amount === "bigint"
       ? data.amount
       : BigInt((data.amount as any).toString());
   return amount;

216-259: Remove debug console.log statement.

Lines 233-237 contain debug logging that should be removed before merging.

Apply this diff:

   const data = MintLayout.decode(accountInfo.data);
-  console.log(
-    "[spl-token-utils.ts:223] Converting supply:",
-    typeof data.supply,
-    data.supply,
-  );
-
   const supply =
     typeof data.supply === "bigint"
       ? data.supply
       : BigInt((data.supply as any).toString());
js/program-test/src/test-rpc/test-rpc.ts (3)

522-531: Remove debug console.log statement.

Line 525 contains a debug log that should be removed before merging. This is a development artifact that will spam logs during test execution.

Apply this diff to remove the debug log:

     const account = await getCompressedTokenAccountByHashTest(this, hash);
     const rawAmount = account.parsed.amount;
-    console.log('[test-rpc.ts:524] Converting amount:', typeof rawAmount, rawAmount);
     // Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)
     const amountBN = typeof rawAmount === 'bigint'
       ? bn(String(rawAmount))
       : bn(rawAmount);
     return { amount: amountBN };

538-562: Remove debug console.log statement.

Line 550 contains a debug log that should be removed before merging.

Apply this diff:

       items: accounts.items.map((account) => {
         const rawAmount = account.parsed.amount;
-        console.log('[test-rpc.ts:543] Converting amount:', typeof rawAmount, rawAmount);
         // Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)
         const balance = typeof rawAmount === 'bigint'
           ? bn(String(rawAmount))
           : bn(rawAmount);

568-595: Remove debug console.log statement.

Line 582 contains a debug log that should be removed before merging.

Apply this diff:

         items: accounts.items.map((account) => {
           const rawAmount = account.parsed.amount;
-          console.log('[test-rpc.ts:567] Converting amount:', typeof rawAmount, rawAmount);
           // Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)
           const balance = typeof rawAmount === 'bigint'
             ? bn(String(rawAmount))
             : bn(rawAmount);
🧹 Nitpick comments (2)
js/program-test/src/test-rpc/get-parsed-events.ts (1)

194-194: Remove redundant Buffer mapping.

The expression Buffer.from(data.map((byte) => byte)) is redundant since data is already a Buffer and mapping each byte to itself adds unnecessary overhead.

Apply this diff:

-  const numericData = Buffer.from(data.map((byte) => byte));
+  const numericData = Buffer.from(data);
js/program-test/src/test-rpc/test-rpc.ts (1)

411-424: Document or explicitly handle StateV2 validation.

The validation loop (lines 412-423) only validates StateV1 proofs and silently skips StateV2 proofs. Since StateV2 uses proveByIndex: true with zero-filled paths, this may be intentional. However, the silent skip makes the intent unclear.

Consider adding an explicit comment or handling for StateV2:

     // Validate proofs
     merkleProofsMap.forEach((proof, index) => {
       if (proof.treeInfo.treeType === TreeType.StateV1) {
         const leafIndex = proof.leafIndex;
         const computedHash = leavesByTree.get(proof.treeInfo.tree.toBase58())!
           .leaves[leafIndex];
         const hashArr = bn(computedHash);
         if (!hashArr.eq(proof.hash)) {
           throw new Error(
             `Mismatch at index ${index}: expected ${proof.hash.toString()}, got ${hashArr.toString()}`,
           );
         }
+      } else if (proof.treeInfo.treeType === TreeType.StateV2) {
+        // StateV2 uses proveByIndex with zero-filled paths - validation not required
+        // as the proof structure is deterministic and leaf index is verified via on-chain index
       }
     });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99542c4 and 0afa6c5.

📒 Files selected for processing (4)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/spl-token-utils.ts (1 hunks)
  • js/program-test/src/test-rpc/get-parsed-events.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
js/program-test/src/litesvm-rpc.ts (4)
js/program-test/src/index.ts (3)
  • LiteSVMRpc (8-8)
  • LiteSVMConfig (14-14)
  • createLiteSVMRpc (8-8)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (114-956)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • COMPRESSED_TOKEN_PROGRAM_ID (256-258)
  • featureFlags (16-35)
js/program-test/src/test-rpc/test-rpc.ts (9)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (16)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • LatestNonVotingSignaturesPaginated (57-67)
  • LatestNonVotingSignatures (33-43)
  • GetCompressedTokenAccountsByOwnerOrDelegateOptions (205-209)
  • ParsedTokenAccount (236-239)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • SignatureWithMetadata (69-73)
  • CompressedTransaction (120-142)
  • PaginatedOptions (217-220)
  • CompressedMintTokenHolders (52-55)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
js/program-test/src/test-rpc/get-parsed-events.ts (4)
js/stateless.js/src/state/types.ts (2)
  • PublicTransactionEvent (231-272)
  • InstructionDataInvoke (277-306)
js/stateless.js/src/constants.ts (8)
  • defaultStaticAccountsStruct (95-105)
  • accountCompressionProgram (76-77)
  • COMPUTE_BUDGET_PATTERN (52-52)
  • noopProgram (74-74)
  • INVOKE_DISCRIMINATOR (54-56)
  • INVOKE_CPI_DISCRIMINATOR (58-60)
  • INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR (62-64)
  • INSERT_INTO_QUEUES_DISCRIMINATOR (70-72)
js/stateless.js/src/programs/system/layout.ts (6)
  • decodePublicTransactionEvent (392-396)
  • decodeInstructionDataInvoke (240-246)
  • decodeInstructionDataInvokeCpi (248-254)
  • decodeInstructionDataInvokeCpiWithReadOnly (234-238)
  • deserializeAppendNullifyCreateAddressInputsIndexer (441-514)
  • convertToPublicTransactionEvent (516-598)
js/stateless.js/src/utils/conversion.ts (1)
  • convertInvokeCpiWithReadOnlyToInvoke (121-191)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: lint
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v1
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: Forester e2e test
  • GitHub Check: cli-v1
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: stateless-js-v2
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
🔇 Additional comments (1)
js/program-test/src/test-rpc/get-parsed-events.ts (1)

76-91: LGTM! Base64 decoding issue resolved.

The code now correctly handles both string (base64) and Uint8Array cases for ix.data. The string case is properly base64-decoded, and the Uint8Array case is used directly without incorrect re-encoding.

Comment on lines +324 to +355
console.log("signatureBytes ", signatureBytes);
const signature = bs58.encode(signatureBytes);
console.log("bs58 signature ", signature);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements.

Lines 324 and 326 contain debug logging that should be removed before merging. These are development artifacts that will spam logs during test execution.

Apply this diff to remove the debug logs:

     const logs = result.logs();
     const signatureBytes = result.signature();
-    console.log("signatureBytes ", signatureBytes);
     const signature = bs58.encode(signatureBytes);
-    console.log("bs58 signature ", signature);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("signatureBytes ", signatureBytes);
const signature = bs58.encode(signatureBytes);
console.log("bs58 signature ", signature);
const logs = result.logs();
const signatureBytes = result.signature();
const signature = bs58.encode(signatureBytes);
🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 324 to 326, remove the two
debug console.log statements that print signatureBytes and the bs58 signature;
keep the bs58.encode(signatureBytes) assignment to signature and any necessary
surrounding code intact, and run lint/format to ensure no leftover trailing
whitespace or unused-variable warnings.

Comment on lines +257 to +262
if (invokeData) {
return convertToPublicTransactionEvent(
appendInputsData,
accountKeys[accountKeys.length - 1],
invokeData,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Verify accountKeys is non-empty before accessing.

Line 260 accesses accountKeys[accountKeys.length - 1] without verifying the array is non-empty. If no inner instructions were processed, accountKeys could be empty, resulting in undefined being passed to convertToPublicTransactionEvent.

Apply this diff to add a safety check:

   if (invokeData) {
+    if (accountKeys.length === 0) {
+      return null;
+    }
     return convertToPublicTransactionEvent(
       appendInputsData,
       accountKeys[accountKeys.length - 1],
       invokeData,
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (invokeData) {
return convertToPublicTransactionEvent(
appendInputsData,
accountKeys[accountKeys.length - 1],
invokeData,
);
if (invokeData) {
if (accountKeys.length === 0) {
return null;
}
return convertToPublicTransactionEvent(
appendInputsData,
accountKeys[accountKeys.length - 1],
invokeData,
);
}
🤖 Prompt for AI Agents
In js/program-test/src/test-rpc/get-parsed-events.ts around lines 257 to 262,
the code accesses accountKeys[accountKeys.length - 1] without ensuring
accountKeys is non-empty; add an explicit guard such as if (!accountKeys ||
accountKeys.length === 0) return undefined (or otherwise handle/error out)
before calling convertToPublicTransactionEvent, and only pass
accountKeys[accountKeys.length - 1] when the array has at least one element.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
js/program-test/src/test-rpc/test-rpc.ts (2)

424-437: Add explicit StateV2 validation or document skip rationale.

The validation loop only validates StateV1 proofs; StateV2 proofs are silently skipped. Given that StateV2 uses proveByIndex: true with zero-valued paths and a fixed leafIndex from the on-chain index, add explicit validation (e.g., check that leafIndex is within bounds, rootIndex is 0, path elements match _zeros) or document why StateV2 proofs don't require validation.

Apply this diff to add explicit handling:

     // Validate proofs
     merkleProofsMap.forEach((proof, index) => {
       if (proof.treeInfo.treeType === TreeType.StateV1) {
         const leafIndex = proof.leafIndex;
         const computedHash = leavesByTree.get(proof.treeInfo.tree.toBase58())!
           .leaves[leafIndex];
         const hashArr = bn(computedHash);
         if (!hashArr.eq(proof.hash)) {
           throw new Error(
             `Mismatch at index ${index}: expected ${proof.hash.toString()}, got ${hashArr.toString()}`,
           );
         }
+      } else if (proof.treeInfo.treeType === TreeType.StateV2) {
+        // StateV2 proofs use proveByIndex with zero-valued paths.
+        // Validate that leafIndex is non-negative and rootIndex is 0.
+        if (proof.leafIndex < 0) {
+          throw new Error(`Invalid StateV2 leafIndex at index ${index}: ${proof.leafIndex}`);
+        }
+        if (proof.rootIndex !== 0) {
+          throw new Error(`Invalid StateV2 rootIndex at index ${index}: expected 0, got ${proof.rootIndex}`);
+        }
       }
     });

541-541: Remove debug console.log statement (still present).

This console.log was flagged in a previous review and claimed to be addressed in commit 0afa6c5, but it's still present. Please remove it before merging.

Apply this diff:

     const account = await getCompressedTokenAccountByHashTest(this, hash);
     const rawAmount = account.parsed.amount;
-    console.log('[test-rpc.ts:524] Converting amount:', typeof rawAmount, rawAmount);
     // Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)

Also applies to: 566-566, 598-598

🧹 Nitpick comments (2)
js/program-test/src/test-rpc/test-rpc.ts (2)

389-392: Use already-available leaves variable instead of redundant lookup.

Line 390-392 performs leavesByTree.get(tree.toBase58())!.leaves.findIndex(...), but leaves is already available from the destructuring at line 334. Use the simpler leaves.findIndex(...) instead.

Apply this diff:

             /// Find array position, then get actual on-chain leaf index
-            const arrayPosition = leavesByTree
-              .get(tree.toBase58())!
-              .leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
+            const arrayPosition = leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));

364-383: Consider using on-chain leafIndex for StateV1 consistency.

StateV1 currently uses the array position from findIndex directly as leafIndex (line 364, 376). While this may work under test-rpc's constraints (fresh tree, <1000 transactions), for consistency with the StateV2 fix (lines 389-402) and to guard against scenarios where leaf indices are non-contiguous, consider explicitly retrieving the on-chain leaf index from the stored leafIndices array.

This would make the code more robust and consistent across both tree types.

Apply this diff:

       for (let i = 0; i < hashes.length; i++) {
-        const leafIndex = leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
+        const arrayPosition = leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
 
         /// If leaf is part of current tree, return proof
-        if (leafIndex !== -1) {
+        if (arrayPosition !== -1) {
           if (treeInfo.treeType === TreeType.StateV1) {
-            const pathElements = merkleTree.path(leafIndex).pathElements;
+            const pathElements = merkleTree.path(arrayPosition).pathElements;
             const bnPathElements = pathElements.map((value) => bn(value));
             const root = bn(merkleTree.root());
 
+            const leafIndex = leavesByTree.get(tree.toBase58())!.leafIndices[arrayPosition];
+
             const merkleProof: MerkleContextWithMerkleProof = {
               hash: bn(hashes[i].toArray("be", 32)),
               treeInfo,
               leafIndex,
               merkleProof: bnPathElements,
               proveByIndex: false,
-              rootIndex: leaves.length,
+              rootIndex: leaves.length, 
               root,
             };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f940635 and c70d71b.

📒 Files selected for processing (1)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
js/program-test/src/test-rpc/test-rpc.ts (9)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (16)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • LatestNonVotingSignaturesPaginated (57-67)
  • LatestNonVotingSignatures (33-43)
  • GetCompressedTokenAccountsByOwnerOrDelegateOptions (205-209)
  • ParsedTokenAccount (236-239)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • SignatureWithMetadata (69-73)
  • CompressedTransaction (120-142)
  • PaginatedOptions (217-220)
  • CompressedMintTokenHolders (52-55)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: Forester e2e test
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v1
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: lint

Comment on lines 275 to 282
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: INPUT - hashes:', hashes.map(h => h.toString('hex').slice(0, 16) + '...'));

// Parse events and organize leaves by their respective merkle trees
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Calling getParsedEvents...');
const events: PublicTransactionEvent[] = await getParsedEvents(this).then(
(events) => events.reverse(),
);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Got', events.length, 'events');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements.

Multiple debug console.log statements appear at lines 275, 278, and 282. These should be removed before merging to production.

Apply this diff:

-    console.log('[TEST-RPC] getMultipleCompressedAccountProofs: INPUT - hashes:', hashes.map(h => h.toString('hex').slice(0, 16) + '...'));
-
     // Parse events and organize leaves by their respective merkle trees
-    console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Calling getParsedEvents...');
     const events: PublicTransactionEvent[] = await getParsedEvents(this).then(
       (events) => events.reverse(),
     );
-    console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Got', events.length, 'events');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: INPUT - hashes:', hashes.map(h => h.toString('hex').slice(0, 16) + '...'));
// Parse events and organize leaves by their respective merkle trees
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Calling getParsedEvents...');
const events: PublicTransactionEvent[] = await getParsedEvents(this).then(
(events) => events.reverse(),
);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Got', events.length, 'events');
// Parse events and organize leaves by their respective merkle trees
const events: PublicTransactionEvent[] = await getParsedEvents(this).then(
(events) => events.reverse(),
);
🤖 Prompt for AI Agents
js/program-test/src/test-rpc/test-rpc.ts around lines 275 to 282: remove the
three debug console.log statements that log hashes, a pre-call message for
getParsedEvents, and the events count; simply delete those console.log lines (or
replace them with an appropriate logger debug call if project logging
conventions require) so no debug console output remains in production code.

Comment on lines 332 to 465
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Processing', leavesByTree.size, 'trees');

for (const [treeKey, { leaves, treeInfo }] of leavesByTree.entries()) {
const tree = new PublicKey(treeKey);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Processing tree:', treeKey, 'with', leaves.length, 'leaves, treeType:', treeInfo.treeType);

let merkleTree: MerkleTree | undefined;
if (treeInfo.treeType === TreeType.StateV1) {
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Creating V1 MerkleTree with depth', this.depth);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: All leaves:', JSON.stringify(leaves));
const leafStrings = leaves.map((leaf) => bn(leaf).toString());
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Leaf strings:', JSON.stringify(leafStrings));
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Calling new MerkleTree...');
merkleTree = new MerkleTree(
this.depth,
this.lightWasm,
leafStrings,
);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: MerkleTree created successfully');
} else if (treeInfo.treeType === TreeType.StateV2) {
/// In V2 State trees, The Merkle tree stays empty until the
/// first forester transaction. And since test-rpc is only used
/// for non-forested tests, we must return a tree with
/// zerovalues.
merkleTree = new MerkleTree(32, this.lightWasm, []);
} else {
throw new Error(
`Invalid tree type: ${treeInfo.treeType} in test-rpc.ts`,
);
}

for (let i = 0; i < hashes.length; i++) {
const leafIndex = leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));

/// If leaf is part of current tree, return proof
if (leafIndex !== -1) {
if (treeInfo.treeType === TreeType.StateV1) {
const pathElements = merkleTree.path(leafIndex).pathElements;
const bnPathElements = pathElements.map((value) => bn(value));
const root = bn(merkleTree.root());

const merkleProof: MerkleContextWithMerkleProof = {
hash: bn(hashes[i].toArray("be", 32)),
treeInfo,
leafIndex,
merkleProof: bnPathElements,
proveByIndex: false,
rootIndex: leaves.length,
root,
};

merkleProofsMap.set(hashes[i].toString(), merkleProof);
} else if (treeInfo.treeType === TreeType.StateV2) {
const pathElements = merkleTree._zeros.slice(0, -1);
const bnPathElements = pathElements.map((value) => bn(value));
const root = bn(merkleTree.root());

/// Find array position, then get actual on-chain leaf index
const arrayPosition = leavesByTree
.get(tree.toBase58())!
.leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));

if (arrayPosition === -1) {
throw new Error(
`Hash ${hashes[i].toString()} not found in tree ${tree.toBase58()}`,
);
}

const leafIndex = leavesByTree.get(tree.toBase58())!.leafIndices[
arrayPosition
];

const merkleProof: MerkleContextWithMerkleProof = {
// Hash is 0 for proveByIndex trees in test-rpc.
hash: bn(hashes[i].toArray("be", 32)),
// hash: bn(new Array(32).fill(0)),
treeInfo,
leafIndex,
merkleProof: bnPathElements,
proveByIndex: true,
// Root index is 0 for proveByIndex trees in
// test-rpc.
rootIndex: 0,
root,
};

merkleProofsMap.set(hashes[i].toString(), merkleProof);
}
}
}
}

// Validate proofs
merkleProofsMap.forEach((proof, index) => {
if (proof.treeInfo.treeType === TreeType.StateV1) {
const leafIndex = proof.leafIndex;
const computedHash = leavesByTree.get(proof.treeInfo.tree.toBase58())!
.leaves[leafIndex];
const hashArr = bn(computedHash);
if (!hashArr.eq(proof.hash)) {
throw new Error(
`Mismatch at index ${index}: expected ${proof.hash.toString()}, got ${hashArr.toString()}`,
);
}
}
});

// Ensure all requested hashes belong to the same tree type
const uniqueTreeTypes = new Set(
hashes.map((hash) => {
const proof = merkleProofsMap.get(hash.toString());
if (!proof) {
throw new Error(`Proof not found for hash: ${hash.toString()}`);
}
return proof.treeInfo.treeType;
}),
);

if (uniqueTreeTypes.size > 1) {
throw new Error(
"Requested hashes belong to different tree types (V1/V2)",
);
}

// Return proofs in the order of requested hashes
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Returning proofs for', hashes.length, 'hashes');
const results = hashes.map((hash) => {
const proof = merkleProofsMap.get(hash.toString());
if (!proof) {
throw new Error(`No proof found for hash: ${hash.toString()}`);
}
return proof;
});
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: OUTPUT - Success, returning', results.length, 'proofs');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove remaining debug console.log statements.

Additional debug console.log statements remain at lines 332, 336, 340, 342, 343, 344, 350, 457, and 465. Remove all of these before merging.

Apply this diff:

-    console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Processing', leavesByTree.size, 'trees');
-
     for (const [treeKey, { leaves, treeInfo }] of leavesByTree.entries()) {
       const tree = new PublicKey(treeKey);
-      console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Processing tree:', treeKey, 'with', leaves.length, 'leaves, treeType:', treeInfo.treeType);
 
       let merkleTree: MerkleTree | undefined;
       if (treeInfo.treeType === TreeType.StateV1) {
-        console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Creating V1 MerkleTree with depth', this.depth);
-        console.log('[TEST-RPC] getMultipleCompressedAccountProofs: All leaves:', JSON.stringify(leaves));
         const leafStrings = leaves.map((leaf) => bn(leaf).toString());
-        console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Leaf strings:', JSON.stringify(leafStrings));
-        console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Calling new MerkleTree...');
         merkleTree = new MerkleTree(
           this.depth,
           this.lightWasm,
           leafStrings,
         );
-        console.log('[TEST-RPC] getMultipleCompressedAccountProofs: MerkleTree created successfully');
       }
       // ... rest of code ...
     }
     // ... validation code ...
 
     // Return proofs in the order of requested hashes
-    console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Returning proofs for', hashes.length, 'hashes');
     const results = hashes.map((hash) => {
       // ... mapping code ...
     });
-    console.log('[TEST-RPC] getMultipleCompressedAccountProofs: OUTPUT - Success, returning', results.length, 'proofs');
     return results;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Processing', leavesByTree.size, 'trees');
for (const [treeKey, { leaves, treeInfo }] of leavesByTree.entries()) {
const tree = new PublicKey(treeKey);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Processing tree:', treeKey, 'with', leaves.length, 'leaves, treeType:', treeInfo.treeType);
let merkleTree: MerkleTree | undefined;
if (treeInfo.treeType === TreeType.StateV1) {
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Creating V1 MerkleTree with depth', this.depth);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: All leaves:', JSON.stringify(leaves));
const leafStrings = leaves.map((leaf) => bn(leaf).toString());
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Leaf strings:', JSON.stringify(leafStrings));
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Calling new MerkleTree...');
merkleTree = new MerkleTree(
this.depth,
this.lightWasm,
leafStrings,
);
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: MerkleTree created successfully');
} else if (treeInfo.treeType === TreeType.StateV2) {
/// In V2 State trees, The Merkle tree stays empty until the
/// first forester transaction. And since test-rpc is only used
/// for non-forested tests, we must return a tree with
/// zerovalues.
merkleTree = new MerkleTree(32, this.lightWasm, []);
} else {
throw new Error(
`Invalid tree type: ${treeInfo.treeType} in test-rpc.ts`,
);
}
for (let i = 0; i < hashes.length; i++) {
const leafIndex = leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
/// If leaf is part of current tree, return proof
if (leafIndex !== -1) {
if (treeInfo.treeType === TreeType.StateV1) {
const pathElements = merkleTree.path(leafIndex).pathElements;
const bnPathElements = pathElements.map((value) => bn(value));
const root = bn(merkleTree.root());
const merkleProof: MerkleContextWithMerkleProof = {
hash: bn(hashes[i].toArray("be", 32)),
treeInfo,
leafIndex,
merkleProof: bnPathElements,
proveByIndex: false,
rootIndex: leaves.length,
root,
};
merkleProofsMap.set(hashes[i].toString(), merkleProof);
} else if (treeInfo.treeType === TreeType.StateV2) {
const pathElements = merkleTree._zeros.slice(0, -1);
const bnPathElements = pathElements.map((value) => bn(value));
const root = bn(merkleTree.root());
/// Find array position, then get actual on-chain leaf index
const arrayPosition = leavesByTree
.get(tree.toBase58())!
.leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
if (arrayPosition === -1) {
throw new Error(
`Hash ${hashes[i].toString()} not found in tree ${tree.toBase58()}`,
);
}
const leafIndex = leavesByTree.get(tree.toBase58())!.leafIndices[
arrayPosition
];
const merkleProof: MerkleContextWithMerkleProof = {
// Hash is 0 for proveByIndex trees in test-rpc.
hash: bn(hashes[i].toArray("be", 32)),
// hash: bn(new Array(32).fill(0)),
treeInfo,
leafIndex,
merkleProof: bnPathElements,
proveByIndex: true,
// Root index is 0 for proveByIndex trees in
// test-rpc.
rootIndex: 0,
root,
};
merkleProofsMap.set(hashes[i].toString(), merkleProof);
}
}
}
}
// Validate proofs
merkleProofsMap.forEach((proof, index) => {
if (proof.treeInfo.treeType === TreeType.StateV1) {
const leafIndex = proof.leafIndex;
const computedHash = leavesByTree.get(proof.treeInfo.tree.toBase58())!
.leaves[leafIndex];
const hashArr = bn(computedHash);
if (!hashArr.eq(proof.hash)) {
throw new Error(
`Mismatch at index ${index}: expected ${proof.hash.toString()}, got ${hashArr.toString()}`,
);
}
}
});
// Ensure all requested hashes belong to the same tree type
const uniqueTreeTypes = new Set(
hashes.map((hash) => {
const proof = merkleProofsMap.get(hash.toString());
if (!proof) {
throw new Error(`Proof not found for hash: ${hash.toString()}`);
}
return proof.treeInfo.treeType;
}),
);
if (uniqueTreeTypes.size > 1) {
throw new Error(
"Requested hashes belong to different tree types (V1/V2)",
);
}
// Return proofs in the order of requested hashes
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: Returning proofs for', hashes.length, 'hashes');
const results = hashes.map((hash) => {
const proof = merkleProofsMap.get(hash.toString());
if (!proof) {
throw new Error(`No proof found for hash: ${hash.toString()}`);
}
return proof;
});
console.log('[TEST-RPC] getMultipleCompressedAccountProofs: OUTPUT - Success, returning', results.length, 'proofs');
for (const [treeKey, { leaves, treeInfo }] of leavesByTree.entries()) {
const tree = new PublicKey(treeKey);
let merkleTree: MerkleTree | undefined;
if (treeInfo.treeType === TreeType.StateV1) {
const leafStrings = leaves.map((leaf) => bn(leaf).toString());
merkleTree = new MerkleTree(
this.depth,
this.lightWasm,
leafStrings,
);
} else if (treeInfo.treeType === TreeType.StateV2) {
/// In V2 State trees, The Merkle tree stays empty until the
/// first forester transaction. And since test-rpc is only used
/// for non-forested tests, we must return a tree with
/// zerovalues.
merkleTree = new MerkleTree(32, this.lightWasm, []);
} else {
throw new Error(
`Invalid tree type: ${treeInfo.treeType} in test-rpc.ts`,
);
}
for (let i = 0; i < hashes.length; i++) {
const leafIndex = leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
/// If leaf is part of current tree, return proof
if (leafIndex !== -1) {
if (treeInfo.treeType === TreeType.StateV1) {
const pathElements = merkleTree.path(leafIndex).pathElements;
const bnPathElements = pathElements.map((value) => bn(value));
const root = bn(merkleTree.root());
const merkleProof: MerkleContextWithMerkleProof = {
hash: bn(hashes[i].toArray("be", 32)),
treeInfo,
leafIndex,
merkleProof: bnPathElements,
proveByIndex: false,
rootIndex: leaves.length,
root,
};
merkleProofsMap.set(hashes[i].toString(), merkleProof);
} else if (treeInfo.treeType === TreeType.StateV2) {
const pathElements = merkleTree._zeros.slice(0, -1);
const bnPathElements = pathElements.map((value) => bn(value));
const root = bn(merkleTree.root());
/// Find array position, then get actual on-chain leaf index
const arrayPosition = leavesByTree
.get(tree.toBase58())!
.leaves.findIndex((leaf) => bn(leaf).eq(hashes[i]));
if (arrayPosition === -1) {
throw new Error(
`Hash ${hashes[i].toString()} not found in tree ${tree.toBase58()}`,
);
}
const leafIndex = leavesByTree.get(tree.toBase58())!.leafIndices[
arrayPosition
];
const merkleProof: MerkleContextWithMerkleProof = {
// Hash is 0 for proveByIndex trees in test-rpc.
hash: bn(hashes[i].toArray("be", 32)),
treeInfo,
leafIndex,
merkleProof: bnPathElements,
proveByIndex: true,
// Root index is 0 for proveByIndex trees in
// test-rpc.
rootIndex: 0,
root,
};
merkleProofsMap.set(hashes[i].toString(), merkleProof);
}
}
}
}
// Validate proofs
merkleProofsMap.forEach((proof, index) => {
if (proof.treeInfo.treeType === TreeType.StateV1) {
const leafIndex = proof.leafIndex;
const computedHash = leavesByTree.get(proof.treeInfo.tree.toBase58())!
.leaves[leafIndex];
const hashArr = bn(computedHash);
if (!hashArr.eq(proof.hash)) {
throw new Error(
`Mismatch at index ${index}: expected ${proof.hash.toString()}, got ${hashArr.toString()}`,
);
}
}
});
// Ensure all requested hashes belong to the same tree type
const uniqueTreeTypes = new Set(
hashes.map((hash) => {
const proof = merkleProofsMap.get(hash.toString());
if (!proof) {
throw new Error(`Proof not found for hash: ${hash.toString()}`);
}
return proof.treeInfo.treeType;
}),
);
if (uniqueTreeTypes.size > 1) {
throw new Error(
"Requested hashes belong to different tree types (V1/V2)",
);
}
// Return proofs in the order of requested hashes
const results = hashes.map((hash) => {
const proof = merkleProofsMap.get(hash.toString());
if (!proof) {
throw new Error(`No proof found for hash: ${hash.toString()}`);
}
return proof;
});
return results;
🤖 Prompt for AI Agents
In js/program-test/src/test-rpc/test-rpc.ts around lines 332-465, remove the
leftover debug console.log statements at lines 332, 336, 340, 342, 343, 344,
350, 457, and 465; delete those console.log calls (do not replace with console
statements) and ensure the remaining logic and variable usages compile cleanly
after removal.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7407e0 and 326ab0a.

📒 Files selected for processing (2)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • js/program-test/src/litesvm-rpc.ts
🧰 Additional context used
🧬 Code graph analysis (1)
js/program-test/src/test-rpc/test-rpc.ts (10)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (16)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • LatestNonVotingSignaturesPaginated (57-67)
  • LatestNonVotingSignatures (33-43)
  • GetCompressedTokenAccountsByOwnerOrDelegateOptions (205-209)
  • ParsedTokenAccount (236-239)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • SignatureWithMetadata (69-73)
  • CompressedTransaction (120-142)
  • PaginatedOptions (217-220)
  • CompressedMintTokenHolders (52-55)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/stateless.js/src/state/types.ts (2)
  • TreeInfo (77-99)
  • PublicTransactionEvent (231-272)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Forester e2e test
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: cli-v1
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: stateless-js-v1
  • GitHub Check: lint

Comment on lines +675 to +686
async getCompressedTokenAccountBalance(hash: BN254): Promise<{ amount: BN }> {
const account = await getCompressedTokenAccountByHashTest(this, hash);
const rawAmount = account.parsed.amount;
console.log(
"[test-rpc.ts:524] Converting amount:",
typeof rawAmount,
rawAmount,
);
// Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)
const amountBN =
typeof rawAmount === "bigint" ? bn(String(rawAmount)) : bn(rawAmount);
return { amount: amountBN };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against missing token account.

getCompressedTokenAccountByHashTest can return undefined, so dereferencing account.parsed throws a plain TypeError instead of the expected “account not found” error. Add the same null check we do for compressed accounts before reading fields.

   async getCompressedTokenAccountBalance(hash: BN254): Promise<{ amount: BN }> {
-    const account = await getCompressedTokenAccountByHashTest(this, hash);
-    const rawAmount = account.parsed.amount;
+    const account = await getCompressedTokenAccountByHashTest(this, hash);
+    if (!account) {
+      throw new Error(
+        `Compressed token account not found for hash ${hash.toString()}`,
+      );
+    }
+    const rawAmount = account.parsed.amount;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getCompressedTokenAccountBalance(hash: BN254): Promise<{ amount: BN }> {
const account = await getCompressedTokenAccountByHashTest(this, hash);
const rawAmount = account.parsed.amount;
console.log(
"[test-rpc.ts:524] Converting amount:",
typeof rawAmount,
rawAmount,
);
// Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)
const amountBN =
typeof rawAmount === "bigint" ? bn(String(rawAmount)) : bn(rawAmount);
return { amount: amountBN };
}
async getCompressedTokenAccountBalance(hash: BN254): Promise<{ amount: BN }> {
const account = await getCompressedTokenAccountByHashTest(this, hash);
if (!account) {
throw new Error(
`Compressed token account not found for hash ${hash.toString()}`,
);
}
const rawAmount = account.parsed.amount;
console.log(
"[test-rpc.ts:524] Converting amount:",
typeof rawAmount,
rawAmount,
);
// Convert amount to BN first (could be bigint or BN from Borsh u64 decoder)
const amountBN =
typeof rawAmount === "bigint" ? bn(String(rawAmount)) : bn(rawAmount);
return { amount: amountBN };
}
🤖 Prompt for AI Agents
In js/program-test/src/test-rpc/test-rpc.ts around lines 675 to 687, the code
dereferences account.parsed without checking whether
getCompressedTokenAccountByHashTest returned undefined; add the same null guard
used elsewhere for compressed accounts: if account is undefined/null, throw the
same “account not found” (or “Compressed token account not found”) error used by
other compressed-account helpers, otherwise proceed to read account.parsed and
convert amount to BN; ensure the thrown error message and type match the
existing pattern so callers receive the expected error.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
js/program-test/src/litesvm-rpc.ts (2)

353-355: Remove debug console.log statements.

These debug logs were flagged in a previous review and should be removed before merging.

Apply this diff:

     const logs = result.logs();
     const signatureBytes = result.signature();
-    console.log("signatureBytes ", signatureBytes);
     const signature = bs58.encode(signatureBytes);
-    console.log("bs58 signature ", signature);

488-513: Fix transaction signatures format.

The current implementation returns Transaction.signatures (objects with publicKey and signature fields) for legacy transactions, but the JSON-RPC getTransaction spec requires an array of base58-encoded signature strings.

Apply this diff to use the stored base58 signature:

     return {
       slot: tx.slot,
       blockTime: tx.blockTime,
       transaction: {
         message: {
           accountKeys,
           compiledInstructions,
           recentBlockhash:
             message.recentBlockhash || message.header?.recentBlockhash || "",
           addressTableLookups: message.addressTableLookups || [],
         },
-        signatures:
-          "signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
+        signatures: [tx.signature],
       },
       meta: {
         err: null,
         logMessages: tx.logs,
         innerInstructions: tx.innerInstructions || [],
         preBalances: [],
         postBalances: [],
         preTokenBalances: [],
         postTokenBalances: [],
         rewards: [],
         fee: 5000,
       },
     };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 326ab0a and 42a8e03.

📒 Files selected for processing (2)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (4 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
js/program-test/src/litesvm-rpc.ts (3)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (114-1120)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (2)
  • defaultTestStateTreeAccounts (236-244)
  • featureFlags (16-35)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (993-999)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (78-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Forester e2e test
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: cli-v2
  • GitHub Check: lint
  • GitHub Check: stateless-js-v2
  • GitHub Check: cli-v1
  • GitHub Check: stateless-js-v1
🔇 Additional comments (3)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (3)

18-22: LGTM! Imports migrated to program-test package.

The imports correctly reference the new @lightprotocol/program-test package utilities, aligning with the PR's objective to centralize testing infrastructure.


117-117: LGTM! RPC initialization updated to LiteSVM.

The change from getTestRpc to createLiteSVMRpc correctly adopts the new program-test RPC factory, enabling in-memory deterministic testing.


137-142: LGTM! Token account creation migrated to program-test utilities.

The switch to splCreateAssociatedTokenAccount from the program-test package correctly standardizes token account creation across tests.

Comment on lines 43 to 46
const recipientBalance = await rpc.getTokenAccountBalance(recipient);
console.log('[TEST] getBalances - recipientBalance:', JSON.stringify(recipientBalance));
console.log('[TEST] getBalances - recipientBalance.value:', recipientBalance.value);
console.log('[TEST] getBalances - recipientBalance.value.amount:', recipientBalance.value?.amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements.

These debug logs are development artifacts and should be removed before merging to avoid spamming test output.

Apply this diff to remove the debug logs:

-    const recipientBalance = await rpc.getTokenAccountBalance(recipient);
-    console.log('[TEST] getBalances - recipientBalance:', JSON.stringify(recipientBalance));
-    console.log('[TEST] getBalances - recipientBalance.value:', recipientBalance.value);
-    console.log('[TEST] getBalances - recipientBalance.value.amount:', recipientBalance.value?.amount);
+    const recipientBalance = await rpc.getTokenAccountBalance(recipient);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const recipientBalance = await rpc.getTokenAccountBalance(recipient);
console.log('[TEST] getBalances - recipientBalance:', JSON.stringify(recipientBalance));
console.log('[TEST] getBalances - recipientBalance.value:', recipientBalance.value);
console.log('[TEST] getBalances - recipientBalance.value.amount:', recipientBalance.value?.amount);
const recipientBalance = await rpc.getTokenAccountBalance(recipient);
🤖 Prompt for AI Agents
In js/compressed-token/tests/program-test/decompress-delegated.test.ts around
lines 43 to 46, remove the three debug console.log statements that print
recipientBalance and its fields; simply delete those console.log lines so the
test no longer outputs development debug information and keeps test output
clean.

Comment on lines +36 to +58
console.log("[LITESVM] Constructor called, PID:", process.pid);

// Initialize TestRpc with dummy endpoints
super(
"http://127.0.0.1:8899",
lightWasm,
"http://127.0.0.1:8784",
proverEndpoint,
{ commitment: "confirmed" },
{ depth: defaultTestStateTreeAccounts().merkleTreeHeight },
);

this.storedTransactions = new Map();
this.storedRawTransactions = new Map();

// Initialize LiteSVM with configuration
console.log("[LITESVM] Creating new LiteSVM()...");
this.litesvm = new LiteSVM()
.withSysvars()
.withBuiltins()
.withDefaultPrograms()
.withPrecompiles();
console.log("[LITESVM] LiteSVM created successfully");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove excessive console.log statements from constructor.

Multiple debug logs in the constructor (lines 36, 52-53, 58) will spam output during test execution. These are development artifacts that should be removed or replaced with a proper logging framework before production use.

Consider removing these logs or using a debug-level logger:

   constructor(
     lightWasm: any,
     config?: LiteSVMConfig,
     proverEndpoint: string = "http://127.0.0.1:3001",
   ) {
-    console.log("[LITESVM] Constructor called, PID:", process.pid);

     // Initialize TestRpc with dummy endpoints
     super(
       "http://127.0.0.1:8899",
       lightWasm,
       "http://127.0.0.1:8784",
       proverEndpoint,
       { commitment: "confirmed" },
       { depth: defaultTestStateTreeAccounts().merkleTreeHeight },
     );

     this.storedTransactions = new Map();
     this.storedRawTransactions = new Map();

     // Initialize LiteSVM with configuration
-    console.log("[LITESVM] Creating new LiteSVM()...");
     this.litesvm = new LiteSVM()
       .withSysvars()
       .withBuiltins()
       .withDefaultPrograms()
       .withPrecompiles();
-    console.log("[LITESVM] LiteSVM created successfully");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("[LITESVM] Constructor called, PID:", process.pid);
// Initialize TestRpc with dummy endpoints
super(
"http://127.0.0.1:8899",
lightWasm,
"http://127.0.0.1:8784",
proverEndpoint,
{ commitment: "confirmed" },
{ depth: defaultTestStateTreeAccounts().merkleTreeHeight },
);
this.storedTransactions = new Map();
this.storedRawTransactions = new Map();
// Initialize LiteSVM with configuration
console.log("[LITESVM] Creating new LiteSVM()...");
this.litesvm = new LiteSVM()
.withSysvars()
.withBuiltins()
.withDefaultPrograms()
.withPrecompiles();
console.log("[LITESVM] LiteSVM created successfully");
constructor(
lightWasm: any,
config?: LiteSVMConfig,
proverEndpoint: string = "http://127.0.0.1:3001",
) {
// Initialize TestRpc with dummy endpoints
super(
"http://127.0.0.1:8899",
lightWasm,
"http://127.0.0.1:8784",
proverEndpoint,
{ commitment: "confirmed" },
{ depth: defaultTestStateTreeAccounts().merkleTreeHeight },
);
this.storedTransactions = new Map();
this.storedRawTransactions = new Map();
// Initialize LiteSVM with configuration
this.litesvm = new LiteSVM()
.withSysvars()
.withBuiltins()
.withDefaultPrograms()
.withPrecompiles();
}
🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 36 to 58, remove the
excessive console.log calls in the constructor (the PID log at ~36, the
"Creating new LiteSVM()" and "LiteSVM created successfully" logs at ~52-53 and
~58) to avoid spamming test output; replace them with calls to a proper logger
at debug level (e.g., this.logger.debug(...)) if a logging facility exists, or
guard them behind a debug flag/config so they only run when verbose debugging is
enabled — ensure no functional behavior changes beyond logging removal and that
any necessary startup errors remain logged at an appropriate error/warn level.

Comment on lines +116 to +133
console.log(
"[LITESVM] addProgramFromFile: light_system_program_pinocchio.so",
);
this.litesvm.addProgramFromFile(
LIGHT_SYSTEM_PROGRAM_ID,
path.join(deployPath, "light_system_program_pinocchio.so"),
);
console.log("[LITESVM] addProgramFromFile: account_compression.so");
this.litesvm.addProgramFromFile(
ACCOUNT_COMPRESSION_PROGRAM_ID,
path.join(deployPath, "account_compression.so"),
);
console.log("[LITESVM] addProgramFromFile: light_compressed_token.so");
this.litesvm.addProgramFromFile(
COMPRESSED_TOKEN_PROGRAM_ID,
path.join(deployPath, "light_compressed_token.so"),
);
console.log("[LITESVM] All programs loaded successfully");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove excessive console.log statements from loadLightPrograms.

Multiple debug logs (lines 116-118, 123, 128, 133) will spam output during test initialization. Remove these or use a debug-level logger.

🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 116 to 133, the function
loadLightPrograms contains multiple console.log calls that spam test output;
remove these console.log statements (or replace them with a debug-level logger
call if a logging framework is available) so that only necessary startup
messages remain; specifically delete the individual "[LITESVM]
addProgramFromFile: ..." and the final "[LITESVM] All programs loaded
successfully" console.log lines, or change them to use the project's debug
logger instead.

Comment on lines +145 to +197
console.log("[LITESVM] loadAccountFixtures: Starting...");
// Find repo root by looking for cli/accounts
// Works whether running from source (src/) or built (dist/cjs/)
let repoRoot = __dirname;
while (!fs.existsSync(path.join(repoRoot, "cli/accounts"))) {
const parent = path.dirname(repoRoot);
if (parent === repoRoot) {
throw new Error("Could not find cli/accounts directory");
}
repoRoot = parent;
}
const accountsPath = path.join(repoRoot, "cli/accounts");
console.log(
"[LITESVM] loadAccountFixtures: Found accounts path:",
accountsPath,
);

// Load all account JSON files from cli/accounts
const files = fs.readdirSync(accountsPath);
console.log("[LITESVM] loadAccountFixtures: Found", files.length, "files");

for (const filename of files) {
if (!filename.endsWith(".json")) continue;

console.log("[LITESVM] loadAccountFixtures: Loading", filename);
const filepath = path.join(accountsPath, filename);
const accountData = JSON.parse(fs.readFileSync(filepath, "utf-8"));
const pubkey = new PublicKey(accountData.pubkey);

// Handle rentEpoch: if it exceeds JavaScript's MAX_SAFE_INTEGER or approaches u64::MAX,
// set it to 0 to avoid overflow issues (same approach as litesvm's copyAccounts test)
let rentEpoch = accountData.account.rentEpoch || 0;
if (rentEpoch > Number.MAX_SAFE_INTEGER) {
rentEpoch = 0;
}

const account = {
lamports: accountData.account.lamports,
data: Buffer.from(accountData.account.data[0], "base64"),
owner: new PublicKey(accountData.account.owner),
executable: accountData.account.executable,
rentEpoch,
};
console.log(
"[LITESVM] setAccount:",
pubkey.toBase58(),
"lamports:",
account.lamports,
);
this.litesvm.setAccount(pubkey, account);
console.log("[LITESVM] setAccount: Success for", filename);
}
console.log("[LITESVM] loadAccountFixtures: Complete");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove excessive console.log statements from loadAccountFixtures.

Multiple debug logs throughout this method will spam output. The rentEpoch overflow handling (lines 176-179) is a good safeguard, but the excessive logging should be removed or moved to a debug-level logger.

🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 145 to 197, remove or replace
the excessive console.log statements in loadAccountFixtures so normal runs
aren't spammed; keep the rentEpoch overflow handling as-is (lines ~176-179).
Specifically, delete the multiple console.log calls that announce start, account
path discovery, file counts, per-file loading, setAccount debug messages, and
completion, or replace them with a single debug-level logger call (e.g.,
logger.debug(...)) if a logger is available; ensure functional behavior and
error handling are unchanged.

Comment on lines +323 to +327
console.log(
"[LITESVM] sendTransaction: Calling litesvm.sendTransaction()...",
);
const result = this.litesvm.sendTransaction(transaction);
console.log("[LITESVM] sendTransaction: Transaction sent");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove excessive console.log statements from sendTransaction.

These logs will spam output during test execution. Remove or use a debug-level logger.

🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 323 to 327, remove the two
console.log calls that announce sendTransaction start/completion (they spam test
output); either delete them entirely or replace them with a debug-level logger
call (e.g. debug(...) or this.logger.debug(...)) so normal test runs remain
quiet while preserving optional verbose logging when enabled.

Comment on lines +600 to +824
console.log("[LITESVM] airdrop:", pubkey.toBase58(), lamports);
this.litesvm.airdrop(pubkey, BigInt(lamports));
console.log("[LITESVM] airdrop: Success");
return "mock-airdrop-signature";
}

/**
* Get account info using LiteSVM
*/
override async getAccountInfo(
publicKey: PublicKey,
commitmentOrConfig?: any,
): Promise<any> {
console.log("[LITESVM] getAccount:", publicKey.toBase58());
const account = this.litesvm.getAccount(publicKey);
if (!account) {
console.log("[LITESVM] getAccount: Not found");
return null;
}
console.log("[LITESVM] getAccount: Found");
return {
executable: account.executable,
owner: new PublicKey(account.owner),
lamports: Number(account.lamports),
data: account.data,
rentEpoch: account.rentEpoch,
};
}

/**
* Get multiple account infos using LiteSVM
*/
override async getMultipleAccountsInfo(
publicKeys: PublicKey[],
commitmentOrConfig?: any,
): Promise<(any | null)[]> {
return publicKeys.map((publicKey) => {
const account = this.litesvm.getAccount(publicKey);
if (!account) {
return null;
}
return {
executable: account.executable,
owner: new PublicKey(account.owner),
lamports: Number(account.lamports),
data: account.data,
rentEpoch: account.rentEpoch,
};
});
}

/**
* Get balance using LiteSVM
*/
override async getBalance(publicKey: PublicKey): Promise<number> {
console.log("[LITESVM] getBalance:", publicKey.toBase58());
const balance = Number(this.litesvm.getBalance(publicKey));
console.log("[LITESVM] getBalance: Result:", balance);
return balance;
}

/**
* Get minimum balance for rent exemption
*/
override async getMinimumBalanceForRentExemption(
dataLength: number,
commitment?: any,
): Promise<number> {
console.log("[LITESVM] minimumBalanceForRentExemption:", dataLength);
const balance = Number(
this.litesvm.minimumBalanceForRentExemption(BigInt(dataLength)),
);
console.log("[LITESVM] minimumBalanceForRentExemption: Result:", balance);
return balance;
}

/**
* Simulate a transaction without executing it
*/
override async simulateTransaction(
transactionOrMessage: any,
configOrSigners?: any,
includeAccounts?: any,
): Promise<any> {
// Extract transaction from possible message wrapper
const transaction =
"message" in transactionOrMessage
? transactionOrMessage
: transactionOrMessage;

console.log("[LITESVM] simulateTransaction: Calling...");
const result = this.litesvm.simulateTransaction(transaction);
console.log("[LITESVM] simulateTransaction: Complete");

// Check if simulation failed
if ("err" in result && typeof result.err === "function") {
const error = result.err();
return {
context: { slot: 1 },
value: {
err: error,
logs: [],
accounts: null,
unitsConsumed: 0,
returnData: null,
},
};
}

const simResult = result as any;
const meta = simResult.meta();

return {
context: { slot: 1 },
value: {
err: null,
logs: meta.logs(),
accounts: null,
unitsConsumed: Number(meta.computeUnitsConsumed()),
returnData: meta.returnData()
? {
programId: new PublicKey(
meta.returnData().programId(),
).toBase58(),
data: [
Buffer.from(meta.returnData().data()).toString("base64"),
"base64",
],
}
: null,
},
};
}

/**
* Get epoch schedule
*/
override async getEpochSchedule(): Promise<any> {
console.log("[LITESVM] getEpochSchedule: Calling...");
const schedule = this.litesvm.getEpochSchedule();
console.log("[LITESVM] getEpochSchedule: Success");
return {
slotsPerEpoch: Number(schedule.slotsPerEpoch),
leaderScheduleSlotOffset: Number(schedule.leaderScheduleSlotOffset),
warmup: schedule.warmup,
firstNormalEpoch: Number(schedule.firstNormalEpoch),
firstNormalSlot: Number(schedule.firstNormalSlot),
};
}

/**
* Get latest blockhash from LiteSVM
*/
override async getRecentBlockhash(): Promise<any> {
const blockhash = this.litesvm.latestBlockhash();
return {
blockhash,
feeCalculator: {
lamportsPerSignature: 5000,
},
};
}

/**
* Get latest blockhash (modern API)
*/
override async getLatestBlockhash(commitment?: any): Promise<any> {
console.log("[LITESVM] latestBlockhash: Calling...");
const blockhash = this.litesvm.latestBlockhash();
console.log("[LITESVM] latestBlockhash:", blockhash);
return {
blockhash,
lastValidBlockHeight: 1000000,
};
}

/**
* Confirm transaction (instant for LiteSVM)
*/
override async confirmTransaction(
signature: string | any,
commitment?: any,
): Promise<any> {
return {
context: { slot: 1 },
value: { err: null },
};
}

/**
* Get signature statuses (return instant confirmation for LiteSVM)
* Since LiteSVM executes synchronously, all transactions are immediately finalized
*/
override async getSignatureStatuses(
signatures: string[],
config?: any,
): Promise<any> {
// LiteSVM executes synchronously, so all transactions are immediately finalized
const commitment = "finalized";
return {
context: { slot: 1 },
value: signatures.map((signature) => {
// Check if we have this transaction stored
const tx = this.storedTransactions.get(signature);
if (!tx) {
return null; // Transaction not found
}
const statusObj = {
slot: 1,
confirmations: null,
err: null,
confirmationStatus: commitment as any, // Return the requested commitment level
};
return statusObj;
}),
};
}

/**
* Get current slot from LiteSVM
*/
override async getSlot(commitment?: any): Promise<number> {
console.log("[LITESVM] getClock: Calling...");
const slot = Number(this.litesvm.getClock().slot);
console.log("[LITESVM] getClock: slot =", slot);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove excessive console.log statements from query methods.

Multiple query methods (requestAirdrop, getAccountInfo, getBalance, getMinimumBalanceForRentExemption, simulateTransaction, getEpochSchedule, getLatestBlockhash, getSlot) contain debug logs that will spam output. These should be removed or moved to a debug-level logger for production use.

🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 600 to 824, several query
methods (requestAirdrop, getAccountInfo, getMultipleAccountsInfo, getBalance,
getMinimumBalanceForRentExemption, simulateTransaction, getEpochSchedule,
getLatestBlockhash, getSlot) contain excessive console.log debug prints; remove
these console.log statements (or replace them with calls to a debug-level logger
if one exists) so normal operation does not spam stdout, keeping only essential
error/warning logs and returning the same values and behavior.

Comment on lines 835 to 924
console.log(
"[litesvm-rpc.ts] getTokenAccountBalance called for:",
tokenAccount.toBase58(),
);
const account = await this.getAccountInfo(tokenAccount);
if (!account) {
throw new Error(`Token account ${tokenAccount.toBase58()} not found`);
}

console.log(
"[litesvm-rpc.ts] Account data length:",
account.data.length,
"type:",
typeof account.data,
);
// Parse SPL token account data using proper layout
let accountData;
try {
accountData = AccountLayout.decode(Buffer.from(account.data));
console.log("[litesvm-rpc.ts] AccountLayout.decode succeeded");
} catch (err) {
console.error("[litesvm-rpc.ts] ERROR decoding account data:", err);
console.error("[litesvm-rpc.ts] Account data (all bytes):", account.data);
throw err;
}
console.log(
"[litesvm-rpc.ts] accountData full object:",
JSON.stringify(accountData, (key, value) =>
typeof value === 'bigint' ? value.toString() + 'n' : value
)
);
console.log(
"[litesvm-rpc.ts] Converting amount:",
"type:", typeof accountData.amount,
"value:", accountData.amount,
"isUndefined:", accountData.amount === undefined,
"isNull:", accountData.amount === null,
);
// Convert amount to bigint first (it could be BN or bigint depending on spl-token version)
let amountBigInt: bigint;
if (accountData.amount === undefined || accountData.amount === null) {
console.warn("[litesvm-rpc.ts] WARNING: accountData.amount is undefined/null, using 0 as fallback");
amountBigInt = BigInt(0);
} else if (typeof accountData.amount === "bigint") {
amountBigInt = accountData.amount;
} else {
try {
amountBigInt = BigInt((accountData.amount as any).toString());
} catch (err) {
console.error("[litesvm-rpc.ts] ERROR converting amount to BigInt:", err);
console.error("[litesvm-rpc.ts] Falling back to 0");
amountBigInt = BigInt(0);
}
}
const mintPubkey = new PublicKey(accountData.mint);

// Fetch mint account to get decimals
const mintAccount = await this.getAccountInfo(mintPubkey);
if (!mintAccount) {
throw new Error(`Mint account ${mintPubkey.toBase58()} not found`);
}

// Parse mint account using MintLayout to get decimals
const mintData = MintLayout.decode(Buffer.from(mintAccount.data));
const decimals = mintData.decimals;

const result = {
context: { slot: 1 },
value: {
amount: amountBigInt.toString(),
decimals,
uiAmount: Number(amountBigInt) / Math.pow(10, decimals),
uiAmountString: (
Number(amountBigInt) / Math.pow(10, decimals)
).toString(),
},
};
console.log(
"[litesvm-rpc.ts] getTokenAccountBalance returning:",
JSON.stringify(result),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove excessive console.log statements from getTokenAccountBalance.

This method contains extensive debug logging throughout (lines 835-876, 912-914) that will spam output. The logic correctly fetches decimals from the mint account (addressing a past review concern), but the debug logs should be removed.

Based on learnings

🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 835-915, remove the excessive
debug console.log statements introduced for troubleshooting (specifically the
logs at ~835-876 and the final return log at ~912-914). Keep only essential
diagnostics (errors/warnings used for real failure handling, e.g., console.error
on decode failure and console.warn when amount is missing) and remove or replace
the remaining console.log calls with no-ops or minimal, non-verbose logging.
Ensure the function still throws/returns as before and that only critical
error/warning logs remain.

@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from 0e1c4c8 to 4d9ca66 Compare October 10, 2025 08:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)

43-46: Remove debug logs from getBalances.

These spam test output and add no value. Keep the fetched balance only.

     const recipientBalance = await rpc.getTokenAccountBalance(recipient);
-    console.log('[TEST] getBalances - recipientBalance:', JSON.stringify(recipientBalance));
-    console.log('[TEST] getBalances - recipientBalance.value:', recipientBalance.value);
-    console.log('[TEST] getBalances - recipientBalance.value.amount:', recipientBalance.value?.amount);
js/program-test/src/litesvm-rpc.ts (2)

36-36: Strip or guard debug console.log statements.

These flood test output and slow CI. Remove or behind a debug flag/logger.

Example (constructor/sendTransaction):

-    console.log("[LITESVM] Constructor called, PID:", process.pid);
@@
-    console.log("[LITESVM] Creating new LiteSVM()...");
@@
-    console.log("[LITESVM] LiteSVM created successfully");
@@
-    console.log(
-      "[LITESVM] sendTransaction: Calling litesvm.sendTransaction()...",
-    );
@@
-    console.log("[LITESVM] sendTransaction: Transaction sent");
@@
-    console.log("signatureBytes ", signatureBytes);
@@
-    console.log("bs58 signature ", signature);

Also applies to: 52-59, 116-134, 145-198, 281-291, 323-328, 353-356, 600-603, 613-620, 655-658, 668-673, 690-693, 738-741, 767-770, 822-825, 835-838, 844-865, 867-876, 921-924


499-501: Return base58 signature strings per JSON-RPC spec.

Currently returns objects/bytes; should be array of base58 strings. You already have sigString—use it.

-        signatures:
-          "signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
+        signatures: [sigString],
🧹 Nitpick comments (6)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)

81-86: Remove debug logs from assertDecompress.

Noisy console logs slow CI and hide real failures. Prefer assertions only.

-    // Defensive type conversion: ensure amount is always a string before passing to bn()
-    console.log('[TEST] assertDecompress - initialBalances.recipient.value.amount:', typeof initialBalances.recipient.value.amount, initialBalances.recipient.value.amount);
-    console.log('[TEST] assertDecompress - finalBalances.recipient.value.amount:', typeof finalBalances.recipient.value.amount, finalBalances.recipient.value.amount);
-    console.log('[TEST] assertDecompress - amount:', amount.toString());
+    // Defensive type conversion: ensure amount is always a string before passing to bn()
@@
-    console.log('[TEST] assertDecompress - expectedRecipientBalance:', expectedRecipientBalance.toString());
-    console.log('[TEST] assertDecompress - actualRecipientBalance:', actualRecipientBalance.toString());

Also applies to: 92-94

js/compressed-token/tests/program-test/compress.test.ts (4)

62-69: Remove debug logs in assertCompress.

Keep assertions; drop verbose logs.

-    const afterAmountStr = String(refSenderAtaBalanceAfter.value.amount);
-    console.log('[TEST] assertCompress - refSenderAtaBalanceAfter.value.amount:', typeof refSenderAtaBalanceAfter.value.amount, refSenderAtaBalanceAfter.value.amount);
-    console.log('[TEST] assertCompress - afterAmountStr:', typeof afterAmountStr, afterAmountStr);
-    console.log('[TEST] assertCompress - refSenderAtaBalanceBefore:', refSenderAtaBalanceBefore.toString());
-    console.log('[TEST] assertCompress - totalAmount:', totalAmount.toString());
-    console.log('[TEST] assertCompress - expected:', refSenderAtaBalanceBefore.sub(totalAmount).toString());
+    const afterAmountStr = String(refSenderAtaBalanceAfter.value.amount);

135-145: Remove setup-time debug logs in beforeAll.

These are dev artifacts and clutter CI output.

-        console.log('post mint');
         stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
-        console.log('post stateTreeInfo');
         tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint));

-        console.log('post tokenPoolInfo');
         bob = await newAccountWithLamports(rpc, 1e9);
-        console.log('post bob');
         charlie = await newAccountWithLamports(rpc, 1e9);
-        console.log('post charlie');
@@
-        console.log('post bobAta');
@@
-        console.log('post mintTo');
         await decompress(rpc, payer, mint, bn(900), bob, bobAta);
@@
-        console.log('post decompress');
@@
-        console.log('post lut');

Also applies to: 153-153, 165-165, 168-168, 184-184


242-251: Redundant loop around assertCompress; call once.

You run the same assertion 15 times. Remove the loop and assert once.

-        for (let i = 0; i < recipients.length; i++) {
-            await assertCompress(
-                rpc,
-                bn(String(senderAtaBalanceBefore.value.amount)),
-                bobAta,
-                mint,
-                amounts.slice(0, 11),
-                recipients.slice(0, 11),
-                recipientCompressedTokenBalancesBefore.map(x => x.items),
-            );
-        }
+        await assertCompress(
+            rpc,
+            bn(String(senderAtaBalanceBefore.value.amount)),
+            bobAta,
+            mint,
+            amounts.slice(0, 11),
+            recipients.slice(0, 11),
+            recipientCompressedTokenBalancesBefore.map(x => x.items),
+        );

262-265: Remove debug logs in batch compress assertion.

-        console.log('[TEST] compress.test - beforeAmount:', typeof beforeAmount, beforeAmount);
-        console.log('[TEST] compress.test - afterAmount:', typeof afterAmount, afterAmount);
-        console.log('[TEST] compress.test - totalCompressed:', totalCompressed.toString());
js/program-test/src/litesvm-rpc.ts (1)

399-415: Filter getSignaturesForAddress by address (or document limitation).

Currently returns all stored txs, ignoring the address parameter. If feasible, filter by address or note the limitation in code comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42a8e03 and 1ea9347.

📒 Files selected for processing (3)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (5 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
js/program-test/src/litesvm-rpc.ts (4)
js/program-test/src/index.ts (3)
  • LiteSVMRpc (8-8)
  • LiteSVMConfig (14-14)
  • createLiteSVMRpc (8-8)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (114-1120)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (2)
  • defaultTestStateTreeAccounts (236-244)
  • featureFlags (16-35)
js/compressed-token/tests/program-test/compress.test.ts (8)
js/stateless.js/src/rpc-interface.ts (1)
  • ParsedTokenAccount (236-239)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (78-115)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/compressed-token/tests/program-test/decompress-delegated.test.ts (3)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (78-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Forester e2e test
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v1
  • GitHub Check: lint
🔇 Additional comments (1)
js/program-test/src/litesvm-rpc.ts (1)

203-225: LGTM: Proper handling of legacy vs versioned raw transactions.

The 0x80 mask detection and VersionedTransaction.deserialize path look correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42a8e03 and 4d9ca66.

📒 Files selected for processing (5)
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (10 hunks)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (5 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/stateless.js/src/state/bn.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts
  • js/compressed-token/tests/program-test/compress.test.ts
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts
  • js/program-test/src/litesvm-rpc.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: lint
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v1
  • GitHub Check: cli-v2
  • GitHub Check: stateless-js-v2
  • GitHub Check: Forester e2e test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
js/compressed-token/tests/e2e/merge-token-accounts.test.ts (1)

60-60: Remove .only before merging.

The .only modifier causes only this test to run, skipping the second test in the suite. This debug artifact should be removed to ensure full test coverage.

Apply this diff:

-    it.only('should merge all token accounts', async () => {
+    it('should merge all token accounts', async () => {
js/compressed-token/tests/e2e/rpc-token-interop.test.ts (1)

255-346: Add test-rpc parity and annotate missing stubs

  • Mirror the RPC assertions in the multi-mint getCompressedTokenAccountsByOwner test by calling testRpc.getCompressedTokenAccountsByOwner and comparing its output to rpc.
  • Prefix the two getCompressedMintTokenHolders tests with [test-rpc missing] since testRpc.getCompressedMintTokenHolders currently throws “not implemented.”
🧹 Nitpick comments (6)
js/program-test/vitest.config.ts (2)

8-16: Thread pool configuration is appropriate for native addon stability.

The thread-based pool and single-thread execution are well-justified for avoiding litesvm native addon issues. Be aware this will serialize all tests, potentially increasing execution time.

Minor note: fileParallelism: false and singleThread: true achieve similar goals. The singleThread: true option alone should ensure sequential execution. Consider removing fileParallelism: false to reduce redundancy.

Apply this diff to remove the redundant setting:

     pool: "threads",
-    // Run all tests sequentially (no parallel test files)
-    fileParallelism: false,
     poolOptions: {
       threads: {
         // Run all tests sequentially in a single thread
         singleThread: true,
       },
     },

4-19: Consider adding explicit test environment and timeout configuration.

For integration tests using native components like litesvm, explicit configuration can improve clarity and prevent timeouts:

  1. Test environment: Explicitly specify environment: 'node' to document the intended runtime
  2. Timeout: Add testTimeout (e.g., 30000ms) for potentially slower integration tests

Apply this diff to add recommended configuration:

   test: {
+    environment: 'node',
+    testTimeout: 30000, // 30s for integration tests
     // litesvm fails with bad alloc if not configured
     // Use threads pool instead of forks to avoid native addon corruption
js/compressed-token/tests/e2e/merge-token-accounts.test.ts (1)

84-113: Consider removing debug console.log statements.

Lines 87, 93, 102, 104, and 111 contain console.log/console.error statements that appear to be debug artifacts. Consider removing them or replacing with proper test assertions or structured logging if the debug information is needed.

.github/workflows/js.yml (1)

103-106: Add retry logic for consistency.

The existing stateless.js and compressed-token jobs retry once to absorb intermittent flakes; consider wrapping the new program-test test command in the same retry loop to keep CI stability consistent.

js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (2)

256-256: Use bn-based comparison for consistency.

This direct comparison is inconsistent with other balance assertions in the test that use bn() for numerical comparisons. While it may work, maintaining a consistent pattern improves readability and reduces potential type coercion issues.

Apply this diff:

-        expect(balanceAfter.value.amount).toBe(balanceBefore.value.amount);
+        expect(bn(String(balanceAfter.value.amount)).eq(bn(String(balanceBefore.value.amount)))).toBe(true);

115-115: Defensive String() conversions are safe but potentially redundant.

The String() conversions ensure type safety before passing to bn(), which is a reasonable defensive practice. However, value.amount from getTokenAccountBalance likely already returns a string or number that bn() handles directly (as per the bn function signature accepting string | number | BN | Buffer | Uint8Array | number[]).

The current approach is safe and the explanatory comments make the intent clear, so this is acceptable as-is.

Also applies to: 130-130, 198-198, 214-214, 245-245, 382-382, 402-402

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd3d9a and 6376344.

📒 Files selected for processing (7)
  • .github/workflows/js-v2.yml (3 hunks)
  • .github/workflows/js.yml (3 hunks)
  • js/compressed-token/tests/e2e/merge-token-accounts.test.ts (1 hunks)
  • js/compressed-token/tests/e2e/rpc-token-interop.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (15 hunks)
  • js/compressed-token/vitest.config.ts (1 hunks)
  • js/program-test/vitest.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • js/compressed-token/vitest.config.ts
  • .github/workflows/js-v2.yml
🧰 Additional context used
🧬 Code graph analysis (1)
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (3)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/spl-token-utils.ts (2)
  • splCreateAssociatedTokenAccount (78-115)
  • splMintTo (120-149)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: cli-v1
  • GitHub Check: lint
  • GitHub Check: stateless-js-v2
  • GitHub Check: Forester e2e test
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: cli-v2
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: stateless-js-v1
🔇 Additional comments (7)
js/program-test/vitest.config.ts (1)

1-2: LGTM!

Correct usage of defineConfig from vitest/config for Vitest 3.x.

js/compressed-token/tests/e2e/merge-token-accounts.test.ts (2)

12-12: LGTM! Import migration aligns with PR objectives.

The import of getTestRpc from @lightprotocol/program-test correctly reflects the migration of test utilities to the new program-test package.


83-84: TODO acknowledged for edge case coverage.

The TODO comment notes that additional coverage is needed for the edge case of merging when only one account exists. The test below provides basic coverage, but additional scenarios may be beneficial when prioritized.

.github/workflows/js.yml (1)

24-24: Confirm warp runner availability.

Please double-check that this repository/organization is authorized for the warp-ubuntu-latest-x64-4x GitHub-hosted runner; otherwise this job will fail immediately at queue time.

js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (1)

16-23: LGTM! Clean migration to program-test utilities.

The migration from legacy test helpers to @lightprotocol/program-test is well-executed:

  • createLiteSVMRpc properly replaces the old RPC setup
  • splCreateAssociatedTokenAccount and splMintTo are correctly used with appropriate signatures
  • Import consolidation improves maintainability

Also applies to: 44-44, 64-69

js/compressed-token/tests/e2e/rpc-token-interop.test.ts (2)

214-244: Tests appropriately document test-rpc feature gaps.

The test names clearly indicate that getSignaturesForTokenOwner (line 214) and getTransactionWithCompressionInfo (line 227) are not yet implemented in test-rpc. This is an acceptable approach for tracking pending test-rpc feature parity.


19-19: Verified getTestRpc import from program-test

getTestRpc is exported in js/program-test/src/test-rpc/test-rpc.ts and used consistently across tests; the import change is correct.

Comment on lines +393 to +398
console.log('totalCompressedAmount ', totalCompressedAmount);
console.log('ataBalanceBefore', ataBalanceBefore);
console.log(
'ataBalanceBefore.value.amount ',
ataBalanceBefore.value.amount,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements.

These debug logging statements should be removed before merging to keep test output clean and professional.

Apply this diff:

-        console.log('totalCompressedAmount ', totalCompressedAmount);
-        console.log('ataBalanceBefore', ataBalanceBefore);
-        console.log(
-            'ataBalanceBefore.value.amount ',
-            ataBalanceBefore.value.amount,
-        );
-        // Defensive type conversion: ensure amount is always a string before passing to bn()
+        // Defensive type conversion: ensure amount is always a string before passing to bn()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('totalCompressedAmount ', totalCompressedAmount);
console.log('ataBalanceBefore', ataBalanceBefore);
console.log(
'ataBalanceBefore.value.amount ',
ataBalanceBefore.value.amount,
);
// Defensive type conversion: ensure amount is always a string before passing to bn()
🤖 Prompt for AI Agents
In js/compressed-token/tests/program-test/compress-spl-token-account.test.ts
around lines 393 to 398, there are debug console.log statements that should be
removed; delete the three console.log calls (totalCompressedAmount,
ataBalanceBefore, ataBalanceBefore.value.amount) so the test output stays clean
and ensure no other debugging logs remain in this test file.

@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from 6376344 to 3b7c4c6 Compare October 10, 2025 20:33
@ananas-block ananas-block force-pushed the jorrit/chore-ts-tests-litesvm branch from a35d8a5 to 82f0ed4 Compare October 10, 2025 20:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
js/stateless.js/package.json (1)

73-85: Replace eslint-plugin-vitest with @vitest/eslint-plugin
eslint-plugin-vitest v0.5.4 is unmaintained and breaks with Vitest v2.x. In package.json, remove it and install "@vitest/eslint-plugin" (v1.x+), then update your ESLint config to use the new plugin.

sdk-tests/sdk-anchor-test/tests/test_v2.ts (1)

70-88: Pass the correct address queue (signature + call + proof input)

The V2 validity proof request sets queue to addressTree; pass the actual address queue and include it in the function signature.

@@
-  const addressTreeInfo = await rpc.getAddressTreeInfoV2();
-  const addressTree = addressTreeInfo.tree;
+  const addressTreeInfo = await rpc.getAddressTreeInfoV2();
+  const addressTree = addressTreeInfo.tree;
@@
-    await createCompressedAccount(
+    await createCompressedAccount(
       rpc,
       addressTree,
+      addressTreeInfo.queue,
       address,
       program,
       outputQueue,
       signer,
       name
     );
@@
-async function createCompressedAccount(
+async function createCompressedAccount(
   rpc: Rpc,
   addressTree: web3.PublicKey,
+  addressQueue: web3.PublicKey,
   address: web3.PublicKey,
   program: Program<SdkAnchorTest>,
   outputMerkleTree: web3.PublicKey,
   signer: web3.Keypair,
   name: string
 ) {
@@
-      {
-        tree: addressTree,
-        queue: addressTree,
-        address: bn(address.toBytes()),
-      },
+      {
+        tree: addressTree,
+        queue: addressQueue,
+        address: bn(address.toBytes()),
+      },

Also applies to: 141-149, 150-159

♻️ Duplicate comments (20)
js/program-test/src/test-rpc/get-compressed-token-accounts.ts (2)

57-79: Past concern remains: Non-token outputs cause hard failures.

As noted in the previous review, parseTokenLayoutWithIdl throws when the account owner doesn't match the expected program ID (lines 67-70). If events contain non-token accounts, this will cause the parser to fail.

Consider returning null instead of throwing to make parsing resilient to mixed account types:

 export function parseTokenLayoutWithIdl(
   compressedAccount: CompressedAccountLegacy,
   programId: PublicKey = COMPRESSED_TOKEN_PROGRAM_ID,
 ): TokenData | null {
   if (compressedAccount.data === null) return null;

   const { data } = compressedAccount.data;

   if (data.length === 0) return null;

   if (compressedAccount.owner.toBase58() !== programId.toBase58()) {
-    throw new Error(
-      `Invalid owner ${compressedAccount.owner.toBase58()} for token layout`,
-    );
+    return null;
   }
   try {
     const decoded = TokenDataLayout.decode(Buffer.from(data));
     return decoded;
   } catch (error) {
     console.error("Decoding error:", error);
     throw error;
   }
 }

85-140: Past concern remains: Event parsing throws on non-token accounts.

As noted in the previous review, parseEventWithTokenTlvData unconditionally attempts to decode every output account, throwing on missing data (line 118) or invalid token data (line 122). This will fail if events contain non-token accounts.

Consider filtering non-token or data-less accounts during event parsing:

   const outputCompressedAccountsWithParsedTokenData: ParsedTokenAccount[] =
-    event.outputCompressedAccounts.map((compressedAccount, i) => {
+    event.outputCompressedAccounts
+      .map((compressedAccount, i) => {
       const maybeTree =
         pubkeyArray[event.outputCompressedAccounts[i].merkleTreeIndex];

       const treeInfo = getStateTreeInfoByPubkey(infos, maybeTree);

       if (
         !treeInfo.tree.equals(
           pubkeyArray[event.outputCompressedAccounts[i].merkleTreeIndex],
         ) &&
         (featureFlags.isV2()
           ? !treeInfo.queue.equals(
               pubkeyArray[event.outputCompressedAccounts[i].merkleTreeIndex],
             )
           : true)
       ) {
         throw new Error("Invalid tree");
       }
       const merkleContext: MerkleContext = {
         treeInfo,
         hash: bn(outputHashes[i]),
         leafIndex: event.outputLeafIndices[i],
         // V2 trees are always proveByIndex in test-rpc.
         proveByIndex: treeInfo.treeType === TreeType.StateV2,
       };
-      if (!compressedAccount.compressedAccount.data) throw new Error("No data");
+      if (!compressedAccount.compressedAccount.data) return null;
       const parsedData = parseTokenLayoutWithIdl(
         compressedAccount.compressedAccount,
       );
-      if (!parsedData) throw new Error("Invalid token data");
+      if (!parsedData) return null;
       const withMerkleContext = createCompressedAccountWithMerkleContextLegacy(
         merkleContext,
         compressedAccount.compressedAccount.owner,
         compressedAccount.compressedAccount.lamports,
         compressedAccount.compressedAccount.data,
         compressedAccount.compressedAccount.address ?? undefined,
       );
       return {
         compressedAccount: withMerkleContext,
         parsed: parsedData,
       };
-    });
+    })
+      .filter((x): x is ParsedTokenAccount => x !== null);
js/program-test/src/merkle-tree/indexed-array.ts (1)

231-246: highestElementIndex never updates due to post-mutation check

You check lowElement.nextIndex after newElementWithLowElementIndex mutates it, so the condition is never true. Capture the pre-call state and use it after insertion.

Apply this diff:

   public appendWithLowElementIndex(
     lowElementIndex: number,
     value: BN,
   ): IndexedElementBundle {
     const lowElement = this.elements[lowElementIndex];
+    const wasLowElementHighest = lowElement.nextIndex === 0;

-    if (lowElement.nextIndex === 0) {
+    if (wasLowElementHighest) {
       if (value.lte(lowElement.value)) {
         throw new Error(
           "New element value must be greater than the low element value.",
         );
       }
     } else {
       const nextElement = this.elements[lowElement.nextIndex];

       if (value.lte(lowElement.value)) {
         throw new Error(
           "New element value must be greater than the low element value.",
         );
       }

       if (value.gte(nextElement.value)) {
         throw new Error(
           "New element value must be less than the next element value.",
         );
       }
     }

     const newElementBundle = this.newElementWithLowElementIndex(
       lowElementIndex,
       value,
     );

-    if (lowElement.nextIndex === 0) {
+    if (wasLowElementHighest) {
       this.highestElementIndex = newElementBundle.newElement.index;
     }
js/program-test/src/test-rpc/get-parsed-events.ts (2)

95-111: Null-guard txRaw/meta and drop non-null assertions

txRaw can be null; accessing txRaw!.meta!.innerInstructions is unsafe. Guard and use the guarded variable.

-    if (
-      txRaw!.meta!.innerInstructions &&
-      txRaw!.meta!.innerInstructions.length > 0
-    ) {
-      for (const innerGroup of txRaw!.meta!.innerInstructions) {
+    if (!txRaw || !txRaw.meta) {
+      continue;
+    }
+    if (txRaw.meta.innerInstructions && txRaw.meta.innerInstructions.length > 0) {
+      for (const innerGroup of txRaw.meta.innerInstructions) {

257-262: Guard empty accountKeys before indexing last group

If no inner instructions produced groups, accountKeys[accountKeys.length - 1] is undefined.

-  if (invokeData) {
-    return convertToPublicTransactionEvent(
-      appendInputsData,
-      accountKeys[accountKeys.length - 1],
-      invokeData,
-    );
+  if (invokeData) {
+    if (!accountKeys || accountKeys.length === 0) {
+      return null;
+    }
+    return convertToPublicTransactionEvent(
+      appendInputsData,
+      accountKeys[accountKeys.length - 1],
+      invokeData,
+    );
js/program-test/src/litesvm-rpc.ts (2)

36-59: Trim verbose console logging or guard behind a debug flag

Constructor, program loading, fixtures, sendTransaction, and query methods log extensively and will spam CI.

  • Remove console.log or guard with an env/config (e.g., if (process.env.LITESVM_DEBUG) ...).
  • Keep only error/warn logs for failures.

Also applies to: 116-134, 145-198, 323-328, 353-356, 600-605, 613-627, 655-659, 668-674, 690-693, 738-741, 767-770, 822-825, 921-924


488-501: JSON-RPC contract: signatures must be base58 strings

You’re returning raw signature objects/arrays from Transaction/VersionedTransaction. getTransaction.transaction.signatures must be string[].

       transaction: {
         message: {
@@
-        signatures:
-          "signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
+        // Return base58 strings per JSON-RPC
+        signatures: [sigString],
       },
js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)

43-46: Remove debug console logs from tests

These logs are noisy and unnecessary in CI.

-    const recipientBalance = await rpc.getTokenAccountBalance(recipient);
-    console.log('[TEST] getBalances - recipientBalance:', JSON.stringify(recipientBalance));
-    console.log('[TEST] getBalances - recipientBalance.value:', recipientBalance.value);
-    console.log('[TEST] getBalances - recipientBalance.value.amount:', recipientBalance.value?.amount);
+    const recipientBalance = await rpc.getTokenAccountBalance(recipient);
@@
-    // Defensive type conversion: ensure amount is always a string before passing to bn()
-    console.log('[TEST] assertDecompress - initialBalances.recipient.value.amount:', typeof initialBalances.recipient.value.amount, initialBalances.recipient.value.amount);
-    console.log('[TEST] assertDecompress - finalBalances.recipient.value.amount:', typeof finalBalances.recipient.value.amount, finalBalances.recipient.value.amount);
-    console.log('[TEST] assertDecompress - amount:', amount.toString());
+    // Defensive type conversion: ensure amount is always a string before passing to bn()
@@
-    console.log('[TEST] assertDecompress - expectedRecipientBalance:', expectedRecipientBalance.toString());
-    console.log('[TEST] assertDecompress - actualRecipientBalance:', actualRecipientBalance.toString());

Also applies to: 83-86, 92-94

js/program-test/tests/rpc-multi-trees.test.ts (1)

34-38: Use the LiteSVM RPC (drop external createRpc)

Replace createRpc() with the in-process LiteSVM RPC to make this spec self-contained in CI. This was flagged previously.

-import {
-  Rpc,
-  createRpc,
+import {
+  Rpc,
   LightSystemProgram,
   TreeInfo,
   bn,
   compress,
   createAccount,
   createAccountWithLamports,
   deriveAddress,
   deriveAddressSeed,
   featureFlags,
   selectStateTreeInfo,
   transfer,
-  newAccountWithLamports,
 } from "@lightprotocol/stateless.js";
-import { getTestRpc, TestRpc } from "../src";
+import { getTestRpc, TestRpc, createLiteSVMRpc, newAccountWithLamports } from "../src";
@@
-    const lightWasm = await NobleHasherFactory.getInstance();
-    rpc = createRpc();
+    const lightWasm = await NobleHasherFactory.getInstance();
+    rpc = await createLiteSVMRpc(lightWasm);
js/program-test/tests/rpc-interop.test.ts (2)

3-24: Import program-test utilities here (newAccountWithLamports, sleep) and remove from stateless

Align with program-test API and correct function arity.

 import {
   Rpc,
-  createRpc,
   LightSystemProgram,
   TreeInfo,
   bn,
   compress,
   createAccount,
   createAccountWithLamports,
   deriveAddress,
   deriveAddressSeed,
   featureFlags,
-  getDefaultAddressTreeInfo,
-  selectStateTreeInfo,
-  sleep,
-  transfer,
-  newAccountWithLamports,
+  getDefaultAddressTreeInfo,
+  selectStateTreeInfo,
+  transfer,
 } from "@lightprotocol/stateless.js";
-import { getTestRpc, TestRpc } from "../src";
+import { getTestRpc, TestRpc, createLiteSVMRpc, newAccountWithLamports, sleep } from "../src";
 import { NobleHasherFactory } from "../src";

54-62: Use in-process LiteSVM RPC and fix newAccountWithLamports arity

Avoid external endpoints and update helper calls.

-    const lightWasm = await NobleHasherFactory.getInstance();
-    rpc = createRpc();
-
-    testRpc = await getTestRpc(lightWasm);
+    const lightWasm = await NobleHasherFactory.getInstance();
+    rpc = await createLiteSVMRpc(lightWasm);
+    testRpc = await getTestRpc(lightWasm);
@@
-    payer = await newAccountWithLamports(rpc, 10e9, 256);
-    bob = await newAccountWithLamports(rpc, 10e9, 256);
+    payer = await newAccountWithLamports(rpc, 10e9);
+    bob = await newAccountWithLamports(rpc, 10e9);
sdk-tests/sdk-anchor-test/tests/test_v2.ts (1)

14-22: Import missing types/enums (SdkAnchorTest, TreeType)

Program is used and treeType comparisons should use the enum. Add imports.

 import {
   bn,
   type CompressedAccountWithMerkleContext,
-  defaultTestStateTreeAccounts,
   deriveAddressSeedV2,
   deriveAddressV2,
   PackedAccounts,
   type Rpc,
   SystemAccountMetaConfig,
+  TreeType,
 } from "@lightprotocol/stateless.js";
+import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";
js/program-test/src/test-rpc/test-rpc.ts (4)

552-566: Explicitly validate V2 proofs or document intentional skip

Currently only V1 proofs are validated. Either add a V2 branch to assert expected defaults (proveByIndex, pathElements/indices) or comment why it's intentionally skipped.

Example:

if (proof.treeInfo.treeType === TreeType.StateV2) {
  if (!proof.proveByIndex) throw new Error("V2 proofs must be proveByIndex");
  // Optionally assert zeros length and leafIndex bounds
}

274-291: Remove debug console.log noise from getMultipleCompressedAccountProofs

These logs are very verbose and clutter CI. Remove them or guard by a debug flag.

-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: INPUT - hashes:", ...);
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Calling getParsedEvents...");
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Got", events.length, "events");
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Processing", leavesByTree.size, "trees");
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Processing tree:", treeKey, ...);
-    console.log("[TEST-RPC] treeInfo.treeType value:", ...);
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Creating V1 MerkleTree with depth", this.depth);
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: All leaves:", ...);
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Leaf strings:", ...);
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Calling new MerkleTree...");
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: MerkleTree created successfully");
-    console.log("[TEST-RPC] Starting hash matching loop, hashes.length:", ...);
-    console.log("[TEST-RPC] Processing hash[${i}]...", ...);
-    console.log("[TEST-RPC] Finding leafIndex in", leaves.length, "leaves");
-    console.log("[TEST-RPC] leaves[0] sample:", ...);
-    console.log("[TEST-RPC] Found leafIndex:", leafIndex);
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: Returning proofs for", hashes.length, "hashes");
-    console.log("[TEST-RPC] getMultipleCompressedAccountProofs: OUTPUT - Success, returning", results.length, "proofs");
+    // Consider: if (this.log) console.debug("...") for selective debugging

Also applies to: 340-366, 367-411, 416-437, 443-471, 584-602


671-686: Remove debug logs in token balance helpers

Delete console.log lines during amount conversion.

-    console.log(
-      "[test-rpc.ts:524] Converting amount:",
-      typeof rawAmount,
-      rawAmount,
-    );
...
-        console.log(
-          "[test-rpc.ts:543] Converting amount:",
-          typeof rawAmount,
-          rawAmount,
-        );
...
-          console.log(
-            "[test-rpc.ts:567] Converting amount:",
-            typeof rawAmount,
-            rawAmount,
-          );

Also applies to: 703-718, 736-757


376-408: Build V1 MerkleTree with on-chain indices (sparse), not dense array order

Using leaves.map(...) drops actual on-chain indices, producing invalid roots/proofs when indices are sparse. Materialize level-0 by true leafIndices.

-        // Detailed logging for each leaf
-        const leafStrings = leaves.map((leaf, idx) => {
-          try {
-            const leafBn = bn(leaf);
-            const leafStr = leafBn.toString();
-            console.log(`[TEST-RPC] Leaf[${idx}]:`, {
-              raw: JSON.stringify(leaf).slice(0, 100),
-              bn: leafBn.toString(16).slice(0, 32) + "...",
-              decimal: leafStr,
-              length: leafStr.length,
-              valid: /^[0-9]+$/.test(leafStr),
-            });
-            return leafStr;
-          } catch (err) {
-            console.log(
-              `[TEST-RPC] ERROR converting leaf[${idx}]:`,
-              err,
-              "raw:",
-              JSON.stringify(leaf).slice(0, 100),
-            );
-            throw err;
-          }
-        });
-        console.log(
-          "[TEST-RPC] getMultipleCompressedAccountProofs: Leaf strings:",
-          JSON.stringify(leafStrings),
-        );
-        console.log(
-          "[TEST-RPC] getMultipleCompressedAccountProofs: Calling new MerkleTree...",
-        );
-        merkleTree = new MerkleTree(this.depth, this.lightWasm, leafStrings);
+        // Materialize sparse leaves at their real on-chain indices
+        const treeData = leavesByTree.get(treeKey)!;
+        const maxLeafIndex = Math.max(...treeData.leafIndices);
+        const zeroLeaf = bn(0).toString();
+        const leafStrings = Array.from({ length: maxLeafIndex + 1 }, () => zeroLeaf);
+        for (let i = 0; i < leaves.length; i++) {
+          const onChainIdx = treeData.leafIndices[i];
+          leafStrings[onChainIdx] = bn(leaves[i]).toString();
+        }
+        merkleTree = new MerkleTree(this.depth, this.lightWasm, leafStrings);
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (1)

393-400: Remove leftover debug logs

Delete the console.log statements to keep test output clean.

-        console.log('totalCompressedAmount ', totalCompressedAmount);
-        console.log('ataBalanceBefore', ataBalanceBefore);
-        console.log(
-            'ataBalanceBefore.value.amount ',
-            ataBalanceBefore.value.amount,
-        );
-        // Defensive type conversion: ensure amount is always a string before passing to bn()
+        // Defensive type conversion: ensure amount is always a string before passing to bn()
js/program-test/src/merkle-tree/merkle-tree.ts (1)

204-213: Critical: deserialize doesn’t initialize _lightWasm (methods will throw)

Deserialized instances lack _lightWasm, so insert/bulkInsert/update/path crash. Accept a LightWasm and set it.

-  static deserialize(
-    data: any,
-    hashFunction: (left: string, right: string) => string,
-  ) {
-    const instance = Object.assign(Object.create(this.prototype), data);
-    instance._hash = hashFunction;
-    instance.capacity = 2 ** instance.levels;
-    instance.zeroElement = instance._zeros[0];
-    return instance;
-  }
+  static deserialize(data: any, lightWasm: LightWasm) {
+    const instance = Object.assign(Object.create(this.prototype), data);
+    instance._lightWasm = lightWasm;
+    instance.capacity = 2 ** instance.levels;
+    instance.zeroElement = instance._zeros[0];
+    return instance;
+  }
js/program-test/src/spl-token-utils.ts (2)

201-205: Remove leftover debug logging.

This console.log statement will spam test output and should have been removed per previous review feedback. Production test utilities should not include debug logging.

Apply this diff:

   const data = AccountLayout.decode(accountInfo.data);
-  console.log(
-    "[spl-token-utils.ts:195] Converting amount:",
-    typeof data.amount,
-    data.amount,
-  );
   const amount =
     typeof data.amount === "bigint"
       ? data.amount
       : BigInt((data.amount as any).toString());

233-237: Remove leftover debug logging.

This console.log statement will spam test output and should have been removed per previous review feedback.

Apply this diff:

   const data = MintLayout.decode(accountInfo.data);
-  console.log(
-    "[spl-token-utils.ts:223] Converting supply:",
-    typeof data.supply,
-    data.supply,
-  );
-
   const supply =
     typeof data.supply === "bigint"
       ? data.supply
       : BigInt((data.supply as any).toString());
🧹 Nitpick comments (21)
js/program-test/vitest.config.ts (1)

3-19: Configuration is appropriate for litesvm constraints.

The sequential execution setup (threads pool + no file parallelism + single thread) correctly addresses litesvm's concurrency limitations documented in the comments. The triple-layered approach ensures safety with the native addon.

Optionally consider adding timeout configurations for integration tests:

 export default defineConfig({
   test: {
+    testTimeout: 30000,
+    hookTimeout: 30000,
     // litesvm fails with bad alloc if not configured
     // Use threads pool instead of forks to avoid native addon corruption

This would help prevent timeouts on slower integration tests with litesvm, though the current defaults may be sufficient.

js/compressed-token/tests/program-test/delegate.test.ts (1)

11-15: LGTM! Migration to program-test package is correct.

The imports for createLiteSVMRpc, newAccountWithLamports, and NobleHasherFactory from @lightprotocol/program-test are correct and align with the new test infrastructure.

Consider consolidating the imports from @lightprotocol/program-test into a single import statement for consistency:

-import {
-    createLiteSVMRpc,
-    newAccountWithLamports,
-} from '@lightprotocol/program-test';
-import { NobleHasherFactory } from '@lightprotocol/program-test';
+import {
+    createLiteSVMRpc,
+    newAccountWithLamports,
+    NobleHasherFactory,
+} from '@lightprotocol/program-test';
js/stateless.js/src/utils/send-and-confirm.ts (1)

87-125: Add exception handling for RPC errors.

If rpc.getSignatureStatuses([txId]) throws an exception (network error, rate limiting, etc.), the interval continues running and the promise remains pending until the 80-second timeout, masking the actual error with a generic timeout message.

Wrap the RPC call in a try-catch block:

         const intervalId = setInterval(async () => {
             elapsed += interval;

             if (elapsed >= timeout) {
                 clearInterval(intervalId);
                 reject(
                     new Error(`Transaction ${txId}'s confirmation timed out`),
                 );
+                return;
             }

-            const status = await rpc.getSignatureStatuses([txId]);
-            const result = status?.value[0];
+            try {
+                const status = await rpc.getSignatureStatuses([txId]);
+                const result = status?.value[0];

-            // Check for transaction errors
-            if (result?.err) {
-                clearInterval(intervalId);
-                reject(
-                    new Error(
-                        `Transaction ${txId} failed: ${JSON.stringify(result.err)}`,
-                    ),
-                );
-                return;
-            }
+                // Check for transaction errors
+                if (result?.err) {
+                    clearInterval(intervalId);
+                    reject(
+                        new Error(
+                            `Transaction ${txId} failed: ${JSON.stringify(result.err)}`,
+                        ),
+                    );
+                    return;
+                }

-            const statusValue = result?.confirmationStatus;
-            // Check if the status meets or exceeds the requested commitment
-            // finalized > confirmed > processed
-            const meetsCommitment =
-                statusValue === commitment ||
-                (commitment === 'processed' &&
-                    (statusValue === 'confirmed' ||
-                        statusValue === 'finalized')) ||
-                (commitment === 'confirmed' && statusValue === 'finalized');
+                const statusValue = result?.confirmationStatus;
+                // Check if the status meets or exceeds the requested commitment
+                // finalized > confirmed > processed
+                const meetsCommitment =
+                    statusValue === commitment ||
+                    (commitment === 'processed' &&
+                        (statusValue === 'confirmed' ||
+                            statusValue === 'finalized')) ||
+                    (commitment === 'confirmed' && statusValue === 'finalized');

-            if (meetsCommitment) {
-                clearInterval(intervalId);
-                resolve(txId);
-            }
+                if (meetsCommitment) {
+                    clearInterval(intervalId);
+                    resolve(txId);
+                }
+            } catch (error) {
+                clearInterval(intervalId);
+                reject(
+                    new Error(
+                        `RPC error while confirming transaction ${txId}: ${error instanceof Error ? error.message : String(error)}`,
+                    ),
+                );
+            }
         }, interval);
js/stateless.js/package.json (3)

98-98: Run both E2E tests in a single Vitest process for speed

Avoid double startup by invoking both files in one run.

-        "test:e2e:all": "vitest run tests/e2e/layout.test.ts && vitest run tests/e2e/safe-conversion.test.ts",
+        "test:e2e:all": "vitest run tests/e2e/layout.test.ts tests/e2e/safe-conversion.test.ts",

38-40: Exclude compromised @solana/web3.js versions in peer range

Protect downstreams by excluding 1.95.6 and 1.95.7 from the allowed peer range.

-        "@solana/web3.js": ">=1.73.5"
+        "@solana/web3.js": ">=1.73.5 <1.95.6 || >=1.95.8"

Based on learnings


42-42: Remove duplicate dependency: @coral-xyz/borsh

Package is listed in both dependencies and devDependencies. Keep it only in dependencies.

   "devDependencies": {
-        "@coral-xyz/anchor": "^0.29.0",
-        "@coral-xyz/borsh": "^0.29.0",
+        "@coral-xyz/anchor": "^0.29.0",

Also applies to: 54-54

js/program-test/rollup.config.js (1)

25-36: Avoid duplicate declaration emission with dts bundling

If tsconfig has declaration: true, the TS plugin may emit .d.ts into dist/{fmt} while rollup-plugin-dts also writes dist/types/index.d.ts. Disable declarations for runtime builds or move dts input to the generated index.d.ts to prevent conflicts.

Apply one of:

  • Set declaration: false in the plugin options:
     typescript({
       target: fmt === "es" ? "ES2022" : "ES2017",
       outDir: `dist/${fmt}`,
       rootDir: "src",
+      declaration: false,
     }),
  • Or change the DTS input to the tsc output (if you enable declarations in tsconfig):
-const typesConfig = {
-  input: "src/index.ts",
+const typesConfig = {
+  input: "dist/types/index.d.ts",
   output: [{ file: "dist/types/index.d.ts", format: "es" }],
   plugins: [dts()],
 };
js/program-test/src/merkle-tree/indexed-array.ts (1)

24-35: Preserve error context in hash()

Catching and rethrowing "Hashing failed" hides the root cause. Include the original error message or use Error cause.

   public hash(lightWasm: LightWasm, nextValue: BN): Uint8Array {
     try {
       const hash = lightWasm.poseidonHash([
         bn(this.value.toArray("be", 32)).toString(),
         bn(this.nextIndex).toString(),
         bn(nextValue.toArray("be", 32)).toString(),
       ]);
       return hash;
     } catch (error) {
-      throw new Error("Hashing failed");
+      throw new Error(
+        `Hashing failed: ${error instanceof Error ? error.message : String(error)}`
+      );
     }
   }
js/compressed-token/package.json (1)

79-97: Ensure program-test is built before local tests

CI wires a dependsOn for program-test, but local pnpm test may fail if @lightprotocol/program-test isn’t built. Add a pretest to build it.

   "scripts": {
+    "pretest": "pnpm --filter @lightprotocol/program-test build",
     "test": "pnpm test:program-test && pnpm test:e2e:all",

Also verify eslint-plugin-vitest (^0.5.4) is compatible with your Vitest version (^2.1.1); upgrade the plugin if needed.

js/compressed-token/tests/program-test/transfer-delegated.test.ts (1)

406-412: Make failure assertion less brittle

Error messages change over time; assert on a stable substring instead of the exact string.

-        ).rejects.toThrowError(
-            'Insufficient balance for transfer. Required: 500, available: 400.',
-        );
+        ).rejects.toThrow(/Insufficient balance for transfer/i);
js/program-test/tests/merkle-tree.test.ts (1)

35-38: Minor naming nit

WasmFactory actually points to NobleHasherFactory. Consider renaming the variable to NobleHasherFactory for clarity.

js/compressed-token/tests/program-test/decompress-delegated.test.ts (1)

30-34: Type shape suggestion for BalanceInfo.recipient

getTokenAccountBalance returns an object with context/value; your interface only models value.amount. Either narrow at callsite or adjust the type to match the response to improve type safety.

js/program-test/src/litesvm-rpc.ts (1)

910-919: Avoid precision loss for uiAmount; build uiAmountString via bigint math

Using Number() can lose precision for large amounts. Keep uiAmountString exact and set uiAmount to null when unsafe.

-      value: {
-        amount: amountBigInt.toString(), // Use toString() instead of toLocaleString() to ensure consistent string format
-        decimals,
-        uiAmount: Number(amountBigInt) / Math.pow(10, decimals),
-        uiAmountString: (
-          Number(amountBigInt) / Math.pow(10, decimals)
-        ).toString(),
-      },
+      value: {
+        amount: amountBigInt.toString(),
+        decimals,
+        uiAmount: amountBigInt <= BigInt(Number.MAX_SAFE_INTEGER)
+          ? Number(amountBigInt) / Math.pow(10, decimals)
+          : null,
+        // Exact string math
+        uiAmountString: (() => {
+          const scale = BigInt(10) ** BigInt(decimals);
+          const intPart = amountBigInt / scale;
+          const fracPart = amountBigInt % scale;
+          if (decimals === 0) return intPart.toString();
+          const frac = fracPart.toString().padStart(decimals, "0");
+          return `${intPart.toString()}.${frac}`;
+        })(),
+      },
js/program-test/src/hasher/noble-hasher.ts (2)

4-4: Use type-only import for LightWasm to avoid runtime dependency/cycle

Importing the interface as a value can create an unnecessary runtime dependency and potential cycles. Switch to a type-only import.

-import { LightWasm } from "../test-rpc/test-rpc";
+import type { LightWasm } from "../test-rpc/test-rpc";

87-104: Deduplicate hashing logic across the three methods

All three methods share the same length checks, state construction, and permutation. Extract a private helper that returns bigint, and keep thin wrappers for Uint8Array/string/BN.

 export class NobleHasher implements LightWasm {
+  private hashToBigInt(input: string[] | BN[]): bigint {
+    const inputs = toBigIntArray(input);
+    if (inputs.length === 2) {
+      return poseidonNoble3([POSEIDON_CAPACITY, inputs[0], inputs[1]])[0];
+    } else if (inputs.length === 3) {
+      return poseidonNoble4([POSEIDON_CAPACITY, inputs[0], inputs[1], inputs[2]])[0];
+    }
+    throw new Error(`Expected 2 or 3 inputs, got ${inputs.length}`);
+  }
   poseidonHash(input: string[] | BN[]): Uint8Array {
-    const inputs = toBigIntArray(input);
-    let hash: bigint;
-    if (inputs.length === 2) {
-      const state = poseidonNoble3([POSEIDON_CAPACITY, inputs[0], inputs[1]]);
-      hash = state[0];
-    } else if (inputs.length === 3) {
-      const state = poseidonNoble4([POSEIDON_CAPACITY, inputs[0], inputs[1], inputs[2]]);
-      hash = state[0];
-    } else {
-      throw new Error(`Expected 2 or 3 inputs, got ${inputs.length}`);
-    }
-    return bigintToUint8Array(hash);
+    return bigintToUint8Array(this.hashToBigInt(input));
   }
   poseidonHashString(input: string[] | BN[]): string {
-    const inputs = toBigIntArray(input);
-    let hash: bigint;
-    if (inputs.length === 2) {
-      const state = poseidonNoble3([POSEIDON_CAPACITY, inputs[0], inputs[1]]);
-      hash = state[0];
-    } else if (inputs.length === 3) {
-      const state = poseidonNoble4([POSEIDON_CAPACITY, inputs[0], inputs[1], inputs[2]]);
-      hash = state[0];
-    } else {
-      throw new Error(`Expected 2 or 3 inputs, got ${inputs.length}`);
-    }
-    return hash.toString();
+    return this.hashToBigInt(input).toString();
   }
   poseidonHashBN(input: string[] | BN[]): BN {
-    const inputs = toBigIntArray(input);
-    let hash: bigint;
-    if (inputs.length === 2) {
-      const state = poseidonNoble3([POSEIDON_CAPACITY, inputs[0], inputs[1]]);
-      hash = state[0];
-    } else if (inputs.length === 3) {
-      const state = poseidonNoble4([POSEIDON_CAPACITY, inputs[0], inputs[1], inputs[2]]);
-      hash = state[0];
-    } else {
-      throw new Error(`Expected 2 or 3 inputs, got ${inputs.length}`);
-    }
-    return bn(hash.toString());
+    return bn(this.hashToBigInt(input).toString());
   }
 }

Also applies to: 111-128, 135-152

sdk-tests/sdk-anchor-test/tests/test_v2.ts (1)

61-64: Avoid magic numbers for treeType

Use the TreeType enum for clarity and safety.

-    const stateTreeInfo = existingTreeInfos.find(
-      (info) => info.treeType === 2 || info.treeType === 3
-    ); // StateV1 or StateV2
+    const stateTreeInfo = existingTreeInfos.find(
+      (info) =>
+        info.treeType === TreeType.StateV1 || info.treeType === TreeType.StateV2
+    ); // StateV1 or StateV2
js/program-test/tests/poseidon-comparison.test.ts (2)

282-355: Silence verbose console output in tests

Drop or gate logs behind an env flag to keep CI output clean.

Example:

-console.log("Light Protocol hash [0, 0]:", lightHash);
-console.log("@noble/curves hash [0, 0]:", nobleHash[0].toString());
+if (process.env.VITEST_DEBUG) {
+  console.debug("Light Protocol hash [0, 0]:", lightHash);
+  console.debug("@noble/curves hash [0, 0]:", nobleHash[0].toString());
+}

Also applies to: 382-400, 453-477, 497-499, 537-538, 575-576


32-49: Avoid duplicating Poseidon constants; import from source

Import MDS_3 and CONSTANTS_3_FLAT from js/program-test/src/hasher/constants.ts instead of re-declaring in the test to prevent drift.

-import * as mod from "@noble/curves/abstract/modular.js";
-import * as poseidon from "@noble/curves/abstract/poseidon.js";
-// ... manual MDS_3 and CONSTANTS_3_FLAT definitions ...
+import * as mod from "@noble/curves/abstract/modular.js";
+import * as poseidon from "@noble/curves/abstract/poseidon.js";
+import { MDS_3, CONSTANTS_3_FLAT } from "../src/hasher/constants";

Also applies to: 51-248

js/program-test/src/merkle-tree/merkle-tree.ts (1)

132-141: JSDoc return type mismatch

Docs mention pathIndex but code returns pathIndices and pathElements as string[]. Update the comment for accuracy.

- * @returns {{pathElements: number[], pathIndex: number[]}} An object containing adjacent elements and left-right index
+ * @returns {{ pathElements: string[]; pathIndices: number[] }} Adjacent elements and left-right indices
js/program-test/src/spl-token-utils.ts (2)

62-70: Replace as any cast with proper type conversion.

The cast to any bypasses type safety and risks runtime errors if the underlying implementation expectations change. Consider one of these approaches:

  1. If Transaction should be supported, update the Rpc interface to accept Transaction | VersionedTransaction
  2. Use a proper conversion method like VersionedTransaction.fromTransaction(transaction) if available
  3. Build the transaction as a VersionedTransaction from the start

This pattern repeats in other functions (lines 112, 148, 184, 321), so a consistent solution should be applied across all of them.


282-283: Consider removing unused parameters or documenting their purpose.

The commitment and confirmOptions parameters are typed as any and unused in the function body. If they exist for API compatibility with @solana/spl-token, document that; otherwise, remove them to reduce confusion.

If these parameters are intentionally present for future use or compatibility, add a JSDoc comment explaining:

/**
 * Get or create an associated token account
 * Replicates the behavior of getOrCreateAssociatedTokenAccount from @solana/spl-token
 * 
 * @param commitment - Reserved for future use (currently unused)
 * @param confirmOptions - Reserved for future use (currently unused)
 */

Comment on lines 64 to 68
console.log('[TEST] assertCompress - refSenderAtaBalanceAfter.value.amount:', typeof refSenderAtaBalanceAfter.value.amount, refSenderAtaBalanceAfter.value.amount);
console.log('[TEST] assertCompress - afterAmountStr:', typeof afterAmountStr, afterAmountStr);
console.log('[TEST] assertCompress - refSenderAtaBalanceBefore:', refSenderAtaBalanceBefore.toString());
console.log('[TEST] assertCompress - totalAmount:', totalAmount.toString());
console.log('[TEST] assertCompress - expected:', refSenderAtaBalanceBefore.sub(totalAmount).toString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statements.

Multiple console.log statements are present throughout the test file (setup and assertions). These should be removed before merging to production.

Apply this diff to remove debug logging:

-        console.log('post mint');
-        console.log('post stateTreeInfo');
-        console.log('post tokenPoolInfo');
-        console.log('post bob');
-        console.log('post charlie');
-        console.log('post bobAta');
-        console.log('post mintTo');
-        console.log('post decompress');
-        console.log('post lut');
-    console.log('[TEST] assertCompress - refSenderAtaBalanceAfter.value.amount:', typeof refSenderAtaBalanceAfter.value.amount, refSenderAtaBalanceAfter.value.amount);
-    console.log('[TEST] assertCompress - afterAmountStr:', typeof afterAmountStr, afterAmountStr);
-    console.log('[TEST] assertCompress - refSenderAtaBalanceBefore:', refSenderAtaBalanceBefore.toString());
-    console.log('[TEST] assertCompress - totalAmount:', totalAmount.toString());
-    console.log('[TEST] assertCompress - expected:', refSenderAtaBalanceBefore.sub(totalAmount).toString());
-        console.log('[TEST] compress.test - beforeAmount:', typeof beforeAmount, beforeAmount);
-        console.log('[TEST] compress.test - afterAmount:', typeof afterAmount, afterAmount);
-        console.log('[TEST] compress.test - totalCompressed:', totalCompressed.toString());

Also applies to: 135-184, 262-264

🤖 Prompt for AI Agents
In js/compressed-token/tests/program-test/compress.test.ts around lines 64 to 68
(and also remove similar debug logs at ranges 135-184 and 262-264), remove the
temporary console.log debug statements that print test internals; replace them
with nothing (delete the lines) or, if preserving context is necessary, use
proper test assertions or test framework logging instead; ensure no stray
console.log calls remain in those ranges before merging.

Comment on lines +242 to +252
for (let i = 0; i < recipients.length; i++) {
await assertCompress(
rpc,
bn(String(senderAtaBalanceBefore.value.amount)),
bobAta,
mint,
amounts.slice(0, 11),
recipients.slice(0, 11),
recipientCompressedTokenBalancesBefore.map(x => x.items),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove redundant loop.

Lines 242-252 loop through recipients but call assertCompress with identical parameters each iteration, making the loop redundant. Remove the loop and call assertCompress once.

Apply this diff:

-        // Defensive type conversion: ensure amount is always a string before passing to bn()
-        for (let i = 0; i < recipients.length; i++) {
-            await assertCompress(
-                rpc,
-                bn(String(senderAtaBalanceBefore.value.amount)),
-                bobAta,
-                mint,
-                amounts.slice(0, 11),
-                recipients.slice(0, 11),
-                recipientCompressedTokenBalancesBefore.map(x => x.items),
-            );
-        }
+        // Defensive type conversion: ensure amount is always a string before passing to bn()
+        await assertCompress(
+            rpc,
+            bn(String(senderAtaBalanceBefore.value.amount)),
+            bobAta,
+            mint,
+            amounts.slice(0, 11),
+            recipients.slice(0, 11),
+            recipientCompressedTokenBalancesBefore.map(x => x.items),
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (let i = 0; i < recipients.length; i++) {
await assertCompress(
rpc,
bn(String(senderAtaBalanceBefore.value.amount)),
bobAta,
mint,
amounts.slice(0, 11),
recipients.slice(0, 11),
recipientCompressedTokenBalancesBefore.map(x => x.items),
);
}
// Defensive type conversion: ensure amount is always a string before passing to bn()
await assertCompress(
rpc,
bn(String(senderAtaBalanceBefore.value.amount)),
bobAta,
mint,
amounts.slice(0, 11),
recipients.slice(0, 11),
recipientCompressedTokenBalancesBefore.map(x => x.items),
);
🤖 Prompt for AI Agents
In js/compressed-token/tests/program-test/compress.test.ts around lines 242-252,
the code contains a redundant for-loop that repeatedly calls assertCompress with
the same arguments; remove the loop and replace it with a single call to
assertCompress using the existing parameters (rpc,
bn(String(senderAtaBalanceBefore.value.amount)), bobAta, mint, amounts.slice(0,
11), recipients.slice(0, 11), recipientCompressedTokenBalancesBefore.map(x =>
x.items)).

Comment on lines +45 to +48
/// These are constant test accounts in between test runs
payer = await newAccountWithLamports(rpc, 10e9, 256);
bob = await newAccountWithLamports(rpc, 10e9, 256);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix newAccountWithLamports import and arity

Import from program-test and drop the third argument; current call signature causes TS/runtime errors.

-    payer = await newAccountWithLamports(rpc, 10e9, 256);
-    bob = await newAccountWithLamports(rpc, 10e9, 256);
+    payer = await newAccountWithLamports(rpc, 10e9);
+    bob = await newAccountWithLamports(rpc, 10e9);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// These are constant test accounts in between test runs
payer = await newAccountWithLamports(rpc, 10e9, 256);
bob = await newAccountWithLamports(rpc, 10e9, 256);
/// These are constant test accounts in between test runs
- payer = await newAccountWithLamports(rpc, 10e9, 256);
payer = await newAccountWithLamports(rpc, 10e9);
bob = await newAccountWithLamports(rpc, 10e9);
🤖 Prompt for AI Agents
In js/program-test/tests/rpc-multi-trees.test.ts around lines 45 to 48, the test
calls newAccountWithLamports with an incorrect import and a spurious third
argument which causes TypeScript/runtime errors; update the import to pull
newAccountWithLamports from the program-test module (where it is exported) and
remove the third numeric argument from both calls so they match the actual
function signature (e.g., newAccountWithLamports(rpc, 10e9)).

Comment on lines +19 to +22
import path from "path";
import os from "os";
import dotenv from "dotenv";
dotenv.config();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing type import for Program

Program is used but SdkAnchorTest isn’t imported. Add the generated Anchor type to avoid TS errors.

 import idl from "../target/idl/sdk_anchor_test.json";
+import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In sdk-tests/sdk-anchor-test/tests/test_v1.ts around lines 19 to 22, the file
uses Program<SdkAnchorTest> but never imports the generated Anchor type
SdkAnchorTest; add the missing type import from the generated IDL/types file
(for example: import type { SdkAnchorTest } from
"../target/types/sdk_anchor_test";) and adjust the relative path to the actual
generated types file so TypeScript recognizes Program<SdkAnchorTest>.

Comment on lines +39 to 42
const programPath = path.join(
__dirname,
"../../../target/deploy/sdk_anchor_test.so"
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix programPath resolution (points too high in the tree)

From tests/, the .so lives in ../target/deploy/. Adjust the path to avoid ENOENT.

-    const programPath = path.join(
-      __dirname,
-      "../../../target/deploy/sdk_anchor_test.so"
-    );
+    const programPath = path.join(
+      __dirname,
+      "../target/deploy/sdk_anchor_test.so"
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const programPath = path.join(
__dirname,
"../../../target/deploy/sdk_anchor_test.so"
);
const programPath = path.join(
__dirname,
"../target/deploy/sdk_anchor_test.so"
);
🤖 Prompt for AI Agents
In sdk-tests/sdk-anchor-test/tests/test_v1.ts around lines 39 to 42, the
programPath currently points too high in the tree and causes ENOENT; update the
path.join call to reference the correct relative location from tests/ (use
"../target/deploy/sdk_anchor_test.so" instead of
"../../../target/deploy/sdk_anchor_test.so") and ensure the filename matches the
built .so; optionally add a quick fs.existsSync check to fail fast if the file
is still missing.

Comment on lines +39 to 42
const programPath = path.join(
__dirname,
"../../../target/deploy/sdk_anchor_test.so"
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix programPath resolution (too many ../ segments)

Point to ../target/deploy from tests/.

-    const programPath = path.join(
-      __dirname,
-      "../../../target/deploy/sdk_anchor_test.so"
-    );
+    const programPath = path.join(
+      __dirname,
+      "../target/deploy/sdk_anchor_test.so"
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const programPath = path.join(
__dirname,
"../../../target/deploy/sdk_anchor_test.so"
);
const programPath = path.join(
__dirname,
"../target/deploy/sdk_anchor_test.so"
);
🤖 Prompt for AI Agents
In sdk-tests/sdk-anchor-test/tests/test_v2.ts around lines 39 to 42, the
programPath uses too many "../" segments and resolves incorrectly; update the
path to point from the tests/ directory into ../target/deploy (i.e., replace the
current join that goes up three levels with one that goes up two levels to
target/deploy/sdk_anchor_test.so) so the test loads the correct binary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk-tests/sdk-anchor-test/tests/test_v1.ts (1)

132-136: Missing SdkAnchorTest type import (compile error).

Program requires importing the generated type.

Add at the top:

import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";
♻️ Duplicate comments (10)
js/program-test/eslint.config.cjs (1)

82-89: Anchor parserOptions.project to this package.

Linting from the workspace root makes @typescript-eslint/parser resolve "./tsconfig.json" against the CWD, so it can’t find js/program-test/tsconfig.json and type-aware rules break. Add tsconfigRootDir: __dirname (or an absolute path) so the parser loads the correct config.

       parserOptions: {
         project: "./tsconfig.json",
+        tsconfigRootDir: __dirname,
         ecmaVersion: 2022,
         sourceType: "module",
       },
js/program-test/tests/rpc-interop.test.ts (1)

1-62: Fix newAccountWithLamports import & call signature
The helper moved into @lightprotocol/program-test and now takes (rpc, lamports) only. Keeping the import from @lightprotocol/stateless.js and passing a third argument (256) still produces the TS error noted earlier. Please import newAccountWithLamports (and sleep) from ../src and drop the extra argument.

-import {
-  ...
-  sleep,
-  transfer,
-  newAccountWithLamports,
-} from "@lightprotocol/stateless.js";
-import { getTestRpc, TestRpc } from "../src";
+import {
+  ...
+  transfer,
+} from "@lightprotocol/stateless.js";
+import {
+  getTestRpc,
+  newAccountWithLamports,
+  sleep,
+  TestRpc,
+} from "../src";
@@
-    payer = await newAccountWithLamports(rpc, 10e9, 256);
-    bob = await newAccountWithLamports(rpc, 10e9, 256);
+    payer = await newAccountWithLamports(rpc, 10e9);
+    bob = await newAccountWithLamports(rpc, 10e9);
js/program-test/src/merkle-tree/merkle-tree.ts (1)

204-213: Fix deserialize to initialise the hasher
deserialize returns an instance with _lightWasm === undefined. Any call to path, update, etc., will immediately throw because they all invoke this._lightWasm.poseidonHashString. Replace the unused hashFunction parameter with a LightWasm argument and store it on the instance.

-  static deserialize(
-    data: any,
-    hashFunction: (left: string, right: string) => string,
-  ) {
+  static deserialize(data: any, lightWasm: LightWasm) {
     const instance = Object.assign(Object.create(this.prototype), data);
-    instance._hash = hashFunction;
+    instance._lightWasm = lightWasm;
     instance.capacity = 2 ** instance.levels;
     instance.zeroElement = instance._zeros[0];
     return instance;
   }
js/program-test/src/test-rpc/get-parsed-events.ts (1)

95-111: Guard txRaw before dereferencing
getTransaction can return null, yet the code immediately uses txRaw!.meta!.innerInstructions, which will throw when RPC misses a slot. Add a null check so we skip those transactions safely.

-    if (
-      txRaw!.meta!.innerInstructions &&
-      txRaw!.meta!.innerInstructions.length > 0
-    ) {
-      for (const innerGroup of txRaw!.meta!.innerInstructions) {
+    if (!txRaw || !txRaw.meta || !txRaw.meta.innerInstructions) {
+      continue;
+    }
+
+    if (txRaw.meta.innerInstructions.length > 0) {
+      for (const innerGroup of txRaw.meta.innerInstructions) {
js/program-test/src/test-rpc/test-rpc.ts (1)

367-547: Restore sparse-index Merkle construction for valid proofs
The V1 branch rebuilds the tree by piling hashes into a dense array and then uses the array position (leafIndex = findIndex(...)) everywhere. As soon as the indexer skips history, on-chain leaf indices jump (e.g., 18 432) and this dense array no longer matches reality—the recomputed root is wrong and every returned proof fails verification. Please materialize the tree with zeros at missing indices and use the true on-chain index whenever deriving paths/metadata.

-      let merkleTree: MerkleTree | undefined;
+      let merkleTree: MerkleTree | undefined;-      if (treeInfo.treeType === TreeType.StateV1) {
+      if (treeInfo.treeType === TreeType.StateV1) {-        const leafStrings = leaves.map((leaf, idx) => {
-
-          return leafStr;
-        });
-        merkleTree = new MerkleTree(this.depth, this.lightWasm, leafStrings);
+        const treeData = leavesByTree.get(tree.toBase58())!;
+        if (treeData.leaves.length === 0) {
+          merkleTree = new MerkleTree(this.depth, this.lightWasm, []);
+        } else {
+          const zeroLeaf = bn(0).toString();
+          const maxLeafIndex = Math.max(...treeData.leafIndices);
+          const leafStrings = Array.from(
+            { length: maxLeafIndex + 1 },
+            () => zeroLeaf,
+          );
+          for (let idx = 0; idx < treeData.leaves.length; idx++) {
+            const onChainIndex = treeData.leafIndices[idx];
+            leafStrings[onChainIndex] = bn(treeData.leaves[idx]).toString();
+          }
+          merkleTree = new MerkleTree(this.depth, this.lightWasm, leafStrings);
+        }-        let leafIndex: number;
+        let leafArrayIndex: number;
         try {
-          leafIndex = leaves.findIndex((leaf, leafIdx) => {
+          leafArrayIndex = leaves.findIndex((leaf, leafIdx) => {
             …
           });
-          console.log(`[TEST-RPC] Found leafIndex:`, leafIndex);
+          console.log(`[TEST-RPC] Found leaf array index:`, leafArrayIndex);
         } catch (err) {
           …
         }
 
-        if (leafIndex !== -1) {
+        if (leafArrayIndex !== -1) {
+          const onChainLeafIndex =
+            leavesByTree.get(tree.toBase58())!.leafIndices[leafArrayIndex];
           if (treeInfo.treeType === TreeType.StateV1) {
-            const pathElements = merkleTree.path(leafIndex).pathElements;
+            const pathElements =
+              merkleTree.path(onChainLeafIndex).pathElements;
             …
             const merkleProof: MerkleContextWithMerkleProof = {
               …
-              leafIndex,
+              leafIndex: onChainLeafIndex,-              rootIndex: leaves.length,
+              rootIndex: leavesByTree.get(tree.toBase58())!.leafIndices.length,
             };
js/program-test/tests/transfer.test.ts (1)

26-26: Test description mentions "alice" but code uses "payer".

The test description refers to "alice -> bob" transfers, but the actual implementation uses the payer variable. This inconsistency was previously flagged.

sdk-tests/sdk-anchor-test/tests/test_v2.ts (2)

62-65: Replace magic numbers with TreeType enum.

Use TreeType.StateV1/StateV2 instead of 2/3 for robustness.

Apply:

-import {
-  bn,
-  type CompressedAccountWithMerkleContext,
-  defaultTestStateTreeAccounts,
-  deriveAddressSeedV2,
-  deriveAddressV2,
-  PackedAccounts,
-  type Rpc,
-  SystemAccountMetaConfig,
-} from "@lightprotocol/stateless.js";
+import {
+  bn,
+  type CompressedAccountWithMerkleContext,
+  deriveAddressSeedV2,
+  deriveAddressV2,
+  PackedAccounts,
+  type Rpc,
+  SystemAccountMetaConfig,
+  TreeType,
+} from "@lightprotocol/stateless.js";
@@
-const stateTreeInfo = existingTreeInfos.find(
-  (info) => info.treeType === 2 || info.treeType === 3
-); // StateV1 or StateV2
+const stateTreeInfo = existingTreeInfos.find(
+  (info) =>
+    info.treeType === TreeType.StateV1 || info.treeType === TreeType.StateV2,
+); // StateV1 or StateV2

141-149: Missing SdkAnchorTest type import (compile error).

Program requires importing the generated type.

Add at the top:

import type { SdkAnchorTest } from "../target/types/sdk_anchor_test";
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (1)

393-398: Remove debug logging from tests.

Drop noisy console.log statements.

-        console.log('totalCompressedAmount ', totalCompressedAmount);
-        console.log('ataBalanceBefore', ataBalanceBefore);
-        console.log(
-            'ataBalanceBefore.value.amount ',
-            ataBalanceBefore.value.amount,
-        );
js/program-test/src/spl-token-utils.ts (1)

201-205: Remove leftover debug logs in helpers.

These pollute output for all callers.

-  console.log(
-    "[spl-token-utils.ts:195] Converting amount:",
-    typeof data.amount,
-    data.amount,
-  );
@@
-  console.log(
-    "[spl-token-utils.ts:223] Converting supply:",
-    typeof data.supply,
-    data.supply,
-  );

Also applies to: 233-237

🧹 Nitpick comments (1)
js/program-test/tests/poseidon-comparison.test.ts (1)

32-248: Import shared Poseidon constants instead of duplicating.

We just exported these MDS matrices and round constants from src/hasher/constants.ts. Repeating 600+ literal lines here risks drift the moment we fix a constant upstream. Please import MDS_3 / CONSTANTS_3_FLAT (and the t=4 variants) from the shared module and reuse them.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6376344 and 82f0ed4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (79)
  • .github/actions/setup-and-build/action.yml (0 hunks)
  • .github/workflows/js-v2.yml (3 hunks)
  • .github/workflows/js.yml (3 hunks)
  • cli/package.json (2 hunks)
  • cli/test/commands/approve-and-mint-to/index.test.ts (1 hunks)
  • cli/test/commands/create-token-pool/index.test.ts (1 hunks)
  • cli/test/helpers/helpers.ts (1 hunks)
  • js/compressed-token/package.json (3 hunks)
  • js/compressed-token/tests/e2e/compress.test.ts (2 hunks)
  • js/compressed-token/tests/e2e/merge-token-accounts.test.ts (2 hunks)
  • js/compressed-token/tests/e2e/mint-to.test.ts (3 hunks)
  • js/compressed-token/tests/e2e/rpc-token-interop.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (4 hunks)
  • js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (15 hunks)
  • js/compressed-token/tests/program-test/compress.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/create-mint.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/create-token-pool.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts (5 hunks)
  • js/compressed-token/tests/program-test/decompress.test.ts (3 hunks)
  • js/compressed-token/tests/program-test/delegate.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/mint-to.test.ts (1 hunks)
  • js/compressed-token/tests/program-test/multi-pool.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/transfer-delegated.test.ts (2 hunks)
  • js/compressed-token/tests/program-test/transfer.test.ts (4 hunks)
  • js/compressed-token/vitest.config.ts (1 hunks)
  • js/program-test/.prettierignore (1 hunks)
  • js/program-test/README.md (1 hunks)
  • js/program-test/eslint.config.cjs (1 hunks)
  • js/program-test/package.json (1 hunks)
  • js/program-test/rollup.config.js (1 hunks)
  • js/program-test/src/hasher/constants.ts (1 hunks)
  • js/program-test/src/hasher/index.ts (1 hunks)
  • js/program-test/src/hasher/noble-hasher.ts (1 hunks)
  • js/program-test/src/index.ts (1 hunks)
  • js/program-test/src/litesvm-rpc.ts (1 hunks)
  • js/program-test/src/merkle-tree/index.ts (1 hunks)
  • js/program-test/src/merkle-tree/indexed-array.ts (1 hunks)
  • js/program-test/src/merkle-tree/merkle-tree.ts (1 hunks)
  • js/program-test/src/spl-token-utils.ts (1 hunks)
  • js/program-test/src/test-rpc/get-compressed-accounts.ts (1 hunks)
  • js/program-test/src/test-rpc/get-compressed-token-accounts.ts (1 hunks)
  • js/program-test/src/test-rpc/get-parsed-events.ts (1 hunks)
  • js/program-test/src/test-rpc/index.ts (1 hunks)
  • js/program-test/src/test-rpc/test-rpc.ts (1 hunks)
  • js/program-test/src/test-utils.ts (1 hunks)
  • js/program-test/src/types.ts (1 hunks)
  • js/program-test/tests/compress.test.ts (1 hunks)
  • js/program-test/tests/merkle-tree.test.ts (1 hunks)
  • js/program-test/tests/poseidon-comparison.test.ts (1 hunks)
  • js/program-test/tests/rpc-interop.test.ts (1 hunks)
  • js/program-test/tests/rpc-multi-trees.test.ts (1 hunks)
  • js/program-test/tests/test-rpc.test.ts (1 hunks)
  • js/program-test/tests/transfer.test.ts (1 hunks)
  • js/program-test/tsconfig.json (1 hunks)
  • js/program-test/vitest.config.ts (1 hunks)
  • js/stateless.js/package.json (2 hunks)
  • js/stateless.js/rollup.config.js (2 hunks)
  • js/stateless.js/src/rpc.ts (0 hunks)
  • js/stateless.js/src/test-helpers/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/indexed-array.ts (0 hunks)
  • js/stateless.js/src/test-helpers/merkle-tree/merkle-tree.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-accounts.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-token-accounts.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/index.ts (0 hunks)
  • js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts (0 hunks)
  • js/stateless.js/src/utils/send-and-confirm.ts (1 hunks)
  • js/stateless.js/tests/e2e/compress.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/rpc-interop.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/rpc-multi-trees.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/test-rpc.test.ts (0 hunks)
  • js/stateless.js/tests/e2e/transfer.test.ts (0 hunks)
  • js/stateless.js/tests/unit/merkle-tree/merkle-tree.test.ts (0 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • scripts/devenv/versions.sh (1 hunks)
  • sdk-tests/sdk-anchor-test/package.json (1 hunks)
  • sdk-tests/sdk-anchor-test/tests/test_v1.ts (4 hunks)
  • sdk-tests/sdk-anchor-test/tests/test_v2.ts (5 hunks)
💤 Files with no reviewable changes (17)
  • js/stateless.js/src/test-helpers/merkle-tree/index.ts
  • js/stateless.js/tests/e2e/transfer.test.ts
  • js/stateless.js/tests/e2e/rpc-interop.test.ts
  • js/stateless.js/src/test-helpers/test-rpc/index.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-accounts.ts
  • .github/actions/setup-and-build/action.yml
  • js/stateless.js/tests/e2e/compress.test.ts
  • js/stateless.js/tests/e2e/rpc-multi-trees.test.ts
  • js/stateless.js/tests/e2e/test-rpc.test.ts
  • js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts
  • js/stateless.js/src/test-helpers/merkle-tree/indexed-array.ts
  • js/stateless.js/src/test-helpers/index.ts
  • js/stateless.js/tests/unit/merkle-tree/merkle-tree.test.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-compressed-token-accounts.ts
  • js/stateless.js/src/test-helpers/merkle-tree/merkle-tree.ts
  • js/stateless.js/src/rpc.ts
  • js/stateless.js/src/test-helpers/test-rpc/get-parsed-events.ts
✅ Files skipped from review due to trivial changes (3)
  • js/program-test/README.md
  • js/compressed-token/tests/program-test/mint-to.test.ts
  • js/program-test/src/merkle-tree/indexed-array.ts
🚧 Files skipped from review as they are similar to previous changes (23)
  • js/program-test/src/merkle-tree/index.ts
  • js/program-test/src/test-utils.ts
  • js/stateless.js/rollup.config.js
  • js/compressed-token/tests/e2e/rpc-token-interop.test.ts
  • cli/test/helpers/helpers.ts
  • js/program-test/tsconfig.json
  • js/program-test/src/types.ts
  • js/program-test/tests/rpc-multi-trees.test.ts
  • js/program-test/tests/merkle-tree.test.ts
  • js/program-test/.prettierignore
  • js/program-test/src/test-rpc/index.ts
  • scripts/devenv/versions.sh
  • js/compressed-token/tests/program-test/delegate.test.ts
  • js/program-test/src/index.ts
  • js/compressed-token/tests/e2e/merge-token-accounts.test.ts
  • pnpm-workspace.yaml
  • cli/test/commands/create-token-pool/index.test.ts
  • js/compressed-token/tests/program-test/compress.test.ts
  • js/program-test/tests/compress.test.ts
  • js/program-test/vitest.config.ts
  • js/compressed-token/tests/program-test/decompress-delegated.test.ts
  • js/stateless.js/package.json
  • js/program-test/src/test-rpc/get-compressed-token-accounts.ts
🧰 Additional context used
🧬 Code graph analysis (23)
js/program-test/rollup.config.js (1)
js/stateless.js/rollup.config.js (1)
  • rolls (11-68)
js/compressed-token/tests/program-test/create-token-pool.test.ts (2)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/litesvm-rpc.ts (4)
js/program-test/src/index.ts (3)
  • LiteSVMRpc (8-8)
  • LiteSVMConfig (14-14)
  • createLiteSVMRpc (8-8)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • TestRpc (113-1119)
js/program-test/src/types.ts (1)
  • LiteSVMConfig (16-27)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • COMPRESSED_TOKEN_PROGRAM_ID (256-258)
  • featureFlags (16-35)
js/program-test/src/test-rpc/test-rpc.ts (11)
js/stateless.js/src/constants.ts (3)
  • defaultTestStateTreeAccounts (236-244)
  • localTestActiveStateTreeInfos (151-219)
  • batchAddressTree (304-304)
js/stateless.js/src/rpc-interface.ts (16)
  • CompressionApiInterface (762-867)
  • GetCompressedAccountsByOwnerConfig (45-50)
  • WithCursor (250-255)
  • LatestNonVotingSignaturesPaginated (57-67)
  • LatestNonVotingSignatures (33-43)
  • GetCompressedTokenAccountsByOwnerOrDelegateOptions (205-209)
  • ParsedTokenAccount (236-239)
  • WithContext (241-248)
  • TokenBalance (210-210)
  • SignatureWithMetadata (69-73)
  • CompressedTransaction (120-142)
  • PaginatedOptions (217-220)
  • CompressedMintTokenHolders (52-55)
  • HashWithTree (103-107)
  • AddressWithTree (109-113)
  • ValidityProofWithContext (161-190)
js/stateless.js/src/state/types.ts (2)
  • TreeInfo (77-99)
  • PublicTransactionEvent (231-272)
js/program-test/src/merkle-tree/indexed-array.ts (2)
  • hash (24-35)
  • IndexedArray (58-307)
js/program-test/src/test-rpc/get-compressed-accounts.ts (4)
  • getCompressedAccountsForTest (42-96)
  • getCompressedAccountByHashTest (23-29)
  • getMultipleCompressedAccountsByHashTest (31-39)
  • getCompressedAccountsByOwnerTest (14-21)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/program-test/src/merkle-tree/merkle-tree.ts (2)
  • MerkleTree (13-214)
  • root (74-78)
js/program-test/src/test-rpc/get-compressed-token-accounts.ts (3)
  • getCompressedTokenAccountsByOwnerTest (179-195)
  • getCompressedTokenAccountsByDelegateTest (197-212)
  • getCompressedTokenAccountByHashTest (214-229)
js/stateless.js/src/rpc.ts (4)
  • MerkleContextWithNewAddressProof (402-412)
  • convertMerkleProofsWithContextToHex (424-442)
  • proverRequest (336-390)
  • convertNonInclusionMerkleProofInputsToHex (444-470)
js/program-test/src/test-rpc/get-compressed-accounts.ts (5)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/stateless.js/src/state/compressed-account.ts (3)
  • CompressedAccountWithMerkleContext (14-17)
  • MerkleContext (80-97)
  • createCompressedAccountWithMerkleContextLegacy (240-253)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/test-rpc/get-parsed-events.ts (1)
  • getParsedEvents (34-144)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • getStateTreeInfoByPubkey (8-13)
js/compressed-token/tests/e2e/compress.test.ts (1)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/tests/rpc-interop.test.ts (8)
js/stateless.js/src/rpc.ts (2)
  • Rpc (551-1892)
  • createRpc (231-264)
js/program-test/src/test-rpc/test-rpc.ts (2)
  • TestRpc (113-1119)
  • getTestRpc (84-103)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/test-utils.ts (2)
  • newAccountWithLamports (7-15)
  • sleep (20-22)
js/stateless.js/src/utils/address.ts (2)
  • deriveAddressSeed (11-18)
  • deriveAddress (29-46)
js/stateless.js/src/actions/create-account.ts (1)
  • createAccountWithLamports (113-175)
js/stateless.js/src/constants.ts (1)
  • featureFlags (16-35)
js/program-test/tests/poseidon-comparison.test.ts (2)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-67)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/test-rpc/get-parsed-events.ts (5)
js/stateless.js/src/rpc.ts (1)
  • Rpc (551-1892)
js/stateless.js/src/state/types.ts (2)
  • PublicTransactionEvent (231-272)
  • InstructionDataInvoke (277-306)
js/stateless.js/src/constants.ts (7)
  • accountCompressionProgram (76-77)
  • COMPUTE_BUDGET_PATTERN (52-52)
  • noopProgram (74-74)
  • INVOKE_DISCRIMINATOR (54-56)
  • INVOKE_CPI_DISCRIMINATOR (58-60)
  • INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR (62-64)
  • INSERT_INTO_QUEUES_DISCRIMINATOR (70-72)
js/stateless.js/src/programs/system/layout.ts (6)
  • decodePublicTransactionEvent (392-396)
  • decodeInstructionDataInvoke (240-246)
  • decodeInstructionDataInvokeCpi (248-254)
  • decodeInstructionDataInvokeCpiWithReadOnly (234-238)
  • deserializeAppendNullifyCreateAddressInputsIndexer (441-514)
  • convertToPublicTransactionEvent (516-598)
js/stateless.js/src/utils/conversion.ts (1)
  • convertInvokeCpiWithReadOnlyToInvoke (121-191)
js/compressed-token/tests/program-test/approve-and-mint-to.test.ts (10)
js/stateless.js/src/state/types.ts (1)
  • TreeInfo (77-99)
js/compressed-token/src/utils/get-token-pool-infos.ts (3)
  • TokenPoolInfo (104-141)
  • selectTokenPoolInfo (173-187)
  • getTokenPoolInfos (37-93)
js/stateless.js/src/utils/get-state-tree-infos.ts (1)
  • selectStateTreeInfo (84-110)
js/program-test/src/spl-token-utils.ts (1)
  • splGetOrCreateAssociatedTokenAccount (276-324)
js/compressed-token/src/program.ts (1)
  • CompressedTokenProgram (600-1659)
js/stateless.js/src/utils/dedupe-signer.ts (1)
  • dedupeSigner (4-11)
js/stateless.js/src/utils/send-and-confirm.ts (2)
  • buildAndSignTx (145-166)
  • sendAndConfirmTx (51-62)
js/stateless.js/src/utils/conversion.ts (1)
  • toArray (28-29)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/compressed-token/tests/program-test/transfer.test.ts (1)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/compressed-token/tests/e2e/mint-to.test.ts (1)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/tests/transfer.test.ts (4)
js/program-test/src/litesvm-rpc.ts (2)
  • LiteSVMRpc (23-997)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/program-test/src/hasher/noble-hasher.ts (3)
js/program-test/src/hasher/constants.ts (4)
  • CONSTANTS_3_FLAT (48-244)
  • MDS_3 (2-18)
  • CONSTANTS_4_FLAT (245-565)
  • MDS_4 (20-45)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-67)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/compressed-token/tests/program-test/create-mint.test.ts (2)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/compressed-token/tests/program-test/multi-pool.test.ts (4)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
cli/test/helpers/helpers.ts (1)
  • createTestSplMint (71-103)
js/program-test/src/spl-token-utils.ts (2)
  • splGetOrCreateAssociatedTokenAccount (276-324)
  • splMintTo (120-149)
js/program-test/tests/test-rpc.test.ts (6)
js/stateless.js/src/constants.ts (4)
  • defaultTestStateTreeAccounts (236-244)
  • STATE_MERKLE_TREE_ROLLOVER_FEE (338-340)
  • featureFlags (16-35)
  • STATE_MERKLE_TREE_NETWORK_FEE (356-356)
js/stateless.js/src/state/compressed-account.ts (1)
  • CompressedAccountWithMerkleContext (14-17)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
js/compressed-token/tests/program-test/transfer-delegated.test.ts (2)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/index.ts (1)
  • createLiteSVMRpc (8-8)
sdk-tests/sdk-anchor-test/tests/test_v2.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/program-test/src/merkle-tree/merkle-tree.ts (1)
js/program-test/src/test-rpc/test-rpc.ts (1)
  • LightWasm (63-67)
js/compressed-token/tests/program-test/decompress.test.ts (2)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/spl-token-utils.ts (1)
  • splCreateAssociatedTokenAccount (78-115)
sdk-tests/sdk-anchor-test/tests/test_v1.ts (2)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/test-utils.ts (1)
  • newAccountWithLamports (7-15)
js/compressed-token/tests/program-test/compress-spl-token-account.test.ts (4)
js/program-test/src/hasher/noble-hasher.ts (1)
  • NobleHasherFactory (159-183)
js/program-test/src/litesvm-rpc.ts (1)
  • createLiteSVMRpc (1002-1008)
js/program-test/src/spl-token-utils.ts (2)
  • splCreateAssociatedTokenAccount (78-115)
  • splMintTo (120-149)
js/stateless.js/src/state/bn.ts (1)
  • bn (3-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: cli-v1
  • GitHub Check: Forester e2e test
  • GitHub Check: stateless-js-v1
  • GitHub Check: cli-v2
  • GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client, cargo test...
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v...
  • GitHub Check: lint
  • GitHub Check: stateless-js-v2
🔇 Additional comments (13)
js/stateless.js/src/utils/send-and-confirm.ts (2)

98-109: LGTM! Error handling correctly implemented.

The error handling logic correctly addresses the past review concerns by:

  • Checking result?.err before continuing to poll
  • Rejecting immediately with a descriptive error message
  • Using return to prevent further execution in the interval iteration

This prevents unnecessary polling until timeout when a transaction has already failed.


111-124: LGTM! Commitment threshold logic correctly implements the hierarchy.

The commitment comparison logic correctly implements the finalized > confirmed > processed hierarchy:

  • Direct equality check handles exact matches
  • 'processed' accepts 'confirmed' or 'finalized' (higher commitments)
  • 'confirmed' accepts 'finalized' (higher commitment)
  • Gracefully handles null/undefined statusValue by continuing to poll

This approach is more robust than direct equality and aligns with typical commitment semantics.

js/compressed-token/vitest.config.ts (1)

7-18: Document LiteSVM failure and scope parallelism

  • Attach the exact LiteSVM “bad alloc” error (stack trace or repro).
  • Benchmark with default parallel Vitest:
    time npx vitest run --config js/compressed-token/vitest.config.ts
  • Scope sequential execution to failing suites via describe.sequential or test.concurrent(false) instead of disabling all parallelism.
-        // litesvm fails with bad alloc if not configured
-        // Use threads pool instead of forks to avoid native addon corruption
-        // Threads share the same V8 isolate and native addon context
+        // LiteSVM exhibits memory-allocation errors under parallel execution (see <error-log-link>).
+        // Using threads pool with singleThread=true ensures isolated native addon state.
+        // TODO: add error logs, benchmark default parallel run, and limit sequential execution
+        // to affected suites (tracked in issue #XXXX).
.github/workflows/js-v2.yml (1)

24-24: LGTM! Workflow changes align with program-test integration.

The changes appropriately:

  • Upgrade to a more powerful runner (warp-ubuntu-latest-x64-4x) for improved test performance
  • Add program-test build step following the existing pattern
  • Add program-test V2 test execution with proper environment setup

Also applies to: 66-69, 103-106

js/program-test/src/hasher/index.ts (1)

1-2: LGTM! Clean barrel export pattern.

The module appropriately consolidates hasher exports into a single public API surface.

.github/workflows/js.yml (1)

24-24: LGTM! Workflow changes mirror V2 and integrate program-test.

The V1 workflow updates are consistent with the V2 workflow changes, adding program-test build and test steps while upgrading to a more capable runner.

Also applies to: 66-69, 103-106

js/compressed-token/tests/program-test/create-mint.test.ts (1)

6-11: LGTM! Clean migration to program-test utilities.

The test correctly migrates from WasmFactory to NobleHasherFactory and from getTestRpc to createLiteSVMRpc, consolidating test infrastructure under the new program-test package.

Also applies to: 55-56

js/compressed-token/tests/e2e/mint-to.test.ts (1)

9-9: LGTM! Migration follows established pattern.

The test correctly adopts the NobleHasherFactory and sources getTestRpc from the program-test package, maintaining consistency with the migration strategy.

Also applies to: 23-23, 29-29, 70-70

js/compressed-token/tests/program-test/decompress.test.ts (1)

12-18: LGTM! Comprehensive migration to program-test infrastructure.

The test successfully migrates to:

  • NobleHasherFactory for hasher initialization
  • createLiteSVMRpc for RPC creation
  • splCreateAssociatedTokenAccount for token account creation

All changes align with the program-test migration strategy.

Also applies to: 82-83, 103-103

js/program-test/tests/transfer.test.ts (1)

1-65: LGTM! Transfer test logic is sound.

The test correctly:

  • Initializes RPC using the new program-test infrastructure
  • Creates funded accounts and compresses initial lamports
  • Executes transfers in a loop with proper balance verification
  • Uses big-number arithmetic for accurate comparisons
js/compressed-token/tests/program-test/transfer-delegated.test.ts (1)

11-15: LGTM! Migration aligns with program-test strategy.

The test correctly adopts NobleHasherFactory for hasher initialization and createLiteSVMRpc for RPC creation, consistent with the systematic migration across all test files.

Also applies to: 181-182

sdk-tests/sdk-anchor-test/tests/test_v2.ts (1)

150-159: Verify address queue passed to getValidityProofV0.

You pass queue: addressTree. Ensure this is correct for V2; typically queue differs from tree.

If available, prefer:

const { tree: addressTree, queue: addressQueue } = await rpc.getAddressTreeInfoV2();
...
{ tree: addressTree, queue: addressQueue, address: bn(address.toBytes()) }
js/compressed-token/tests/program-test/multi-pool.test.ts (1)

86-115: Migration to program-test utilities looks solid.

NobleHasherFactory + createLiteSVMRpc + spl* helpers are used correctly; account funding and mint setup are consistent.

import { Keypair } from "@solana/web3.js";
import { createTestSplMint, requestAirdrop } from "../../helpers/helpers";
import { getTestRpc } from "@lightprotocol/stateless.js";
import { getTestRpc } from "@lightprotocol/program-test";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify the migration is complete and tests pass.

The import change from @lightprotocol/stateless.js to @lightprotocol/program-test aligns with the PR objective. The usage at line 24 remains unchanged, suggesting API compatibility.

Please confirm:

  1. @lightprotocol/program-test is properly added to the package dependencies
  2. The getTestRpc function signature is identical or compatible
  3. All tests pass with the new import

🏁 Script executed:

#!/bin/bash
# Verify package dependency and getTestRpc usage

# Check if @lightprotocol/program-test is in package.json
echo "=== Checking package.json for @lightprotocol/program-test dependency ==="
fd -t f "package.json" cli | xargs cat | jq '.dependencies, .devDependencies' 2>/dev/null | grep -i "program-test" || echo "Note: Check if dependency is added"

# Search for all getTestRpc usages to verify migration consistency
echo -e "\n=== Finding all getTestRpc usages ==="
rg -n --type=ts 'getTestRpc' cli/test

# Check if there are remaining imports from stateless.js test helpers
echo -e "\n=== Checking for remaining stateless.js test-helper imports ==="
rg -n --type=ts "from ['\"]@lightprotocol/stateless.js['\"]" cli/test | grep -v "node_modules" || echo "No remaining imports found"

Length of output: 1024


Remove remaining stateless.js import in test helpers

cli/test/helpers/helpers.ts:15 – replace or remove the import from @lightprotocol/stateless.js and switch to @lightprotocol/program-test equivalents to complete the migration.

🤖 Prompt for AI Agents
In cli/test/commands/approve-and-mint-to/index.test.ts around line 8 and
specifically in cli/test/helpers/helpers.ts at line 15, remove the remaining
import from `@lightprotocol/stateless.js` and replace it with the corresponding
exports from `@lightprotocol/program-test`; update the helpers file to import
the `program-test` equivalents (e.g., getTestRpc and any other helper functions
used) and refactor any usages in helpers.ts to call those `program-test`
functions instead so the test helpers no longer depend on `stateless.js`.

Comment on lines +492 to +501
message: {
accountKeys,
compiledInstructions,
recentBlockhash:
message.recentBlockhash || message.header?.recentBlockhash || "",
addressTableLookups: message.addressTableLookups || [],
},
signatures:
"signatures" in rawTx ? rawTx.signatures : [(rawTx as any).signature],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return base58 signature strings in getTransaction.

getTransaction still returns whatever is stored on the Transaction object (SignaturePubkeyPair objects for legacy txs, Uint8Arrays for versioned txs). JSON-RPC requires an array of base58 strings. Downstream consumers (and our own tooling) will break when they try to deserialize these non-string values. Please encode every signature with bs58.encode(...) before returning.

🤖 Prompt for AI Agents
In js/program-test/src/litesvm-rpc.ts around lines 492-501, the signatures being
returned are raw objects/Uint8Arrays instead of base58 strings; map the
signatures array to strings by importing bs58 and converting each entry: if it's
a SignaturePubkeyPair object use its .signature field, if it's a
Uint8Array/Buffer use it directly, and for non-null signatures call
bs58.encode(...) to produce a base58 string (leave null/undefined as null if
present); replace the current signatures value with this mapped array so
getTransaction returns an array of base58 signature strings.

@ananas-block ananas-block marked this pull request as draft October 10, 2025 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant