Skip to content

feat(fx): validate swap-quote sell + buy tokens in validatePoolFlow - #54

Merged
zy0n merged 1 commit into
Railgun-Community:mainfrom
Squabble9:fx-swap-quote-shape-check
May 29, 2026
Merged

feat(fx): validate swap-quote sell + buy tokens in validatePoolFlow#54
zy0n merged 1 commit into
Railgun-Community:mainfrom
Squabble9:fx-swap-quote-shape-check

Conversation

@Squabble9

Copy link
Copy Markdown
Contributor

Follow-up to #53 (item #3 from @zy0n's review).

Summary

validatePoolFlow previously checked only that a swapQuote was present when a named pool required one (e.g., wstETH-Long). It did not validate the quote's contents — so a caller could pass a quote that swaps the wrong tokens, and the failure surfaced later as a confusing on-chain gas-estimate revert.

This PR adds an inner shape check: when a swapQuote is provided, the helper verifies that its sell and buy tokens match what the recipe will actually execute. The check is direction-aware:

  • 'deposit' (open / topup / topup-and-borrow): sellTokenAddress === WETH, buyERC20Amount.tokenAddress === pool.collateralToken
  • 'withdraw' (close): sellTokenAddress === pool.collateralToken, buyERC20Amount.tokenAddress === WETH

The direction asymmetry surfaced during implementation: open/topup recipes hardcode inputToken = FX_ADDRESSES.WETH and target pool.collateralToken, while close-recipe reverses (withdraws collateral, swaps to WETH). A single direction-agnostic check would have rejected every valid close-recipe quote, so validatePoolFlow takes a third direction argument.

Both layers — presence AND shape — run on both named-pool and custom-pool refs (addresses @zy0n's "both of those logic branches" comment). Compares are case-insensitive.

Behavior change

Quotes that were previously accepted and later reverted on-chain now fail fast at recipe construction time with a clear fxmint: error message. Correctly-formed quotes continue to work unchanged. validatePoolFlow gains a required third arg (direction: SwapDirection), updated at all four internal call sites.

Test plan

  • 9 new cases in the existing describe('validatePoolFlow', …) block: deposit + withdraw directions, named + custom pool refs, sell-side mismatch, buy-side mismatch, and case-insensitivity.
  • Existing presence-check tests updated to pass 'deposit' as the new third arg; behavior unchanged.
  • Fixture sweep: fakeSwapQuote constants across the four recipe test files now include sellTokenAddress (previously cast as unknown as SwapQuoteData to bypass the type check; the new shape check would otherwise throw TypeError on undefined).
  • npm run lint — no new errors (pre-existing baseline unchanged).
  • npm test — 143 passing (baseline +9), 19 pending, 10 failing. The 10 failures are pre-existing environmental (missing ZeroXConfig.API_KEY + RPC 503s on eth.llamarpc.com).

🤖 Generated with Claude Code

Follow-up to PR Railgun-Community#53 (item Railgun-Community#3 from @zy0n's review).

validatePoolFlow now performs a direction-aware shape check on the
provided swapQuote, in addition to the existing presence check. The
shape check runs on BOTH named-pool and custom-pool refs (zy0n's
"both of those logic branches" comment):

  'deposit'  (open / topup / topup-and-borrow):
    sellTokenAddress           must equal WETH
    buyERC20Amount.tokenAddress must equal pool.collateralToken

  'withdraw' (close):
    sellTokenAddress           must equal pool.collateralToken
    buyERC20Amount.tokenAddress must equal WETH

The direction asymmetry surfaced during implementation: open/topup
recipes hardcode `inputToken = FX_ADDRESSES.WETH` and target
pool.collateralToken, while close-recipe reverses (withdraws collateral,
swaps to WETH). A single direction-agnostic check would reject every
valid close-recipe quote, so validatePoolFlow takes a third arg.

Compares are case-insensitive. Mismatches raise a clear `fxmint:`
error at recipe construction time, replacing what previously
surfaced as a confusing on-chain gas-estimate revert.

Tests: 9 new cases in the validatePoolFlow describe block, covering
both directions (deposit + withdraw), both branches (named + custom),
and the checksum-vs-lowercase case-insensitivity path. Existing
presence-check tests pass unchanged after adding the direction arg.
Fixture sweep across the four recipe test files added the previously-
missing sellTokenAddress (the fixtures were cast `as unknown as
SwapQuoteData` to bypass the type check; the new shape check would
otherwise throw TypeError on undefined).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zy0n
zy0n merged commit eba7a81 into Railgun-Community:main May 29, 2026
2 of 4 checks passed
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