Skip to content

Commit

Permalink
refine min output and leftover tokens handling
Browse files Browse the repository at this point in the history
  • Loading branch information
dohaki committed Nov 11, 2024
1 parent cf9f1e6 commit 789e553
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 18 deletions.
1 change: 1 addition & 0 deletions api/_dexes/cross-swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export async function getCrossSwapQuotesForMinOutputB2B(crossSwap: CrossSwap) {
message: "0x",
});
return {
crossSwap,
destinationSwapQuote: undefined,
bridgeQuote,
originSwapQuote: undefined,
Expand Down
6 changes: 6 additions & 0 deletions api/_dexes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,16 @@ export type SwapQuote = {
data: string;
value: string;
};
tokenIn: Token;
tokenOut: Token;
};

export type CrossSwapQuotes = {
crossSwap: CrossSwap;
bridgeQuote: {
message?: string;
inputToken: Token;
outputToken: Token;
inputAmount: BigNumber;
outputAmount: BigNumber;
minOutputAmount: BigNumber;
Expand Down
89 changes: 83 additions & 6 deletions api/_dexes/uniswap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
Token as AcrossToken,
Swap,
CrossSwap,
SwapQuote,
CrossSwapQuotes,
} from "./types";
import { getSwapAndBridgeAddress, NoSwapRouteError } from "./utils";
import { AMOUNT_TYPE } from "./cross-swap";
Expand Down Expand Up @@ -163,9 +165,35 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A(
}),
});

// 3. Re-fetch destination swap quote with updated input amount and EXACT_INPUT type.
// This prevents leftover tokens in the MulticallHandler contract.
const updatedDestinationSwapQuote = await getUniswapQuote({
chainId: destinationSwapChainId,
tokenIn: bridgeableOutputToken,
tokenOut: crossSwap.outputToken,
amount: bridgeQuote.outputAmount.toString(),
recipient: crossSwap.recipient,
slippageTolerance: crossSwap.slippageTolerance,
type: AMOUNT_TYPE.EXACT_INPUT,
});

// 4. Rebuild message
bridgeQuote.message = buildMulticallHandlerMessage({
// @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress`
fallbackRecipient: crossSwap.recipient,
actions: [
{
target: updatedDestinationSwapQuote.swapTx.to,
callData: updatedDestinationSwapQuote.swapTx.data,
value: updatedDestinationSwapQuote.swapTx.value,
},
],
});

return {
crossSwap,
bridgeQuote,
destinationSwapQuote,
destinationSwapQuote: updatedDestinationSwapQuote,
originSwapQuote: undefined,
};
}
Expand Down Expand Up @@ -230,6 +258,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B(
});

return {
crossSwap,
bridgeQuote,
destinationSwapQuote: undefined,
originSwapQuote,
Expand Down Expand Up @@ -277,15 +306,26 @@ export async function getBestUniswapCrossSwapQuotesForMinOutputA2A(
);
}

const crossSwapQuotes = await Promise.all(
const crossSwapQuotesSettledResults = await Promise.allSettled(
bridgeRoutesToCompare.map((bridgeRoute) =>
getUniswapCrossSwapQuotesForMinOutputA2A(crossSwap, bridgeRoute)
)
);
const crossSwapQuotes = crossSwapQuotesSettledResults
.filter((res) => res.status === "fulfilled")
.map((res) => (res as PromiseFulfilledResult<CrossSwapQuotes>).value);

if (crossSwapQuotes.length === 0) {
console.log("crossSwapQuotesSettledResults", crossSwapQuotesSettledResults);
throw new Error(
`No successful bridge quotes found for ${originSwapChainId} -> ${destinationSwapChainId}`
);
}

// Compare quotes by lowest input amount
const bestCrossSwapQuote = crossSwapQuotes.reduce((prev, curr) =>
prev.originSwapQuote.maximumAmountIn.lt(
curr.originSwapQuote.maximumAmountIn
prev.originSwapQuote!.maximumAmountIn.lt(
curr.originSwapQuote!.maximumAmountIn
)
? prev
: curr
Expand Down Expand Up @@ -375,6 +415,31 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A(
}),
});

// 3. Re-fetch destination swap quote with updated input amount and EXACT_INPUT type.
// This prevents leftover tokens in the MulticallHandler contract.
const updatedDestinationSwapQuote = await getUniswapQuote({
chainId: destinationSwapChainId,
tokenIn: bridgeableOutputToken,
tokenOut: crossSwap.outputToken,
amount: bridgeQuote.outputAmount.toString(),
recipient: crossSwap.recipient,
slippageTolerance: crossSwap.slippageTolerance,
type: AMOUNT_TYPE.EXACT_INPUT,
});

// 4. Rebuild message
bridgeQuote.message = buildMulticallHandlerMessage({
// @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress`
fallbackRecipient: crossSwap.recipient,
actions: [
{
target: updatedDestinationSwapQuote.swapTx.to,
callData: updatedDestinationSwapQuote.swapTx.data,
value: updatedDestinationSwapQuote.swapTx.value,
},
],
});

// 3. Get origin swap quote for any input token -> bridgeable input token
const originSwapQuote = await getUniswapQuote({
chainId: originSwapChainId,
Expand All @@ -387,13 +452,14 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A(
});

return {
destinationSwapQuote,
crossSwap,
destinationSwapQuote: updatedDestinationSwapQuote,
bridgeQuote,
originSwapQuote,
};
}

