Skip to content

Commit

Permalink
feat: add leftover handling type
Browse files Browse the repository at this point in the history
  • Loading branch information
dohaki committed Nov 18, 2024
1 parent 589dc98 commit 8ac1e95
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 25 deletions.
7 changes: 7 additions & 0 deletions api/_dexes/cross-swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export type CrossSwapType =

export type AmountType = (typeof AMOUNT_TYPE)[keyof typeof AMOUNT_TYPE];

export type LeftoverType = (typeof LEFTOVER_TYPE)[keyof typeof LEFTOVER_TYPE];

export const AMOUNT_TYPE = {
EXACT_INPUT: "exactInput",
MIN_OUTPUT: "minOutput",
Expand All @@ -36,6 +38,11 @@ export const CROSS_SWAP_TYPE = {
ANY_TO_ANY: "anyToAny",
} as const;

export const LEFTOVER_TYPE = {
OUTPUT_TOKEN: "outputToken",
BRIDGEABLE_TOKEN: "bridgeableToken",
} as const;

export const PREFERRED_BRIDGE_TOKENS = ["WETH", "USDC"];

export async function getCrossSwapQuotes(
Expand Down
4 changes: 3 additions & 1 deletion api/_dexes/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber } from "ethers";
import { getSuggestedFees } from "../_utils";
import { AmountType, CrossSwapType } from "./cross-swap";
import { AmountType, CrossSwapType, LeftoverType } from "./cross-swap";

export type { AmountType, CrossSwapType };

Expand All @@ -19,6 +19,7 @@ export type Swap = {
recipient: string;
slippageTolerance: number;
type: AmountType;
leftoverType?: LeftoverType;
};

export type CrossSwap = {
Expand All @@ -29,6 +30,7 @@ export type CrossSwap = {
recipient: string;
slippageTolerance: number;
type: AmountType;
leftoverType?: LeftoverType;
refundOnOrigin: boolean;
refundAddress?: string;
};
Expand Down
105 changes: 81 additions & 24 deletions api/_dexes/uniswap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
SwapQuote,
} from "./types";
import { getSwapAndBridgeAddress, NoSwapRouteError } from "./utils";
import { LEFTOVER_TYPE } from "./cross-swap";

// Taken from here: https://docs.uniswap.org/contracts/v3/reference/deployments/
export const SWAP_ROUTER_02_ADDRESS = {
Expand Down Expand Up @@ -136,19 +137,41 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A(
chainId: destinationSwapChainId,
};

// 1. Get destination swap quote for bridgeable output token -> any token
// with exact output amount.
const destinationSwapQuote = await getUniswapQuote(
const destinationSwap = {
chainId: destinationSwapChainId,
tokenIn: bridgeableOutputToken,
tokenOut: crossSwap.outputToken,
recipient: crossSwap.recipient,
slippageTolerance: crossSwap.slippageTolerance,
};
// 1.1. Get destination swap quote for bridgeable output token -> any token
// with exact output amount.
let destinationSwapQuote = await getUniswapQuote(
{
chainId: destinationSwapChainId,
tokenIn: bridgeableOutputToken,
tokenOut: crossSwap.outputToken,
...destinationSwap,
amount: crossSwap.amount.toString(),
recipient: crossSwap.recipient,
slippageTolerance: crossSwap.slippageTolerance,
},
TradeType.EXACT_OUTPUT
);
// 1.2. Re-fetch destination swap quote with exact input amount if leftover tokens
// should be sent as output tokens instead of bridgeable output tokens.
if (crossSwap.leftoverType === LEFTOVER_TYPE.OUTPUT_TOKEN) {
destinationSwapQuote = await getUniswapQuote(
{
...destinationSwap,
amount: destinationSwapQuote.maximumAmountIn
.mul(
ethers.utils.parseEther(
(1 + Number(crossSwap.slippageTolerance) / 100).toString()
)
)
.div(utils.fixedPointAdjustment)
.toString(),
},
TradeType.EXACT_INPUT
);
assertMinOutputAmount(destinationSwapQuote.minAmountOut, crossSwap.amount);
}

// 2. Get bridge quote for bridgeable input token -> bridgeable output token
const bridgeQuote = await getBridgeQuoteForMinOutput({
Expand Down Expand Up @@ -368,20 +391,49 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A(
symbol: _bridgeableOutputToken.symbol,
chainId: bridgeRoute.toChain,
};
const originSwap = {
chainId: originSwapChainId,
tokenIn: crossSwap.inputToken,
tokenOut: bridgeableInputToken,
recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId),
slippageTolerance: crossSwap.slippageTolerance,
};
const destinationSwap = {
chainId: destinationSwapChainId,
tokenIn: bridgeableOutputToken,
tokenOut: crossSwap.outputToken,
recipient: crossSwap.recipient,
slippageTolerance: crossSwap.slippageTolerance,
};

// 1. Get destination swap quote for bridgeable output token -> any token
// with exact output amount
const destinationSwapQuote = await getUniswapQuote(
// 1.1. Get destination swap quote for bridgeable output token -> any token
// with exact output amount
let destinationSwapQuote = await getUniswapQuote(
{
chainId: destinationSwapChainId,
tokenIn: bridgeableOutputToken,
tokenOut: crossSwap.outputToken,
...destinationSwap,
amount: crossSwap.amount.toString(),
recipient: crossSwap.recipient,
slippageTolerance: crossSwap.slippageTolerance,
},
TradeType.EXACT_OUTPUT
);
// 1.2. Re-fetch destination swap quote with exact input amount if leftover tokens
// should be sent as output tokens instead of bridgeable output tokens.
if (crossSwap.leftoverType === LEFTOVER_TYPE.OUTPUT_TOKEN) {
destinationSwapQuote = await getUniswapQuote(
{
...destinationSwap,
amount: destinationSwapQuote.maximumAmountIn
.mul(
ethers.utils.parseEther(
(1 + Number(crossSwap.slippageTolerance) / 100).toString()
)
)
.div(utils.fixedPointAdjustment)
.toString(),
},
TradeType.EXACT_INPUT
);
assertMinOutputAmount(destinationSwapQuote.minAmountOut, crossSwap.amount);
}

// 2. Get bridge quote for bridgeable input token -> bridgeable output token
const bridgeQuote = await getBridgeQuoteForMinOutput({
Expand All @@ -396,13 +448,6 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A(
}),
});

const originSwap = {
chainId: originSwapChainId,
tokenIn: crossSwap.inputToken,
tokenOut: bridgeableInputToken,
recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId),
slippageTolerance: crossSwap.slippageTolerance,
};
// 3.1. Get origin swap quote for any input token -> bridgeable input token
const originSwapQuote = await getUniswapQuote(
{
Expand Down Expand Up @@ -589,7 +634,7 @@ function buildDestinationSwapCrossChainMessage({
callData: destinationSwapQuote.swapTx.data,
value: destinationSwapQuote.swapTx.value,
},
// drain remaining bridgeable output tokens from MulticallHandler contract
// drain remaining bridgeable output tokens from MultiCallHandler contract
{
target: getMultiCallHandlerAddress(destinationSwapChainId),
callData: encodeDrainCalldata(
Expand Down Expand Up @@ -618,3 +663,15 @@ function encodeDrainCalldata(token: string, destination: string) {
destination,
]);
}

function assertMinOutputAmount(
amountOut: BigNumber,
expectedMinAmountOut: BigNumber
) {
if (amountOut.lt(expectedMinAmountOut)) {
throw new Error(
`Swap quote output amount ${amountOut.toString()} ` +
`is less than required min. output amount ${expectedMinAmountOut.toString()}`
);
}
}
2 changes: 2 additions & 0 deletions api/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ const handler = async (
type: amountType,
refundOnOrigin,
refundAddress,
// @TODO: Make this configurable via env var or query param
leftoverType: "outputToken",
});

// 3. Build cross swap tx
Expand Down

0 comments on commit 8ac1e95

Please sign in to comment.