Skip to content

Fix Solana execute crash on Relay's raw-instruction bridge quotes - #513

Draft
kome12 wants to merge 3 commits into
mainfrom
fix/solana-relay-bridge-signing
Draft

Fix Solana execute crash on Relay's raw-instruction bridge quotes#513
kome12 wants to merge 3 commits into
mainfrom
fix/solana-relay-bridge-signing

Conversation

@kome12

@kome12 kome12 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • trade execute normalized Solana quote transactions into two known shapes — Jupiter (base64 string) and OKX ({data: base58}) — but Relay's Solana-source bridge quotes return a third, uncompiled shape ({instructions, addressLookupTableAddresses}). That shape fell through and crashed on Buffer.from(object, 'base64'), so any Solana→EVM bridge failed whenever Relay won the aggregator price race, even though trade quote reported success.
  • Adds a compiler that turns Relay's raw instructions into a signable VersionedTransaction, keeping all accounts static (address lookup tables are a size optimization, not a correctness requirement — skipped for now, with a clear error if a future quote is ever too large to fit without them).
  • No changes to the signing paths themselves — this is purely a normalization step, so Jupiter/OKX behavior is unchanged.

Test plan

  • npm test — all existing tests pass, plus new unit tests covering: Jupiter/OKX passthrough (no RPC call), compiling a real captured Relay-shaped quote into a valid, signable transaction (byte-level assertions), an oversized-transaction error path, and a missing-signer error path.
  • npm run lint
  • Live e2e: executed a real Solana→Base bridge via a forced Relay quote (--aggregator relay). Confirmed the exact previously-crashing transaction shape, and it signed + broadcast successfully. Verified the destination balance increased by the quoted amount (bridge completed).

🤖 Generated with Claude Code

Relay's Solana-source bridge quotes return uncompiled
{instructions, addressLookupTableAddresses} instead of a ready-to-sign
transaction, unlike Jupiter (base64) and OKX ({data: base58}). This shape
fell through the existing normalization and crashed on Buffer.from(object,
'base64'), so `trade execute` failed for any Solana bridge where Relay won
the aggregator price race.

Compile the raw instructions into a static-keys VersionedTransaction
(address lookup tables are a size optimization, not a correctness
requirement, so skipping them is valid as long as the transaction fits
Solana's packet limit — this throws instead of silently building an
oversized one).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kome12 kome12 self-assigned this Aug 21, 2026
@nansen-pr-reviewer

nansen-pr-reviewer Bot commented Aug 21, 2026

Copy link
Copy Markdown

pr-reviewer Summary for #e11c234

No issues found

The code review completed successfully with no findings.

Review effort: 3/5 (Moderate)

Summary

This PR correctly fixes a crash in trade execute for Solana→EVM bridge quotes where Relay returns raw uncompiled instructions instead of a ready-to-sign transaction. The implementation is well-structured and the security posture is sound.

Overall assessment: The fix is targeted and safe. normalizeSolanaTransaction cleanly wraps the three known shapes without touching Jupiter/OKX paths. The new compileRawSolanaTransaction correctly reuses the existing buildMessageV0 builder (already battle-tested by x402 payments), and the preflight guards — signer identity check, multi-signer rejection, size check before the RPC round-trip — are exactly the right defenses for a live-funds signing path. The changeset is correctly classified as patch. Test coverage is thorough: passthrough tests confirm no regression for Jupiter/OKX, the byte-level round-trip test verifies the compiled transaction is actually signable and the resulting signature validates, and all error branches are covered. No issues found.


Token usage: 1,917 input, 2,882 output, 382,716 cache read, 38,457 cache write | Usage Guide

New pushes are reviewed automatically with a 10-minute cooldown between reviews. To request a review at any time, comment @nansen-pr-reviewer re-review.

kome12 and others added 2 commits August 21, 2026 15:35
…structions

Address review feedback: compileRawSolanaTransaction picked its fee-payer by
trusting whichever account the quote itself marked isSigner:true, with no
check against the wallet actually about to sign, and didn't reject a raw
instruction set requiring more than one signature — either would silently
sign the wrong account (or leave a signature slot empty) and fail on-chain
with an opaque error instead of a clear pre-flight one.

Also: validate the signer/signature-count before spending a network round
trip on a real blockhash (recentBlockhash is fixed-size regardless of value,
so a placeholder is exact for that preflight), decode instruction data with
an explicit optional-0x-prefix strip instead of raw Buffer.from(hex) (bare,
non-prefixed hex — the real shape Relay returns), and fail with an
actionable message instead of a raw TypeError when an instruction is
missing its accounts list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
decodeInstructionData called .startsWith unconditionally, so a Relay
instruction omitting `data` (valid at the protocol level — some
instructions legitimately carry none) threw a raw TypeError instead of
compiling correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@Codier Codier left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we're still missing one important safety check here.

This now compiles and signs whatever program IDs, accounts, and instruction data Relay returns. We verify that the connected wallet is the signer and that the transaction fits, but we don't verify what the transaction actually does. If the Relay/trading response is compromised, we could still sign an arbitrary SOL/SPL transfer, authority change, or very high fee.

Can we validate the compiled transaction against the original trade intent before signing — expected token, amount, recipient and programs; no unrelated authority changes; and capped fees? Simulation would be a useful extra guard, but I don't think it should be the only check.

The focused tests and CI are green. Reviewed at e11c234ee2711e963667e688d045812861d6fcf4.

@Codier

Codier commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

To help move this forward, here is a specific remediation plan for the Solana raw-instruction validation:

  1. Instruction Decoding: We need to decode the data for instructions targeting the System Program and Token Program to extract the amount and destination.
  2. Intent Binding: The sum of outflows in these instructions must be verified against request.amount and request.maxInputAmount.
  3. Program Allowlist: We should only allow known programs (System, Token, Token-2022, Memo, ComputeBudget, and the specific Relay bridge program). Any other programId should be rejected.
  4. Signer Audit: Verify that the wallet is only a signer for the specific instructions we expect (e.g., the bridge deposit).

This would bring the Solana raw-instruction path up to the same security standard we have for EVM trades in src/trade-validation.js.

@kome12

kome12 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

To help move this forward, here is a specific remediation plan for the Solana raw-instruction validation:

  1. Instruction Decoding: We need to decode the data for instructions targeting the System Program and Token Program to extract the amount and destination.
  2. Intent Binding: The sum of outflows in these instructions must be verified against request.amount and request.maxInputAmount.
  3. Program Allowlist: We should only allow known programs (System, Token, Token-2022, Memo, ComputeBudget, and the specific Relay bridge program). Any other programId should be rejected.
  4. Signer Audit: Verify that the wallet is only a signer for the specific instructions we expect (e.g., the bridge deposit).

This would bring the Solana raw-instruction path up to the same security standard we have for EVM trades in src/trade-validation.js.

Ah, sorry, I should have replied here. I have PRs to work on hardening the Solana side so I'll look at this PR again once those are merged.

@Codier

Codier commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Sounds good, Ko. I see #512 and #514 are in flight — those look like the right building blocks for this. I'll keep this on my radar and re-review once you've had a chance to pull those patterns in here.

@kome12

kome12 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Sounds good, Ko. I see #512 and #514 are in flight — those look like the right building blocks for this. I'll keep this on my radar and re-review once you've had a chance to pull those patterns in here.

There will probably be another PR in addition to the above PRs. I'll ping here again once this is ready for review. In the meantime, let me switch this back to draft.

@kome12
kome12 marked this pull request as draft August 25, 2026 05:53
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.

2 participants