Skip to content

fix(solana-utils): lazy-load jito-ts so non-Jito consumers don't crash#3702

Open
0xghost42 wants to merge 1 commit into
pyth-network:mainfrom
0xghost42:fix/1838-lazy-jito-import
Open

fix(solana-utils): lazy-load jito-ts so non-Jito consumers don't crash#3702
0xghost42 wants to merge 1 commit into
pyth-network:mainfrom
0xghost42:fix/1838-lazy-jito-import

Conversation

@0xghost42
Copy link
Copy Markdown

@0xghost42 0xghost42 commented May 14, 2026

Summary

Closes #1838.

target_chains/solana/sdk/js/solana_utils/src/jito.ts previously did top-level value imports of jito-ts/dist/sdk/block-engine/{searcher,types}. Loading those modules transitively pulls jito-ts's nested @solana/web3.js@~1.77.3, which in turn requires rpc-websockets/dist/lib/client — a path removed in rpc-websockets@>=7.11.

Because solana_utils/transaction.ts imports buildJitoTipInstruction from ./jito, any consumer that loaded a @pythnetwork/solana-utils export — including PythSolanaReceiver from @pythnetwork/pyth-solana-receiver — eagerly walked the broken chain and crashed at module load with:

Error: Cannot find module 'rpc-websockets/dist/lib/client'
Require stack:
  - .../jito-ts/node_modules/@solana/web3.js/lib/index.cjs.js
  - .../jito-ts/dist/sdk/block-engine/types.js
  - .../@pythnetwork/solana-utils/lib/jito.js
  - .../@pythnetwork/solana-utils/lib/transaction.js
  - .../@pythnetwork/solana-utils/lib/index.js
  - .../@pythnetwork/pyth-solana-receiver/lib/PythSolanaReceiver.js
  ...

(matches the stack the issue reporter pasted)

Change

Split jito.ts imports so that jito-ts only resolves when the Jito send path is actually exercised:

  • SearcherClient and Bundle become import type (they were only used as types in function signatures and as a constructor inside one function).
  • sendTransactionsJito await imports Bundle immediately before constructing the bundle. The function is already async, so this is zero-cost for callers.
  • A short comment in jito.ts records the why so this isn't undone in a future cleanup.

Non-Jito consumers (the case reported in the issue) no longer trigger the broken require. Jito users still get the same runtime path; the underlying jito-ts / rpc-websockets clash there is a separate problem for jito-ts itself to resolve.

Verification

