Skip to content

sdk: fix helius-laserstream detection and prebuild:browser hook - #2199

Open
Ysh204 wants to merge 4 commits into
velocity-exchange:masterfrom
Ysh204:master
Open

sdk: fix helius-laserstream detection and prebuild:browser hook#2199
Ysh204 wants to merge 4 commits into
velocity-exchange:masterfrom
Ysh204:master

Conversation

@Ysh204

@Ysh204 Ysh204 commented Jun 21, 2026

Copy link
Copy Markdown

Two small SDK build fixes:

  • Replace fs.existsSync with require.resolve() in mock-optional-deps.js
    so hoisted/non-local installs are detected correctly before creating the shim
  • Add prebuild:browser script so the shim runs before yarn build:browser
    (the prebuild lifecycle hook only fires for build, not build:browser)

Summary by CodeRabbit

  • New Features

    • Added transfer hook token support for force delete user operations
    • Enabled transfer hook token support for pool transfer operations
    • Implemented optional dependency shimming to improve compatibility
  • Chores

    • Enabled overflow checks in release builds
    • Updated Node.js engine requirements to support versions 18.0.0 and above
    • Enhanced build script infrastructure

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Transfer-hook token support is wired into handle_force_delete_user and handle_transfer_pools on-chain by sharing a remaining_accounts_iter with load_maps and conditionally passing it to send_from_program_vault/receive. The SDK instruction builders getForceDeleteUserIx and getTransferPoolsIx are updated to append extra account metas when a spot market uses a transfer hook. A helius-laserstream shim script is added for optional-dependency builds, and overflow-checks = true is added to the release profile.

Changes

Transfer Hook Support in Programs and SDK

Layer / File(s) Summary
Rust program iterator reuse and conditional hook accounts
Cargo.toml, programs/drift/src/instructions/keeper.rs, programs/drift/src/instructions/user.rs
Both handle_force_delete_user and handle_transfer_pools now store remaining_accounts as a named peekable iterator passed to load_maps and later conditionally supplied to send_from_program_vault and receive based on has_transfer_hook(). Cargo.toml adds overflow-checks = true to the release profile.
SDK instruction builders: conditional extra account metas
sdk/src/driftClient.ts
getForceDeleteUserIx calls addExtraAccountMetasToRemainingAccounts per spot market mint when isTransferHook is true. getTransferPoolsIx appends deposit-from and borrow-to extra metas only when the respective amount is undefined or non-zero.

SDK Optional Dependency Shim and Build Config

Layer / File(s) Summary
helius-laserstream shim script and package.json build hooks
sdk/scripts/mock-optional-deps.js, sdk/package.json
mock-optional-deps.js resolves helius-laserstream and, if absent, writes a shim package.json, index.js, and index.d.ts under node_modules. package.json adds prebuild/prebuild:browser hooks invoking the script, updates clean to use fs.rmSync, and broadens engines.node to >=18.0.0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A hook in the vault, a shim in the node,
The iterator shared down each token road.
No more TODO left dangling in code,
Overflow-checks guard each release that's deployed.
The rabbit hops forward, the transfers comply —
With hooks, metas, and shims in the SDK sky! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'sdk: fix helius-laserstream detection and prebuild:browser hook' directly addresses the main SDK build issues described in the objectives, but the substantial changes to keeper.rs and user.rs (adding transfer-hook support) are not reflected in the title. Update the title to reflect all major changes, such as 'sdk: fix helius-laserstream detection and add transfer-hook support' or split into multiple PRs by concern.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@Ysh204

Ysh204 commented Jun 21, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
sdk/src/driftClient.ts (1)

4108-4143: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align token-program remaining accounts with the active transfer-hook side.

borrowToSpotMarket hook metas are appended (Lines 4133-4141), but tokenPrograms is built from borrowFromSpotMarket (Lines 4104-4113). If borrowTo uses a different token program, the on-chain account contract can break for the borrow leg.

Suggested fix
 		const tokenPrograms = new Set<string>();
 		const depositFromSpotMarket = this.getSpotMarketAccount(
 			depositFromMarketIndex
 		);
 		const borrowFromSpotMarket = this.getSpotMarketAccount(
 			borrowFromMarketIndex
 		);
+		const borrowToSpotMarket = this.getSpotMarketAccount(borrowToMarketIndex);

 		tokenPrograms.add(
 			this.getTokenProgramForSpotMarket(depositFromSpotMarket).toBase58()
 		);
 		tokenPrograms.add(
 			this.getTokenProgramForSpotMarket(borrowFromSpotMarket).toBase58()
 		);
+		if (borrowAmount === undefined || !borrowAmount.isZero()) {
+			tokenPrograms.add(
+				this.getTokenProgramForSpotMarket(borrowToSpotMarket).toBase58()
+			);
+		}

