}
@@ -275,10 +275,9 @@ export function Example() {
Swap successful! Check your metamask to see updated token balances
+ target='_blank'>
Transaction
>
diff --git a/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts b/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts
index b7b21f19bb..3fee4e5d52 100644
--- a/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts
+++ b/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts
@@ -42,6 +42,7 @@ import {
expectToBeString,
decodeMulticallExactInputWithoutFees,
buildBlock,
+ refundETHFunctionSignature,
TEST_MAX_PRIORITY_FEE_PER_GAS,
TEST_BASE_FEE,
} from './test/utils';
@@ -433,6 +434,44 @@ describe('getUnsignedSwapTxFromAmountIn', () => {
expect(result.approval).toBeNull();
});
+
+ it('should include a call to refundETH as the final step of the multicall calldata', async () => {
+ mockRouterImplementation({
+ pools: [createPool(nativeTokenService.wrappedToken, FUN_TEST_TOKEN)],
+ });
+
+ const swapRouterInterface = SwapRouter.INTERFACE;
+ const paymentsInterface = PaymentsExtended.INTERFACE;
+ const exchange = new Exchange(TEST_DEX_CONFIGURATION);
+
+ // Sell 100 native tokens for X amount of FUN where the exchange rate is 1 token-in : 10 token-out
+ // Route is WIMX > FUN
+ const { swap } = await exchange.getUnsignedSwapTxFromAmountIn(
+ TEST_FROM_ADDRESS,
+ 'native',
+ FUN_TEST_TOKEN.address,
+ newAmountFromString('100', nativeTokenService.nativeToken).value,
+ 3, // 3 % slippage
+ );
+
+ expectToBeDefined(swap.transaction.data);
+ expectToBeDefined(swap.transaction.value);
+ const calldata = swap.transaction.data.toString();
+
+ const topLevelParams = swapRouterInterface.decodeFunctionData('multicall(uint256,bytes[])', calldata);
+
+ expect(topLevelParams.data.length).toBe(2); // expect that there are two calls in the multicall
+ const swapTransactionCalldata = topLevelParams.data[0];
+ const refundETHTransactionCalldata = topLevelParams.data[1];
+
+ expectToBeString(swapTransactionCalldata);
+ expectToBeString(refundETHTransactionCalldata);
+
+ const decodedRefundEthTx = paymentsInterface.decodeFunctionData('refundETH', refundETHTransactionCalldata);
+
+ expect(topLevelParams.data[1]).toEqual(refundETHFunctionSignature);
+ expect(decodedRefundEthTx.length).toEqual(0); // expect that the refundETH call has no parameters
+ });
});
describe('with a single pool and a native token out', () => {
@@ -583,6 +622,49 @@ describe('getUnsignedSwapTxFromAmountIn', () => {
});
describe('with multiple pools', () => {
+ describe('with a native token in', () => {
+ it('should include a call to refundETH as the final step of the multicall calldata', async () => {
+ mockRouterImplementation({
+ pools: [
+ createPool(nativeTokenService.wrappedToken, USDC_TEST_TOKEN),
+ createPool(USDC_TEST_TOKEN, FUN_TEST_TOKEN),
+ ],
+ });
+
+ const swapRouterInterface = SwapRouter.INTERFACE;
+ const paymentsInterface = PaymentsExtended.INTERFACE;
+ const exchange = new Exchange(TEST_DEX_CONFIGURATION);
+
+ // Sell 100 native tokens for X amount of FUN where the exchange rate is 1 token-in : 10 token-out
+ // Route is WIMX > USDC > FUN
+ const { swap } = await exchange.getUnsignedSwapTxFromAmountIn(
+ TEST_FROM_ADDRESS,
+ 'native',
+ FUN_TEST_TOKEN.address,
+ newAmountFromString('100', nativeTokenService.nativeToken).value,
+ 3, // 3 % slippage
+ );
+
+ expectToBeDefined(swap.transaction.data);
+ expectToBeDefined(swap.transaction.value);
+ const calldata = swap.transaction.data.toString();
+
+ const topLevelParams = swapRouterInterface.decodeFunctionData('multicall(uint256,bytes[])', calldata);
+
+ expect(topLevelParams.data.length).toBe(2); // expect that there are two calls in the multicall
+ const swapTransactionCalldata = topLevelParams.data[0];
+ const refundETHTransactionCalldata = topLevelParams.data[1];
+
+ expectToBeString(swapTransactionCalldata);
+ expectToBeString(refundETHTransactionCalldata);
+
+ const decodedRefundEthTx = paymentsInterface.decodeFunctionData('refundETH', refundETHTransactionCalldata);
+
+ expect(topLevelParams.data[1]).toEqual(refundETHFunctionSignature);
+ expect(decodedRefundEthTx.length).toEqual(0); // expect that the refundETH call has no parameters
+ });
+ });
+
describe('with a native token out', () => {
it('should specify the Router contract as the recipient of the swap function call', async () => {
mockRouterImplementation({
diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts
index bcece56934..848eca9df9 100644
--- a/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts
+++ b/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts
@@ -67,9 +67,12 @@ function buildSinglePoolSwap(
);
}
- const shouldRefundNativeTokens = trade.tradeType === Uniswap.TradeType.EXACT_OUTPUT && isNative(tokenIn);
+ const shouldRefundNativeTokens = isNative(tokenIn);
if (shouldRefundNativeTokens) {
- // Refund ETH if the input token is native and the swap is exact output
+ // Refund ETH if the input token is native.
+ // In some cases, the user may have specified an input amount that is greater than what
+ // the liqudiity of the pool can provide.
+ // To account for this case, always call `refundETH` to refund any excess native tokens.
calldatas.push(paymentsContract.encodeFunctionData('refundETH'));
}
@@ -182,9 +185,12 @@ function buildMultiPoolSwap(
);
}
- const shouldRefundNativeTokens = trade.tradeType === Uniswap.TradeType.EXACT_OUTPUT && isNative(tokenIn);
+ const shouldRefundNativeTokens = isNative(tokenIn);
if (shouldRefundNativeTokens) {
- // Refund ETH if the input token is native and the swap is exact output
+ // Refund ETH if the input token is native.
+ // In some cases, the user may have specified an input amount that is greater than what
+ // the liqudiity of the pool can provide.
+ // To account for this case, always call `refundETH` to refund any excess native tokens.
calldatas.push(paymentsContract.encodeFunctionData('refundETH'));
}