Added src/__tests__/JitoLazyImport.test.ts with two cases that assert importing either ../transaction or ../jito leaves jito-ts/* out of Node's require.cache. Both pass; existing TransactionSize.test.ts still passes.

$ pnpm --filter @pythnetwork/solana-utils test:unit
PASS src/__tests__/JitoLazyImport.test.ts
PASS src/__tests__/TransactionSize.test.ts
Test Suites: 2 passed, 2 total
Tests:       4 passed, 4 total

pnpm --filter @pythnetwork/solana-utils build succeeds (esm + cjs). No public API change — runtime contract is preserved, only the module-load order changes.

Bumped patch version 0.6.0 -> 0.6.1.

Out of scope

  • I did not bump jito-ts itself. Even the latest jito-ts@4.x still pins @solana/web3.js@~1.77.3, so the bump alone wouldn't have helped here; lazy-loading is the more robust fix and survives whatever jito-ts decides to do upstream.
  • Consumer-side rpc-websockets overrides (the workaround in the issue comment thread) remain a valid escape hatch if a user explicitly needs the Jito path on a newer rpc-websockets.

Open in Devin Review

Closes pyth-network#1838.

`jito.ts` previously did top-level value imports of
`jito-ts/dist/sdk/block-engine/{searcher,types}`. Pulling those modules
also pulls jito-ts's nested `@solana/web3.js@~1.77.3`, which transitively
`require`s `rpc-websockets/dist/lib/client` — a path removed in
`rpc-websockets@>=7.11`. Because `solana_utils/transaction.ts` imports
`buildJitoTipInstruction` from `./jito`, any consumer that loaded a
@pythnetwork/solana-utils export — including `PythSolanaReceiver` from
`@pythnetwork/pyth-solana-receiver` — eagerly walked the broken chain
and crashed at module load with:

  Error: Cannot find module 'rpc-websockets/dist/lib/client'

This change splits the imports so that jito-ts only resolves when the
Jito send path is actually exercised:

- `SearcherClient` and `Bundle` become `import type` — these were only
  used as types in function signatures.
- `sendTransactionsJito` dynamic-`import`s `Bundle` from
  `jito-ts/.../types` immediately before constructing the bundle. The
  function is already async, so this is zero-cost for callers.

Non-Jito consumers (the case reported in the issue) no longer trigger
the broken require. Jito users still get the same runtime path; the
underlying jito-ts/rpc-websockets clash there is a separate problem
for jito-ts to solve.

Tests
- Added `JitoLazyImport.test.ts` asserting that importing either
  `../transaction` or `../jito` leaves `jito-ts/*` out of `require.cache`.
- Existing `TransactionSize.test.ts` still passes (2 cases).
- `pnpm --filter @pythnetwork/solana-utils build` succeeds (esm + cjs).

Bumped to 0.6.1 (patch — runtime contract preserved, only load order changes).
@0xghost42 0xghost42 requested a review from a team as a code owner May 14, 2026 10:59
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

@0xghost42 is attempting to deploy a commit to the Pyth Network Team on Vercel.

A member of the Team first needs to authorize it.

@vercel vercel Bot temporarily deployed to Preview – entropy-explorer May 14, 2026 10:59 Inactive
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
entropy-explorer Skipped Skipped May 14, 2026 10:59am

Request Review

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Dependencies use pinned version ranges instead of catalog references

The REVIEW.md states "For TypeScript dependencies, prefer using catalog: versions over declaring package-specific dependency versions." Several dependencies in this package use explicit version ranges (e.g., @coral-xyz/anchor: ^0.29.0, @solana/web3.js: ^1.90.0, bs58: ^5.0.0) while the pnpm workspace catalog defines versions for some of these (e.g., @coral-xyz/anchor: ^0.30.1, @solana/web3.js: ^1.98.0, bs58: ^6.0.0). This is a pre-existing issue not introduced by this PR, but it's worth noting the divergence — the package uses older version ranges than what the catalog specifies.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

"type": "module",
"types": "./dist/cjs/index.d.ts",
"version": "0.6.0"
"version": "0.6.1"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Lock file not updated after version bump

The version was bumped from 0.6.0 to 0.6.1 in package.json:84, but the pnpm-lock.yaml was not updated in this PR. Per the repository's REVIEW.md guidelines ("Also suggest the lock file changes when a bump happens"), the lock file should typically be regenerated to reflect the new version. This is usually handled by running pnpm install before committing.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@0xghost42
Copy link
Copy Markdown
Author

Confirmed clean: re-ran pnpm install against this branch (fix/1838-lazy-jito-import, Node 24.15.0, pnpm 10.28.0) and lockfile is unchanged — pnpm reports Lockfile is up to date, resolution step is skipped / Already up to date.

Reason: every workspace consumer of @pythnetwork/solana-utils references it via workspace:* or workspace:^ (apps/price_pusher, contract_manager, governance/*, pyth_solana_receiver), so the package.json version bump 0.6.0 -> 0.6.1 is resolved at install time and doesn't materialize into pnpm-lock.yaml entries. The lock would only need regen if a consumer pinned a concrete semver range.

Re: the catalog finding — agreed it's pre-existing divergence, kept this PR scoped to the require-time crash fix; happy to follow up with a catalog migration sweep separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error: Cannot find module 'rpc-websockets/dist/lib/client'

1 participant