Skip to content

fix(v3-sdk): refund unspent ETH on native exact-input swaps - #698

Open
gomesalexandre wants to merge 2 commits into
Uniswap:mainfrom
gomesalexandre:fix_v3_swaprouter_native_exactin_refund
Open

fix(v3-sdk): refund unspent ETH on native exact-input swaps#698
gomesalexandre wants to merge 2 commits into
Uniswap:mainfrom
gomesalexandre:fix_v3_swaprouter_native_exactin_refund

Conversation

@gomesalexandre

Copy link
Copy Markdown

closes #216

what

SwapRouter.swapCallParameters (v3-sdk) only appends a refundETH sweep for exact-output trades. A native-input exact-input swap can leave ETH stuck in the router.

why

For a native-input trade msg.value is set to the input amount. For exact-input that is the exact amountIn, and exactInputSingle is encoded with sqrtPriceLimitX96: options.sqrtPriceLimitX96 ?? 0. If a caller sets a price limit and the swap stops early against it, less than amountIn is consumed and the remainder sits in the router until some later exact-output swap happens to sweep it.

how

Refund whenever the input currency is native, not only for exact-output:

- const mustRefund = sampleTrade.inputAmount.currency.isNative && sampleTrade.tradeType === TradeType.EXACT_OUTPUT
+ const mustRefund = inputIsNative

refundETH is a no-op when the router holds no ETH, so exact-input swaps without a price limit are unaffected beyond a negligible extra call. This matches the periphery's own always-sweep pattern.

tests

  • added a focused test: a native exact-input swap with sqrtPriceLimitX96 set now produces multicall(exactInputSingle, refundETH) (refundETH selector 0x12210e8a)
  • updated the three existing native exact-input calldata expectations to include the sweep
  • full v3-sdk suite: 341 pass; tsc, eslint (--max-warnings 0) and prettier clean. The four native-exact-input tests fail against the previous implementation.

`SwapRouter.swapCallParameters` only appended a `refundETH` call for
exact-output trades. But a native-input exact-input swap sends
`msg.value` equal to the exact input amount, and `exactInputSingle` can
stop early against `sqrtPriceLimitX96` and consume less than that, so the
remainder gets stuck in the router until some later exact-output swap
happens to sweep it.

Refund whenever the input currency is native, matching the periphery's
own "always sweep" pattern. `refundETH` is a no-op when the router holds
no ETH, so exact-input swaps without a price limit are unaffected beyond
a negligible extra call.

closes Uniswap#216
@gomesalexandre
gomesalexandre requested a review from a team as a code owner August 18, 2026 08:56
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.

ETH Refund Logic Bug in V3 SDK SwapRouter

1 participant