@@
-		if (borrowAmount === undefined || !borrowAmount.isZero()) {
-			const borrowToSpotMarket = this.getSpotMarketAccount(borrowToMarketIndex);
+		if (borrowAmount === undefined || !borrowAmount.isZero()) {
 			this.addTokenMintToRemainingAccounts(borrowToSpotMarket, remainingAccounts);
 			if (this.isTransferHook(borrowToSpotMarket)) {
 				await this.addExtraAccountMetasToRemainingAccounts(
 					borrowToSpotMarket.mint,
 					remainingAccounts
 				);
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/driftClient.ts` around lines 4108 - 4143, The tokenPrograms set is
populated with token programs from depositFromSpotMarket and
borrowFromSpotMarket, but transfer hook metadata is later added for
borrowToSpotMarket. If borrowToSpotMarket uses a different token program than
borrowFromSpotMarket, that token program will be missing from the remaining
accounts, causing on-chain execution to fail. Add the token program for
borrowToSpotMarket to the tokenPrograms set in the same conditional block where
you call addExtraAccountMetasToRemainingAccounts for the borrow leg (around
lines 4134-4141), ensuring all token programs used in the transaction are
included.
🧹 Nitpick comments (1)
sdk/scripts/mock-optional-deps.js (1)

23-28: ⚡ Quick win

Consider deriving the version from package.json.

The shim version is hardcoded to 0.1.8. If the helius-laserstream version in optionalDependencies is updated, this hardcoded value will become stale and could cause version mismatch confusion.

📦 Proposed fix to read version from package.json
+const pkgJson = require('../package.json');
+const shimVersion = pkgJson.optionalDependencies?.['helius-laserstream'] || '0.1.8';
+
 const packageJson = {
 	name: 'helius-laserstream',
-	version: '0.1.8',
+	version: shimVersion,
 	main: 'index.js',
 	types: 'index.d.ts'
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/scripts/mock-optional-deps.js` around lines 23 - 28, The version property
in the packageJson object is hardcoded to '0.1.8', which will become stale if
the actual helius-laserstream version in optionalDependencies is updated.
Instead of hardcoding the version, read it dynamically from the package.json
file at runtime and assign it to the version property in the packageJson object.
This ensures the mock always reflects the current version without manual
synchronization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@programs/drift/src/instructions/keeper.rs`:
- Around line 3183-3189: The issue is that a single remaining_accounts_iter is
initialized once and reused across the entire function, but the transfer-hook
execution path consumes iterator entries. When the spot-position loop processes
multiple hook-enabled transfers, the first hook CPI drains accounts from the
shared iterator that are needed by subsequent hook CPIs. To fix this, create
isolated account windows or iterators for each transfer within the loop instead
of relying on one shared draining iterator. This means calculating the account
slice needed for each individual transfer (considering load_maps consumption and
hook-specific accounts) and passing only that slice to the hook execution, or
rebuilding the iterator state for each transfer iteration.

In `@programs/drift/src/instructions/user.rs`:
- Around line 1322-1328: The shared remaining_accounts_iter is being consumed by
both deposit-side and borrow-side token CPI calls, causing the first transfer
hook to deplete accounts needed by the second transfer. Either segment the
remaining_accounts into separate slices before passing to each transfer
operation (one for deposit operations and one for borrow operations), or
refactor the code to pass only the required account slice to each transfer
invocation instead of sharing a single peekable iterator. This applies to all
locations where dual hook transfers occur (around lines 1322-1328, 1698-1702,
and 1731-1735).

In `@sdk/package.json`:
- Line 16: The build:browser script currently does not invoke the
prebuild:browser script that is required to run beforehand. Modify the
build:browser command to call prebuild:browser first before executing the
TypeScript compilation and post-build steps. This ensures that the
helius-laserstream shim is created during the prebuild phase so it's available
for the subsequent browser build compilation to use. You should structure the
command so that prebuild:browser runs and completes successfully before the tsc
and postbuild commands execute.

---

Outside diff comments:
In `@sdk/src/driftClient.ts`:
- Around line 4108-4143: The tokenPrograms set is populated with token programs
from depositFromSpotMarket and borrowFromSpotMarket, but transfer hook metadata
is later added for borrowToSpotMarket. If borrowToSpotMarket uses a different
token program than borrowFromSpotMarket, that token program will be missing from
the remaining accounts, causing on-chain execution to fail. Add the token
program for borrowToSpotMarket to the tokenPrograms set in the same conditional
block where you call addExtraAccountMetasToRemainingAccounts for the borrow leg
(around lines 4134-4141), ensuring all token programs used in the transaction
are included.

---

Nitpick comments:
In `@sdk/scripts/mock-optional-deps.js`:
- Around line 23-28: The version property in the packageJson object is hardcoded
to '0.1.8', which will become stale if the actual helius-laserstream version in
optionalDependencies is updated. Instead of hardcoding the version, read it
dynamically from the package.json file at runtime and assign it to the version
property in the packageJson object. This ensures the mock always reflects the
current version without manual synchronization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: adecb57b-7714-41ac-96bc-f9ebda3ea5ae

📥 Commits

Reviewing files that changed from the base of the PR and between 0aee1b1 and a811db6.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • Cargo.toml
  • programs/drift/src/instructions/keeper.rs
  • programs/drift/src/instructions/user.rs
  • sdk/package.json
  • sdk/scripts/mock-optional-deps.js
  • sdk/src/driftClient.ts

Comment on lines +3183 to +3189
let mut remaining_accounts_iter = ctx.remaining_accounts.iter().peekable();
let AccountMaps {
perp_market_map,
spot_market_map,
mut oracle_map,
} = load_maps(
&mut ctx.remaining_accounts.iter().peekable(),
&mut remaining_accounts_iter,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Shared transfer-hook iterator can break later CPIs in the same instruction.

A single remaining_accounts_iter is reused across the whole function, but the transfer-hook path consumes iterator entries. Since this function can perform multiple hook-enabled transfers (inside the spot-position loop), the first hook CPI can drain accounts required by later hook CPIs and cause runtime failure.

Use per-transfer account windows/iterators (or make hook-account consumption bounded to one transfer) instead of one shared draining iterator.

Also applies to: 3317-3321, 3339-3343

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@programs/drift/src/instructions/keeper.rs` around lines 3183 - 3189, The
issue is that a single remaining_accounts_iter is initialized once and reused
across the entire function, but the transfer-hook execution path consumes
iterator entries. When the spot-position loop processes multiple hook-enabled
transfers, the first hook CPI drains accounts from the shared iterator that are
needed by subsequent hook CPIs. To fix this, create isolated account windows or
iterators for each transfer within the loop instead of relying on one shared
draining iterator. This means calculating the account slice needed for each
individual transfer (considering load_maps consumption and hook-specific
accounts) and passing only that slice to the hook execution, or rebuilding the
iterator state for each transfer iteration.

Comment on lines +1322 to +1328
let mut remaining_accounts_iter = ctx.remaining_accounts.iter().peekable();
let AccountMaps {
perp_market_map,
spot_market_map,
mut oracle_map,
} = load_maps(
&mut ctx.remaining_accounts.iter().peekable(),
&mut remaining_accounts_iter,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Dual hook transfers can fail due to shared draining iterator.

remaining_accounts_iter is shared between deposit-side and borrow-side token CPIs. When both sides require transfer-hook accounts, the first hook transfer can consume the iterator, leaving the second without required accounts.

Please split/segment remaining accounts per transfer invocation (or change consumption semantics so each call reads only its own required extras).

Also applies to: 1698-1702, 1731-1735

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@programs/drift/src/instructions/user.rs` around lines 1322 - 1328, The shared
remaining_accounts_iter is being consumed by both deposit-side and borrow-side
token CPI calls, causing the first transfer hook to deplete accounts needed by
the second transfer. Either segment the remaining_accounts into separate slices
before passing to each transfer operation (one for deposit operations and one
for borrow operations), or refactor the code to pass only the required account
slice to each transfer invocation instead of sharing a single peekable iterator.
This applies to all locations where dual hook transfers occur (around lines
1322-1328, 1698-1702, and 1731-1735).

Comment thread sdk/package.json
@@ -15,7 +15,9 @@
"build": "yarn clean && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && node scripts/postbuild.js",
"build:browser": "yarn clean && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && node scripts/postbuild.js --force-env browser",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: prebuild:browser is never invoked by build:browser.

The prebuild:browser script is defined on line 19 but the build:browser command does not call it. Per the PR objectives, the shim script must run before the browser build to create the helius-laserstream shim for compilation. Without this, the browser build will fail when the optional dependency is not installed.

🔧 Proposed fix to invoke prebuild:browser
-"build:browser": "yarn clean && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && node scripts/postbuild.js --force-env browser",
+"build:browser": "yarn prebuild:browser && yarn clean && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && node scripts/postbuild.js --force-env browser",
📝 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
"build:browser": "yarn clean && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && node scripts/postbuild.js --force-env browser",
"build:browser": "yarn prebuild:browser && yarn clean && tsc -p tsconfig.json && tsc -p tsconfig.browser.json && node scripts/postbuild.js --force-env browser",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/package.json` at line 16, The build:browser script currently does not
invoke the prebuild:browser script that is required to run beforehand. Modify
the build:browser command to call prebuild:browser first before executing the
TypeScript compilation and post-build steps. This ensures that the
helius-laserstream shim is created during the prebuild phase so it's available
for the subsequent browser build compilation to use. You should structure the
command so that prebuild:browser runs and completes successfully before the tsc
and postbuild commands execute.

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.

1 participant