feat: jito block engine for solana mev protection and bundle splitting#12136
feat: jito block engine for solana mev protection and bundle splitting#12136gomesalexandre merged 13 commits intodevelopfrom
Conversation
Jito HTTP client, tip instruction builder, CSP, env vars. No swapper changes - pure infra for Solana MEV protection and atomic bundles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When Butter returns a Solana tx exceeding the 1232-byte limit, splits instructions into a 2-tx Jito bundle with tip in the last tx. Signs sequentially (hdwallet limitation), submits atomically via Jito. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds support for executing oversized Solana transactions via Jito bundles: new env var and validator, CSP allowlist entries, new Jito client/types and tip-instruction builder, detection of oversized transactions in multiple swappers, a Jito bundle execution module, and wiring in trade execution to sign and submit bundles. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Trade UI
participant Exec as Trade Execution
participant Adapter as Wallet Adapter
participant JitoSvc as Jito Service
participant Engine as Jito Block Engine
User->>UI: Initiate trade (oversized)
UI->>Exec: execSolanaTransaction(with isOversized)
Exec->>Exec: split instructions, build tip
Exec->>Adapter: signTransaction(tx1)
Adapter->>User: Request signature
User->>Adapter: Sign tx1
Adapter->>Exec: Return signed tx1
Exec->>Adapter: signTransaction(tx2 + tip)
Adapter->>User: Request signature
User->>Adapter: Sign tx2
Adapter->>Exec: Return signed tx2
Exec->>JitoSvc: sendBundle([tx1, tx2])
JitoSvc->>Engine: POST bundle
Engine->>JitoSvc: bundleId
loop poll
Exec->>JitoSvc: getBundleStatuses(bundleId)
JitoSvc->>Engine: status request
Engine->>JitoSvc: status response
end
JitoSvc->>Exec: landing confirmed
Exec->>UI: Transaction hash / success
UI->>User: Success
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
packages/swapper/src/swappers/ButterSwap/swapperApi/getTradeQuote.ts (1)
198-198: Consider extractingSOLANA_TX_SIZE_LIMITas a shared constant.This magic number is likely needed in other parts of the Jito bundle flow (e.g.,
src/lib/solanaJitoBundle.ts). Extracting it to a shared location likepackages/swapper/src/swappers/ButterSwap/utils/constants.tswould improve maintainability.💡 Optional: Extract constant
In
packages/swapper/src/swappers/ButterSwap/utils/constants.ts:/** Maximum Solana transaction size in bytes */ export const SOLANA_TX_SIZE_LIMIT = 1232Then import where needed:
+import { SOLANA_TX_SIZE_LIMIT } from '../utils/constants' ... - const SOLANA_TX_SIZE_LIMIT = 1232 const isOversized = txBytes.length > SOLANA_TX_SIZE_LIMIT🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swapper/src/swappers/ButterSwap/swapperApi/getTradeQuote.ts` at line 198, Extract the magic number by creating a shared constant SOLANA_TX_SIZE_LIMIT in a new module (e.g., export const SOLANA_TX_SIZE_LIMIT = 1232 in packages/swapper/src/swappers/ButterSwap/utils/constants.ts) and replace the local declaration in getTradeQuote.ts (the const SOLANA_TX_SIZE_LIMIT) with an import from that module; also update any other files that rely on the same value (for example functions in src/lib/solanaJitoBundle.ts) to import SOLANA_TX_SIZE_LIMIT so the value is centralized and maintainable.src/lib/tradeExecution.ts (1)
550-560: Consider providing a fallback foraddressLookupTableAddresses.
metadata.addressLookupTableAddressescould beundefinedif not set on the metadata. WhileexecSolanaJitoBundlemay handle this gracefully, it's safer to provide an explicit default.🔧 Suggested defensive default
return await execSolanaJitoBundle({ instructions: metadata.instructions, - addressLookupTableAddresses: metadata.addressLookupTableAddresses, + addressLookupTableAddresses: metadata.addressLookupTableAddresses ?? [], from, accountNumber: executableStep.accountNumber, sellAssetChainId: chainId, signTransaction, })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/tradeExecution.ts` around lines 550 - 560, The call to execSolanaJitoBundle should defensively handle a possibly undefined metadata.addressLookupTableAddresses; update the return call in the oversized branch that uses getExecutableTradeStep and execSolanaJitoBundle to pass a safe default (e.g., an empty array) for addressLookupTableAddresses when metadata.addressLookupTableAddresses is falsy, keeping all other arguments (instructions, from, accountNumber from getExecutableTradeStep(tradeQuote, stepIndex), sellAssetChainId: chainId, signTransaction) unchanged.src/lib/solanaJitoBundle.ts (1)
39-42: Simple midpoint split may produce unbalanced transactions.The current implementation splits instructions at the midpoint by count, not by serialized size. If instructions have varying sizes, one transaction could still exceed the 1232-byte limit while the other is well under.
For the initial implementation this is acceptable, but consider adding validation or a size-aware splitting strategy if issues arise in production.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/solanaJitoBundle.ts` around lines 39 - 42, The current midpoint-based split (variables midpoint, group1, group2 derived from instructions) can produce imbalanced serialized sizes; update the split logic to be size-aware by measuring each instruction's serialized size (or assembled transaction size) and partitioning instructions so each group stays below the 1232-byte limit, or at minimum add a validation step after splitting that calculates the serialized size of group1 and group2 and, if either exceeds the limit, re-buckets instructions (moving/merging) until both are valid; reference and modify the midpoint/group1/group2 logic in the function that performs the split and add clear validation/error handling when a group is too large.packages/chain-adapters/src/solana/jito/jitoService.ts (1)
30-47: Consider adding a fetch timeout for resilience.The
fetchcalls don't have a timeout configured. If the Jito endpoint is slow or unresponsive, this could block the trade execution indefinitely. Consider usingAbortControllerwith a timeout.⏱️ Example timeout pattern
const jsonRpc = async <T>( baseUrl: string, path: string, method: string, params: unknown[], timeoutMs = 30_000, ): Promise<T> => { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeoutMs) try { const response = await fetch(`${baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal, }) // ... rest of handling } finally { clearTimeout(timeoutId) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chain-adapters/src/solana/jito/jitoService.ts` around lines 30 - 47, The fetch in the jsonRpc function in jitoService.ts currently has no timeout; add an AbortController with a configurable timeoutMs (default e.g. 30_000) inside jsonRpc, pass controller.signal to fetch({ ..., signal }), set a timeoutId that calls controller.abort() after timeoutMs, and clearTimeout(timeoutId) in a finally block; when catching an aborted request, rethrow a clear timeout-specific Error (e.g., "Jito RPC timeout") so callers of jsonRpc / the jitoService know the request timed out.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/solanaJitoBundle.ts`:
- Around line 128-139: When handling the 'Landed' case after calling
getInflightBundleStatuses and then getBundleStatuses, guard against
bundleStatuses.value being empty: check bundleStatus (from
bundleStatuses.value[0]) and its transactions (txs) before returning a tx hash;
if no valid tx is present, do not return bundleId (which may not be a tx hash) —
instead return null (or throw a descriptive error) and optionally log the
missing bundleStatus so callers can handle upstream polling; update the logic
around variables bundleStatuses, bundleStatus, and txs to implement this safer
fallback.
---
Nitpick comments:
In `@packages/chain-adapters/src/solana/jito/jitoService.ts`:
- Around line 30-47: The fetch in the jsonRpc function in jitoService.ts
currently has no timeout; add an AbortController with a configurable timeoutMs
(default e.g. 30_000) inside jsonRpc, pass controller.signal to fetch({ ...,
signal }), set a timeoutId that calls controller.abort() after timeoutMs, and
clearTimeout(timeoutId) in a finally block; when catching an aborted request,
rethrow a clear timeout-specific Error (e.g., "Jito RPC timeout") so callers of
jsonRpc / the jitoService know the request timed out.
In `@packages/swapper/src/swappers/ButterSwap/swapperApi/getTradeQuote.ts`:
- Line 198: Extract the magic number by creating a shared constant
SOLANA_TX_SIZE_LIMIT in a new module (e.g., export const SOLANA_TX_SIZE_LIMIT =
1232 in packages/swapper/src/swappers/ButterSwap/utils/constants.ts) and replace
the local declaration in getTradeQuote.ts (the const SOLANA_TX_SIZE_LIMIT) with
an import from that module; also update any other files that rely on the same
value (for example functions in src/lib/solanaJitoBundle.ts) to import
SOLANA_TX_SIZE_LIMIT so the value is centralized and maintainable.
In `@src/lib/solanaJitoBundle.ts`:
- Around line 39-42: The current midpoint-based split (variables midpoint,
group1, group2 derived from instructions) can produce imbalanced serialized
sizes; update the split logic to be size-aware by measuring each instruction's
serialized size (or assembled transaction size) and partitioning instructions so
each group stays below the 1232-byte limit, or at minimum add a validation step
after splitting that calculates the serialized size of group1 and group2 and, if
either exceeds the limit, re-buckets instructions (moving/merging) until both
are valid; reference and modify the midpoint/group1/group2 logic in the function
that performs the split and add clear validation/error handling when a group is
too large.
In `@src/lib/tradeExecution.ts`:
- Around line 550-560: The call to execSolanaJitoBundle should defensively
handle a possibly undefined metadata.addressLookupTableAddresses; update the
return call in the oversized branch that uses getExecutableTradeStep and
execSolanaJitoBundle to pass a safe default (e.g., an empty array) for
addressLookupTableAddresses when metadata.addressLookupTableAddresses is falsy,
keeping all other arguments (instructions, from, accountNumber from
getExecutableTradeStep(tradeQuote, stepIndex), sellAssetChainId: chainId,
signTransaction) unchanged.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 67894efe-47ea-477c-8801-f44c2c1483f6
📒 Files selected for processing (15)
.env.env.development.env.testheaders/csps/chains/solana.tspackages/chain-adapters/src/solana/index.tspackages/chain-adapters/src/solana/jito/buildJitoTipInstruction.tspackages/chain-adapters/src/solana/jito/index.tspackages/chain-adapters/src/solana/jito/jitoService.tspackages/chain-adapters/src/solana/jito/types.tspackages/swapper/src/swappers/ButterSwap/swapperApi/getTradeQuote.tspackages/swapper/src/types.tssrc/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsxsrc/config.tssrc/lib/solanaJitoBundle.tssrc/lib/tradeExecution.ts
- Throw error instead of returning bundleId when Jito bundle lands but no tx hash returned - Add defensive default for addressLookupTableAddresses in Jito bundle path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Tackled the actionable one (throw instead of returning bundleId) and the defensive Re: nits
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/solanaJitoBundle.ts`:
- Around line 39-42: The split logic using midpoint (const midpoint =
Math.ceil(instructions.length / 2); group1 = instructions.slice(0, midpoint);
group2 = instructions.slice(midpoint)) can produce an empty or tip-only last
bundle when instructions.length === 1, causing _execWalletAgnostic() to record
the wrong signature in sellTxHash; fix by ensuring the bundle that is used to
set sellTxHash is the one that actually contains the swap instruction: either
change the splitting strategy to guarantee the swap instruction is included in
the final bundle (e.g., locate the swap instruction index and split so that the
swap is in group2/last bundle) or, after sending bundles, detect which bundle
contained the swap (by inspecting the instruction arrays or their sent
signatures) and select the signature returned for that bundle when setting
sellTxHash in _execWalletAgnostic(); apply the same change for the other split
sites with similar midpoint logic.
In `@src/lib/tradeExecution.ts`:
- Around line 549-561: If metadata?.isOversized is true but required Jito
prerequisites are missing (e.g., no metadata.instructions or no
signTransaction), fail fast by throwing a descriptive error instead of falling
through to the normal Solana executor; update the oversized branch (the block
using metadata?.isOversized, metadata.instructions, signTransaction,
getExecutableTradeStep, and execSolanaJitoBundle) to validate that
metadata.instructions exists and signTransaction is provided and, if not, throw
an error like "Oversized quote requires Jito instructions and signTransaction"
so the caller gets an immediate, actionable failure.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 62a02b6b-9e9c-4ec2-a6f8-4444bec87e37
📒 Files selected for processing (2)
src/lib/solanaJitoBundle.tssrc/lib/tradeExecution.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 QA Verification Report - PR #12136Test Date: 2026-03-12 06:15 UTC Test Scenario: SOL → USDC on Solana
Verification NotesThis PR was already thoroughly tested by the author with actual swap execution screenshots. My verification confirms:
Note: The Jito bundle path ( Screenshot✅ Result: VERIFIED - UI works, Bebop quotes available, ready for review |
NeOMakinG
left a comment
There was a problem hiding this comment.
✅ Code Review — LGTM
Changes: Full Jito Block Engine integration for Solana MEV protection.
New packages/chain-adapters/src/solana/jito/:
jitoService.ts- JSON-RPC client (sendTransaction, sendBundle, getBundleStatuses, getInflightBundleStatuses, getTipAccounts, getTipFloor)buildJitoTipInstruction.ts- Creates tip transfer to random tip account (10k lamports default)types.ts- Full type coverage for Jito API
Integration in SolanaChainAdapter:
broadcastTransactionJito()- Single tx submission via JitosignAndBroadcastAtomicBundle()- Bundle multiple txs for atomic execution- Tip included in last bundle tx automatically
Trade execution changes:
- executes Solana swaps via Jito when available
- Oversized tx detection (>1232 bytes) triggers bundle splitting
buildBundlableSolanaTransactions()compiles full txs with compute budget
CSP/env:
- VITE_JITO_BLOCK_ENGINE_URL added to all environments
- bundles.jito.wtf added for tip floor API
Solid MEV protection implementation. CI passes ✅
* feat: add jito bundle support for all solana swappers Add isOversized detection to Jupiter, Relay, and Across swappers. When a Solana transaction exceeds the 1232-byte limit, the trade execution layer automatically routes through Jito bundle splitting. - Jupiter: trial-serialize compiled MessageV0 to check size - Relay: same trial-serialize pattern for Solana quote items - Across: check raw base64 tx bytes before deserialization - Updated types JSDoc to reflect all-swappers scope Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: b2b affiliate tracking system (#12012) * fix: bebop solana signing + reject amm-routed quotes (#12134) * feat: chainflip lending - product docs (#12124) * feat: chainflip lending - product docs Regenerate product overview docs that were lost when gomes-bot got soft-banned. Three docs covering all lending flows with live mainnet data from RPC curls and xstate charts derived from the codebase: - Deposit to State Chain (account creation + deposit channel flow) - Borrow, Collateral & Repay (loan lifecycle + voluntary liquidation) - Supply & Withdraw (supply positions + egress to on-chain wallet) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: clarify batching sections as planned, not yet in UI Address coderabbitai review - encodeBatch primitive exists in scale.ts but UI state machines sign each operation individually. Reword both batching sections to make it clear these are aspirational patterns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: scrub personal addresses from product docs no doxxy baby Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: gomes-bot <contact@0xgom.es> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jibles <premiumjibles@gmail.com> * fix(affiliate-dashboard): add default API_URL to prevent 403 errors (#12146) * feat: portless support for stable dev server URLs (#12130) * fix: bebop solana ghost tx + malformed amm routes (#12148) * fix: chainflip lending ui fixes and polish * fix: chainflip lending ui fixes and polish - add modal headers (title + close button) to all chainflip lending modals - fix card context error (cardBody/cardFooter crash - missing card wrapper in modal) - show asset symbol next to amount input in all modals (deposit, supply, collateral, borrow, repay, egress, withdraw) - fiat/crypto toggle: properly convert value on mode switch, show $ prefix in fiat mode - move "deposit to chainflip" tab to be first (leftmost) - fix button widths to be equal 50/50 across all tabs (tooltip shouldWrapChildren span fix) - fix "sign twice" inaccurate copy in deposit confirm - multi-step flow description - allowance polling in deposit funding (guard against rpc propagation lag) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address CodeRabbit review comments - Use effectiveRefundAddress in DepositConfirm to always show refund destination in the confirm step - Replace .toString() with .toFixed() in DepositInput and SupplyInput to prevent exponential notation for small fiat conversions --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: gomes-bot <contact@0xgom.es> --------- Co-authored-by: gomes-bot <contact@0xgom.es> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Co-authored-by: Jibles <premiumjibles@gmail.com>
…undles # Conflicts: # packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/swapper/src/swappers/AcrossSwapper/utils/getTrade.ts (1)
277-311:⚠️ Potential issue | 🟠 MajorMeasure oversize from the normalized transaction, not the raw vendor payload.
Line 278 checks the size of
txBytesbefore Lines 303-306 strip compute-budget instructions, but the later execution path rebuilds the Solana transaction frominstructionsWithoutComputeBudget. That makesisOversizeddescribe a different transaction than the one we actually sign, so a quote can be pushed into the Jito path even when the normalized tx would fit under the 1232-byte limit. Please recompute the flag from the rebuilt instruction set, or move the check to the final unsigned-tx builder.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swapper/src/swappers/AcrossSwapper/utils/getTrade.ts` around lines 277 - 311, The oversize check currently measures txBytes from the vendor payload (txBytes / isOversized) before removing compute-budget instructions, which makes isOversized inconsistent with the rebuilt transaction; change the logic in getTrade.ts to defer computing isOversized until after you build the normalized transaction—i.e., after TransactionMessage.decompile produces instructionsWithoutComputeBudget—by reconstructing the normalized VersionedTransaction/TransactionMessage (using the same addressLookupTableAccounts and instructionsWithoutComputeBudget) and measuring its serialized byte length (or serializing the rebuilt VersionedTransaction) to set isOversized; update any return value that uses isOversized to reference this recomputed flag instead of the original txBytes-based value.
🧹 Nitpick comments (2)
packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts (1)
190-192: Consider logging the caught error for debugging.The silent catch returning
falseis a safe fallback, but logging the error would help diagnose issues if oversized transactions fail to be routed correctly through the Jito bundle path.🔧 Suggested improvement
} catch (e) { + console.error('[Jupiter] Failed to calculate transaction size for oversized check', e) return false }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts` around lines 190 - 192, In getTradeQuote's catch block (the one that currently just "return false"), log the caught error before returning so failures are visible; use the module's existing logger (e.g., processLogger or logger if available) to emit a descriptive error message including the error object, and fall back to console.error if no logger is present, then return false as before.packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts (1)
656-682: Add defensive fallback foraddressLookupTableAddresses.For consistency with similar code in the Jito bundle implementation (where
?? []was added per PR comments), consider adding a defensive fallback here. While the try-catch prevents crashes, an explicit empty array default makes the intent clearer and avoids relying on adapter error handling for undefined input.Suggested fix
const isOversized = await (async () => { if (!solanaInstructions?.length || !sendAddress) return false try { const adapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) const lookupTableInfos = await adapter.getAddressLookupTableAccounts( - addressLookupTableAddresses, + addressLookupTableAddresses ?? [], )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts` around lines 656 - 682, The isOversized block in getTrade.ts should defensively default addressLookupTableAddresses to an empty array before using it; change the call to adapter.getAddressLookupTableAccounts(addressLookupTableAddresses ?? []) (and ensure any subsequent mapping over lookupTableInfos still works with an empty array) so undefined inputs are handled explicitly instead of relying solely on the try/catch around adapter.getAddressLookupTableAccounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/swapper/src/swappers/AcrossSwapper/utils/getTrade.ts`:
- Around line 277-311: The oversize check currently measures txBytes from the
vendor payload (txBytes / isOversized) before removing compute-budget
instructions, which makes isOversized inconsistent with the rebuilt transaction;
change the logic in getTrade.ts to defer computing isOversized until after you
build the normalized transaction—i.e., after TransactionMessage.decompile
produces instructionsWithoutComputeBudget—by reconstructing the normalized
VersionedTransaction/TransactionMessage (using the same
addressLookupTableAccounts and instructionsWithoutComputeBudget) and measuring
its serialized byte length (or serializing the rebuilt VersionedTransaction) to
set isOversized; update any return value that uses isOversized to reference this
recomputed flag instead of the original txBytes-based value.
---
Nitpick comments:
In `@packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts`:
- Around line 190-192: In getTradeQuote's catch block (the one that currently
just "return false"), log the caught error before returning so failures are
visible; use the module's existing logger (e.g., processLogger or logger if
available) to emit a descriptive error message including the error object, and
fall back to console.error if no logger is present, then return false as before.
In `@packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts`:
- Around line 656-682: The isOversized block in getTrade.ts should defensively
default addressLookupTableAddresses to an empty array before using it; change
the call to adapter.getAddressLookupTableAccounts(addressLookupTableAddresses ??
[]) (and ensure any subsequent mapping over lookupTableInfos still works with an
empty array) so undefined inputs are handled explicitly instead of relying
solely on the try/catch around adapter.getAddressLookupTableAccounts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4200797f-4a94-44fb-b991-3434118d4fd1
📒 Files selected for processing (7)
.env.developmentpackages/swapper/src/swappers/AcrossSwapper/utils/getTrade.tspackages/swapper/src/swappers/ButterSwap/swapperApi/getTradeQuote.tspackages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.tspackages/swapper/src/swappers/RelaySwapper/utils/getTrade.tspackages/swapper/src/types.tssrc/lib/tradeExecution.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/swapper/src/types.ts
ICU data differs across node/platform versions - newer versions emit 'bn' for billions in compact notation where older ones used 'B'. Same pattern as the existing M/m normalization for millions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/hooks/useLocaleFormatter/useLocaleFormatter.test.tsx`:
- Around line 201-208: The normalizeCompact helper currently lowercases the
entire formatted string which masks unrelated casing regressions; update
normalizeCompact to only normalize the compact suffix token (e.g., match and
lower-case trailing compact tokens like M/m, B/b, bn) while leaving the rest of
the string intact so the assertions that call
normalizeCompact(result.current.number.toFiat(...)) still verify currency casing
but tolerate ICU compact-suffix differences against expected[x].
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f655ef6f-fd6d-465e-951f-4ed7cbfa7a52
📒 Files selected for processing (4)
.env.env.developmentsrc/config.tssrc/hooks/useLocaleFormatter/useLocaleFormatter.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/config.ts
Lowercasing the whole string was too broad - it would mask unrelated casing regressions in currency symbols etc. Only normalize the M/m and B/bn compact suffixes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Description
Adds Jito Block Engine integration for Solana transactions - MEV protection and atomic bundle support for oversized Butter swaps.
tl;dr Butter Solana routes with complex multi-hop paths can produce transactions exceeding the 1232-byte Solana limit. This PR adds Jito bundle support to split those into 2 atomic txs with a tip, signed sequentially and submitted as a bundle. Also lays the groundwork for using Jito across all Solana swappers (tracked in #12135).
Commit 1 - Jito infra layer:
packages/chain-adapters/src/solana/jito/- pure HTTP client for Jito Block Engine (sendTransaction, sendBundle, getTipAccounts, getBundleStatuses, getInflightBundleStatuses, tip floor)buildJitoTipInstruction- SystemProgram.transfer to a random dynamic tip account (10,000 lamports default)VITE_JITO_BLOCK_ENGINE_URLandbundles.jito.wtfto connect-src.env,.env.development,.env.test,src/config.tsCommit 2 - Butter Solana bundle splitting:
src/lib/solanaJitoBundle.ts- splits instructions into 2 groups, builds unsigned txs with tip in last, signs sequentially, submits as atomic Jito bundle, polls for landingtradeExecution.ts- routes oversized Butter Solana txs through Jito bundle pathuseTradeExecution.tsx- passessignTransactioncallback for sign-only flow (no broadcast, Jito handles submission)No jito-js-rpc package - pure fetch/HTTP, zero new dependencies.
Issue (if applicable)
Closes #11858
Follow-up work: #10765, #12135
Risk
Medium - new Solana transaction submission path, but only triggers for oversized Butter txs (> 1232 bytes). Normal-sized Solana txs are completely unaffected. Jito bundle path is additive, not replacing existing broadcast.
Butter Solana swaps with complex routes that exceed the 1232-byte tx limit. All other Solana swappers (Jupiter, Bebop, Relay) are unaffected.
Testing
Tested Butter Solana swaps on localhost with native wallet (teest):
USDC -> SOL via ButterSwap ✅
SOL -> USDC via ButterSwap ✅
Available quotes showing Butter option:
Note: The Jito bundle path (
execSolanaJitoBundle) only triggers for oversized Butter transactions (>1232 bytes). Normal same-chain swaps go through the standard single-tx path. The bundle splitting is a safety net for complex cross-chain routes with many instructions.Ops testing on release: Test a Butter swap with Phantom wallet on Solana and ensure there's no security warning / simulation failure. The Jito bundle path bypasses Phantom's simulation (which was previously rejecting oversized txs), so this should resolve Phantom-related Butter Solana failures.
Screenshots (if applicable)
☝🏽
Summary by CodeRabbit
New Features
Enhancements
Configuration
Tests