export async function getUniswapQuote(swap: Swap) {
export async function getUniswapQuote(swap: Swap): Promise<SwapQuote> {
const { router, options } = getSwapRouterAndOptions(swap);

const amountCurrency =
Expand Down Expand Up @@ -458,6 +524,17 @@ export async function getUniswapQuote(swap: Swap) {
},
};

console.log("swapQuote", {
type: swap.type,
tokenIn: swapQuote.tokenIn.symbol,
tokenOut: swapQuote.tokenOut.symbol,
chainId: swap.chainId,
maximumAmountIn: swapQuote.maximumAmountIn.toString(),
minAmountOut: swapQuote.minAmountOut.toString(),
expectedAmountOut: swapQuote.expectedAmountOut.toString(),
expectedAmountIn: swapQuote.expectedAmountIn.toString(),
});

return swapQuote;
}

Expand Down
13 changes: 12 additions & 1 deletion api/_dexes/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ENABLED_ROUTES } from "../_utils";
import { UniversalSwapAndBridge__factory } from "@across-protocol/contracts/dist/typechain";

import { ENABLED_ROUTES, getProvider } from "../_utils";

export class UnsupportedDex extends Error {
constructor(dex: string) {
Expand Down Expand Up @@ -44,6 +46,15 @@ export function getSwapAndBridgeAddress(dex: string, chainId: number) {
return address;
}

export function getSwapAndBridge(dex: string, chainId: number) {
const swapAndBridgeAddress = getSwapAndBridgeAddress(dex, chainId);

return UniversalSwapAndBridge__factory.connect(
swapAndBridgeAddress,
getProvider(chainId)
);
}

function _isDexSupported(
dex: string
): dex is keyof typeof ENABLED_ROUTES.swapAndBridgeAddresses {
Expand Down
14 changes: 11 additions & 3 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -869,15 +869,20 @@ export async function getBridgeQuoteForMinOutput(params: {
outputToken: params.outputToken.address,
originChainId: params.inputToken.chainId,
destinationChainId: params.outputToken.chainId,
skipAmountLimit: true,
skipAmountLimit: false,
recipient: params.recipient,
message: params.message,
};

// 1. Use 1 bps as indicative relayer fee pct
// 1. Use the suggested fees to get an indicative quote with
// input amount equal to minOutputAmount
let tries = 0;
let adjustedInputAmount = params.minOutputAmount;
let adjustmentPct = utils.parseEther("0.001").toString();
let indicativeQuote = await getSuggestedFees({
...baseParams,
amount: adjustedInputAmount.toString(),
});
let adjustmentPct = indicativeQuote.totalRelayFee.pct;
let finalQuote: Awaited<ReturnType<typeof getSuggestedFees>> | undefined =
undefined;

Expand All @@ -900,6 +905,7 @@ export async function getBridgeQuoteForMinOutput(params: {
finalQuote = adjustedQuote;
break;
} else {
adjustmentPct = adjustedQuote.totalRelayFee.pct;
tries++;
}
}
Expand All @@ -914,6 +920,8 @@ export async function getBridgeQuoteForMinOutput(params: {
minOutputAmount: params.minOutputAmount,
suggestedFees: finalQuote,
message: params.message,
inputToken: params.inputToken,
outputToken: params.outputToken,
};
}

Expand Down
14 changes: 6 additions & 8 deletions scripts/tests/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import axios from "axios";
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants";
import { ethers } from "ethers";

import { resolveVercelEndpoint } from "../../api/_utils";

/**
* Manual test script for the swap API. Should be converted to a proper test suite.
*/
Expand All @@ -22,7 +20,7 @@ const MIN_OUTPUT_CASES = [
},
// B2A
{
minOutputAmount: ethers.utils.parseUnits("1", 18).toString(),
minOutputAmount: ethers.utils.parseUnits("0.001", 18).toString(),
inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE],
originChainId: CHAIN_IDs.BASE,
outputToken: TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.ARBITRUM],
Expand All @@ -32,8 +30,8 @@ const MIN_OUTPUT_CASES = [
},
// A2B
{
minOutputAmount: ethers.utils.parseUnits("100", 6).toString(),
inputToken: "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed", // DEGEN
minOutputAmount: ethers.utils.parseUnits("10", 6).toString(),
inputToken: TOKEN_SYMBOLS_MAP.USDbC.addresses[CHAIN_IDs.BASE],
originChainId: CHAIN_IDs.BASE,
outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.ARBITRUM],
destinationChainId: CHAIN_IDs.ARBITRUM,
Expand All @@ -42,8 +40,8 @@ const MIN_OUTPUT_CASES = [
},
// A2A
{
minOutputAmount: ethers.utils.parseUnits("100", 18).toString(),
inputToken: "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed", // DEGEN
minOutputAmount: ethers.utils.parseUnits("1", 18).toString(),
inputToken: TOKEN_SYMBOLS_MAP.USDbC.addresses[CHAIN_IDs.BASE],
originChainId: CHAIN_IDs.BASE,
outputToken: "0x74885b4D524d497261259B38900f54e6dbAd2210", // APE Coin
destinationChainId: CHAIN_IDs.ARBITRUM,
Expand All @@ -54,7 +52,7 @@ const MIN_OUTPUT_CASES = [

async function swap() {
for (const testCase of MIN_OUTPUT_CASES) {
const response = await axios.get(`${resolveVercelEndpoint()}/api/swap`, {
const response = await axios.get(`http://localhost:3000/api/swap`, {
params: testCase,
});
console.log(response.data);
Expand Down

0 comments on commit 789e553

Please sign in to comment.