From 57b5d6dfdc081989709de6f62eea4674a39dafac Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Thu, 14 Nov 2024 12:26:16 +0700 Subject: [PATCH 01/15] feat: build cross swap tx --- api/_dexes/cross-swap.ts | 99 ++++++++++++- api/_dexes/types.ts | 1 + api/_dexes/uniswap.ts | 310 ++++++++++++++++++++++----------------- api/_integrator-id.ts | 29 ++++ api/_utils.ts | 119 +++++++++------ api/swap.ts | 43 ++++-- scripts/tests/swap.ts | 51 +++++-- 7 files changed, 450 insertions(+), 202 deletions(-) create mode 100644 api/_integrator-id.ts diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index c435a20c3..462ccab5a 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -1,8 +1,12 @@ +import { SpokePool } from "@across-protocol/contracts/dist/typechain"; + import { isRouteEnabled, isInputTokenBridgeable, isOutputTokenBridgeable, getBridgeQuoteForMinOutput, + getSpokePool, + latestGasPriceCache, } from "../_utils"; import { getUniswapCrossSwapQuotesForMinOutputB2A, @@ -10,6 +14,10 @@ import { getBestUniswapCrossSwapQuotesForMinOutputA2A, } from "./uniswap"; import { CrossSwap, CrossSwapQuotes } from "./types"; +import { getSwapAndBridge } from "./utils"; +import { tagIntegratorId } from "../_integrator-id"; +import { PopulatedTransaction } from "ethers"; +import { getMultiCallHandlerAddress } from "../_multicall-handler"; export type CrossSwapType = (typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE]; @@ -139,4 +147,93 @@ export function getCrossSwapType(params: { return CROSS_SWAP_TYPE.ANY_TO_ANY; } -export function calcFees() {} +export async function buildCrossSwapTx( + crossSwapQuotes: CrossSwapQuotes, + integratorId?: string +) { + const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; + const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId; + const spokePool = getSpokePool(originChainId); + const deposit = { + depositor: crossSwapQuotes.crossSwap.recipient, + recipient: crossSwapQuotes.destinationSwapQuote + ? getMultiCallHandlerAddress(destinationChainId) + : crossSwapQuotes.crossSwap.recipient, + inputToken: crossSwapQuotes.bridgeQuote.inputToken.address, + outputToken: crossSwapQuotes.bridgeQuote.outputToken.address, + inputAmount: crossSwapQuotes.bridgeQuote.inputAmount, + outputAmount: crossSwapQuotes.bridgeQuote.outputAmount, + destinationChainid: crossSwapQuotes.bridgeQuote.outputToken.chainId, + exclusiveRelayer: + crossSwapQuotes.bridgeQuote.suggestedFees.exclusiveRelayer, + quoteTimestamp: crossSwapQuotes.bridgeQuote.suggestedFees.timestamp, + fillDeadline: await getFillDeadline(spokePool), + exclusivityDeadline: + crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline, + message: crossSwapQuotes.bridgeQuote?.message || "0x", + }; + + let tx: PopulatedTransaction; + let toAddress: string; + + if (crossSwapQuotes.originSwapQuote) { + const swapAndBridge = getSwapAndBridge("uniswap", originChainId); + tx = await swapAndBridge.populateTransaction.swapAndBridge( + crossSwapQuotes.originSwapQuote.tokenIn.address, + crossSwapQuotes.originSwapQuote.tokenOut.address, + crossSwapQuotes.originSwapQuote.swapTx.data, + crossSwapQuotes.originSwapQuote.maximumAmountIn, + crossSwapQuotes.originSwapQuote.minAmountOut, + deposit + ); + toAddress = swapAndBridge.address; + console.log(tx, toAddress); + } else { + const spokePool = getSpokePool( + crossSwapQuotes.crossSwap.inputToken.chainId + ); + tx = await spokePool.populateTransaction.depositV3( + deposit.depositor, + deposit.recipient, + deposit.inputToken, + deposit.outputToken, + deposit.inputAmount, + deposit.outputAmount, + deposit.destinationChainid, + deposit.exclusiveRelayer, + deposit.quoteTimestamp, + deposit.fillDeadline, + deposit.exclusivityDeadline, + deposit.message + ); + toAddress = spokePool.address; + } + + const [gas, gasPrice] = await Promise.all([ + spokePool.provider.estimateGas({ + from: crossSwapQuotes.crossSwap.depositor, + ...tx, + }), + latestGasPriceCache(originChainId).get(), + ]); + + return { + from: crossSwapQuotes.crossSwap.depositor, + to: toAddress, + data: integratorId ? tagIntegratorId(integratorId, tx.data!) : tx.data, + gas, + gasPrice, + value: tx.value, + }; +} + +async function getFillDeadline(spokePool: SpokePool): Promise { + const calls = [ + spokePool.interface.encodeFunctionData("getCurrentTime"), + spokePool.interface.encodeFunctionData("fillDeadlineBuffer"), + ]; + + const [currentTime, fillDeadlineBuffer] = + await spokePool.callStatic.multicall(calls); + return Number(currentTime) + Number(fillDeadlineBuffer); +} diff --git a/api/_dexes/types.ts b/api/_dexes/types.ts index da5e448c8..f6b85472b 100644 --- a/api/_dexes/types.ts +++ b/api/_dexes/types.ts @@ -25,6 +25,7 @@ export type CrossSwap = { amount: BigNumber; inputToken: Token; outputToken: Token; + depositor: string; recipient: string; slippageTolerance: number; type: AmountType; diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index cf2fc2c53..b9a82a148 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -27,7 +27,6 @@ import { Swap, CrossSwap, SwapQuote, - CrossSwapQuotes, } from "./types"; import { getSwapAndBridgeAddress, NoSwapRouteError } from "./utils"; import { AMOUNT_TYPE } from "./cross-swap"; @@ -70,10 +69,13 @@ export async function getUniswapQuoteForOriginSwapExactInput( swap.tokenIn = getMainnetToken(swap.tokenIn); swap.tokenOut = getMainnetToken(swap.tokenOut); - const { swapTx, minAmountOut } = await getUniswapQuote({ - ...swap, - recipient: swapAndBridgeAddress, - }); + const { swapTx, minAmountOut } = await getUniswapQuote( + { + ...swap, + recipient: swapAndBridgeAddress, + }, + TradeType.EXACT_INPUT + ); // replace mainnet token addresses with initial token addresses in calldata swapTx.data = swapTx.data.replace( @@ -136,15 +138,19 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( }; // 1. Get destination swap quote for bridgeable output token -> any token - const destinationSwapQuote = await getUniswapQuote({ - chainId: destinationSwapChainId, - tokenIn: bridgeableOutputToken, - tokenOut: crossSwap.outputToken, - amount: crossSwap.amount.toString(), - recipient: crossSwap.recipient, - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, - }); + // with exact output amount. + const destinationSwapQuote = await getUniswapQuote( + { + chainId: destinationSwapChainId, + tokenIn: bridgeableOutputToken, + tokenOut: crossSwap.outputToken, + amount: crossSwap.amount.toString(), + recipient: crossSwap.recipient, + slippageTolerance: crossSwap.slippageTolerance, + type: AMOUNT_TYPE.MIN_OUTPUT, + }, + TradeType.EXACT_OUTPUT + ); // 2. Get bridge quote for bridgeable input token -> bridgeable output token const bridgeQuote = await getBridgeQuoteForMinOutput({ @@ -152,48 +158,17 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( outputToken: bridgeableOutputToken, minOutputAmount: destinationSwapQuote.maximumAmountIn, recipient: getMultiCallHandlerAddress(destinationSwapChainId), - message: buildMulticallHandlerMessage({ - // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` - fallbackRecipient: crossSwap.recipient, - actions: [ - { - target: destinationSwapQuote.swapTx.to, - callData: destinationSwapQuote.swapTx.data, - value: destinationSwapQuote.swapTx.value, - }, - ], + message: buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote, + bridgeableOutputToken, }), }); - // 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: updatedDestinationSwapQuote, + destinationSwapQuote, originSwapQuote: undefined, }; } @@ -246,22 +221,46 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( // @TODO: handle ETH/WETH message generation }); - // 2. Get origin swap quote for any input token -> bridgeable input token - const originSwapQuote = await getUniswapQuote({ - chainId: originSwapChainId, - tokenIn: crossSwap.inputToken, - tokenOut: bridgeableInputToken, - amount: bridgeQuote.inputAmount.toString(), - recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, - }); + // 2.1. Get origin swap quote for any input token -> bridgeable input token + const originSwapQuote = await getUniswapQuote( + { + chainId: originSwapChainId, + tokenIn: crossSwap.inputToken, + tokenOut: bridgeableInputToken, + amount: bridgeQuote.inputAmount.toString(), + recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), + slippageTolerance: crossSwap.slippageTolerance, + type: AMOUNT_TYPE.MIN_OUTPUT, + }, + TradeType.EXACT_OUTPUT + ); + // 2.2. Re-fetch origin swap quote with updated input amount and EXACT_INPUT type. + // This prevents leftover tokens in the SwapAndBridge contract. + const adjOriginSwapQuote = await getUniswapQuote( + { + chainId: originSwapChainId, + tokenIn: crossSwap.inputToken, + tokenOut: bridgeableInputToken, + amount: originSwapQuote.maximumAmountIn.toString(), + recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), + slippageTolerance: crossSwap.slippageTolerance, + type: AMOUNT_TYPE.EXACT_INPUT, + }, + TradeType.EXACT_INPUT + ); + + if (adjOriginSwapQuote.minAmountOut.lt(crossSwap.amount)) { + throw new Error( + `Origin swap quote min. output amount ${adjOriginSwapQuote.minAmountOut.toString()} ` + + `is less than targeted min. output amount ${crossSwap.amount.toString()}` + ); + } return { crossSwap, bridgeQuote, destinationSwapQuote: undefined, - originSwapQuote, + originSwapQuote: adjOriginSwapQuote, }; } @@ -293,8 +292,8 @@ export async function getBestUniswapCrossSwapQuotesForMinOutputA2A( ); } - const preferredBridgeRoutes = allBridgeRoutes.filter(({ fromTokenSymbol }) => - opts.preferredBridgeTokens.includes(fromTokenSymbol) + const preferredBridgeRoutes = allBridgeRoutes.filter(({ toTokenSymbol }) => + opts.preferredBridgeTokens.includes(toTokenSymbol) ); const bridgeRoutesToCompare = ( preferredBridgeRoutes.length > 0 ? preferredBridgeRoutes : allBridgeRoutes @@ -306,21 +305,11 @@ export async function getBestUniswapCrossSwapQuotesForMinOutputA2A( ); } - const crossSwapQuotesSettledResults = await Promise.allSettled( + const crossSwapQuotes = await Promise.all( bridgeRoutesToCompare.map((bridgeRoute) => getUniswapCrossSwapQuotesForMinOutputA2A(crossSwap, bridgeRoute) ) ); - const crossSwapQuotes = crossSwapQuotesSettledResults - .filter((res) => res.status === "fulfilled") - .map((res) => (res as PromiseFulfilledResult).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) => @@ -386,15 +375,19 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( }; // 1. Get destination swap quote for bridgeable output token -> any token - const destinationSwapQuote = await getUniswapQuote({ - chainId: destinationSwapChainId, - tokenIn: bridgeableOutputToken, - tokenOut: crossSwap.outputToken, - amount: crossSwap.amount.toString(), - recipient: crossSwap.recipient, - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, - }); + // with exact output amount + const destinationSwapQuote = await getUniswapQuote( + { + chainId: destinationSwapChainId, + tokenIn: bridgeableOutputToken, + tokenOut: crossSwap.outputToken, + amount: crossSwap.amount.toString(), + recipient: crossSwap.recipient, + slippageTolerance: crossSwap.slippageTolerance, + type: AMOUNT_TYPE.MIN_OUTPUT, + }, + TradeType.EXACT_OUTPUT + ); // 2. Get bridge quote for bridgeable input token -> bridgeable output token const bridgeQuote = await getBridgeQuoteForMinOutput({ @@ -402,70 +395,59 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( outputToken: bridgeableOutputToken, minOutputAmount: destinationSwapQuote.maximumAmountIn, recipient: getMultiCallHandlerAddress(destinationSwapChainId), - message: buildMulticallHandlerMessage({ - // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` - fallbackRecipient: crossSwap.recipient, - actions: [ - { - target: destinationSwapQuote.swapTx.to, - callData: destinationSwapQuote.swapTx.data, - value: destinationSwapQuote.swapTx.value, - }, - ], + message: buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote, + bridgeableOutputToken, }), }); - // 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, - tokenIn: crossSwap.inputToken, - tokenOut: bridgeableInputToken, - amount: bridgeQuote.inputAmount.toString(), - recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, - }); + // 3.1. Get origin swap quote for any input token -> bridgeable input token + const originSwapQuote = await getUniswapQuote( + { + chainId: originSwapChainId, + tokenIn: crossSwap.inputToken, + tokenOut: bridgeableInputToken, + amount: bridgeQuote.inputAmount.toString(), + recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), + slippageTolerance: crossSwap.slippageTolerance, + type: AMOUNT_TYPE.MIN_OUTPUT, + }, + TradeType.EXACT_OUTPUT + ); + // 3.2. Re-fetch origin swap quote with updated input amount and EXACT_INPUT type. + // This prevents leftover tokens in the SwapAndBridge contract. + const adjOriginSwapQuote = await getUniswapQuote( + { + chainId: originSwapChainId, + tokenIn: crossSwap.inputToken, + tokenOut: bridgeableInputToken, + amount: originSwapQuote.maximumAmountIn.toString(), + recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), + slippageTolerance: crossSwap.slippageTolerance, + type: AMOUNT_TYPE.EXACT_INPUT, + }, + TradeType.EXACT_INPUT + ); return { crossSwap, - destinationSwapQuote: updatedDestinationSwapQuote, + destinationSwapQuote, bridgeQuote, - originSwapQuote, + originSwapQuote: adjOriginSwapQuote, }; } -export async function getUniswapQuote(swap: Swap): Promise { +export async function getUniswapQuote( + swap: Swap, + tradeType: TradeType +): Promise { const { router, options } = getSwapRouterAndOptions(swap); const amountCurrency = - swap.type === AMOUNT_TYPE.EXACT_INPUT ? swap.tokenIn : swap.tokenOut; + tradeType === TradeType.EXACT_INPUT ? swap.tokenIn : swap.tokenOut; const quoteCurrency = - swap.type === AMOUNT_TYPE.EXACT_INPUT ? swap.tokenOut : swap.tokenIn; + tradeType === TradeType.EXACT_INPUT ? swap.tokenOut : swap.tokenIn; const route = await router.route( CurrencyAmount.fromRawAmount( @@ -481,9 +463,7 @@ export async function getUniswapQuote(swap: Swap): Promise { quoteCurrency.address, quoteCurrency.decimals ), - swap.type === AMOUNT_TYPE.EXACT_INPUT - ? TradeType.EXACT_INPUT - : TradeType.EXACT_OUTPUT, + tradeType, options ); @@ -587,3 +567,61 @@ function getMainnetToken(token: AcrossToken) { address: mainnetTokenAddress, }; } + +function buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote, + bridgeableOutputToken, +}: { + crossSwap: CrossSwap; + bridgeableOutputToken: AcrossToken; + destinationSwapQuote: SwapQuote; +}) { + const destinationSwapChainId = destinationSwapQuote.tokenOut.chainId; + return buildMulticallHandlerMessage({ + // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` + fallbackRecipient: crossSwap.recipient, + actions: [ + // approve bridgeable input token + { + target: bridgeableOutputToken.address, + callData: encodeApproveCalldata( + SWAP_ROUTER_02_ADDRESS[destinationSwapChainId], + destinationSwapQuote.maximumAmountIn + ), + value: "0", + }, + // swap + { + target: destinationSwapQuote.swapTx.to, + callData: destinationSwapQuote.swapTx.data, + value: destinationSwapQuote.swapTx.value, + }, + // drain + { + target: getMultiCallHandlerAddress(destinationSwapChainId), + callData: encodeDrainCalldata( + bridgeableOutputToken.address, + crossSwap.recipient + ), + value: "0", + }, + ], + }); +} + +function encodeApproveCalldata(spender: string, value: ethers.BigNumber) { + const approveFunction = "function approve(address spender, uint256 value)"; + const erc20Interface = new ethers.utils.Interface([approveFunction]); + return erc20Interface.encodeFunctionData("approve", [spender, value]); +} + +function encodeDrainCalldata(token: string, destination: string) { + const drainFunction = + "function drainLeftoverTokens(address token, address payable destination)"; + const multicallHandlerInterface = new ethers.utils.Interface([drainFunction]); + return multicallHandlerInterface.encodeFunctionData("drainLeftoverTokens", [ + token, + destination, + ]); +} diff --git a/api/_integrator-id.ts b/api/_integrator-id.ts new file mode 100644 index 000000000..982971eeb --- /dev/null +++ b/api/_integrator-id.ts @@ -0,0 +1,29 @@ +import { utils } from "ethers"; + +export const DOMAIN_CALLDATA_DELIMITER = "0x1dc0de"; + +export function isValidIntegratorId(integratorId: string) { + return ( + utils.isHexString(integratorId) && + // "0x" + 2 bytes = 6 hex characters + integratorId.length === 6 + ); +} + +export function assertValidIntegratorId(integratorId: string) { + if (!isValidIntegratorId(integratorId)) { + throw new Error( + `Invalid integrator ID: ${integratorId}. Needs to be 2 bytes hex string.` + ); + } + + return true; +} + +export function tagIntegratorId(integratorId: string, txData: string) { + assertValidIntegratorId(integratorId); + + return utils.hexlify( + utils.concat([txData, DOMAIN_CALLDATA_DELIMITER, integratorId]) + ); +} diff --git a/api/_utils.ts b/api/_utils.ts index 0b50770f8..e1654fe7b 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -14,7 +14,7 @@ import { BalancerNetworkConfig, Multicall3, } from "@balancer-labs/sdk"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import { BigNumber, BigNumberish, @@ -64,6 +64,9 @@ import { MissingParamError, InvalidParamError, RouteNotEnabledError, + AcrossApiError, + HttpErrorToStatusCode, + AcrossErrorCode, } from "./_errors"; import { Token } from "./_dexes/types"; @@ -183,6 +186,7 @@ export const getLogger = (): LoggingUtility => { * @returns A valid URL of the current endpoint in vercel */ export const resolveVercelEndpoint = () => { + return "https://app.across.to"; const url = process.env.VERCEL_URL ?? "across.to"; const env = process.env.VERCEL_ENV ?? "development"; switch (env) { @@ -874,55 +878,84 @@ export async function getBridgeQuoteForMinOutput(params: { message: params.message, }; - // 1. Use the suggested fees to get an indicative quote with - // input amount equal to minOutputAmount - let tries = 0; - let adjustedInputAmount = params.minOutputAmount; - let indicativeQuote = await getSuggestedFees({ - ...baseParams, - amount: adjustedInputAmount.toString(), - }); - let adjustmentPct = indicativeQuote.totalRelayFee.pct; - let finalQuote: Awaited> | undefined = - undefined; - - // 2. Adjust input amount to meet minOutputAmount - while (tries < 3) { - adjustedInputAmount = adjustedInputAmount - .mul(utils.parseEther("1").add(adjustmentPct)) - .div(sdk.utils.fixedPointAdjustment); - const adjustedQuote = await getSuggestedFees({ + try { + // 1. Use the suggested fees to get an indicative quote with + // input amount equal to minOutputAmount + let tries = 0; + let adjustedInputAmount = params.minOutputAmount; + let indicativeQuote = await getSuggestedFees({ ...baseParams, amount: adjustedInputAmount.toString(), }); - const outputAmount = adjustedInputAmount.sub( - adjustedInputAmount - .mul(adjustedQuote.totalRelayFee.pct) - .div(sdk.utils.fixedPointAdjustment) - ); + let adjustmentPct = indicativeQuote.totalRelayFee.pct; + let finalQuote: Awaited> | undefined = + undefined; + + // 2. Adjust input amount to meet minOutputAmount + while (tries < 3) { + adjustedInputAmount = adjustedInputAmount + .mul(utils.parseEther("1").add(adjustmentPct)) + .div(sdk.utils.fixedPointAdjustment); + const adjustedQuote = await getSuggestedFees({ + ...baseParams, + amount: adjustedInputAmount.toString(), + }); + const outputAmount = adjustedInputAmount.sub( + adjustedInputAmount + .mul(adjustedQuote.totalRelayFee.pct) + .div(sdk.utils.fixedPointAdjustment) + ); - if (outputAmount.gte(params.minOutputAmount)) { - finalQuote = adjustedQuote; - break; - } else { - adjustmentPct = adjustedQuote.totalRelayFee.pct; - tries++; + if (outputAmount.gte(params.minOutputAmount)) { + finalQuote = adjustedQuote; + break; + } else { + adjustmentPct = adjustedQuote.totalRelayFee.pct; + tries++; + } } - } - if (!finalQuote) { - throw new Error("Failed to adjust input amount to meet minOutputAmount"); - } + if (!finalQuote) { + throw new Error("Failed to adjust input amount to meet minOutputAmount"); + } - return { - inputAmount: adjustedInputAmount, - outputAmount: adjustedInputAmount.sub(finalQuote.totalRelayFee.total), - minOutputAmount: params.minOutputAmount, - suggestedFees: finalQuote, - message: params.message, - inputToken: params.inputToken, - outputToken: params.outputToken, - }; + return { + inputAmount: adjustedInputAmount, + outputAmount: adjustedInputAmount.sub(finalQuote.totalRelayFee.total), + minOutputAmount: params.minOutputAmount, + suggestedFees: finalQuote, + message: params.message, + inputToken: params.inputToken, + outputToken: params.outputToken, + }; + } catch (err) { + if (err instanceof AxiosError) { + const { response = { data: {} } } = err; + // If upstream error is an AcrossApiError, we just return it + if (response?.data?.type === "AcrossApiError") { + throw new AcrossApiError( + { + message: response.data.message, + status: response.data.status, + code: response.data.code, + param: response.data.param, + }, + { cause: err } + ); + } else { + const message = `Upstream http request to ${err.request?.url} failed with ${err.status} ${err.message}`; + throw new AcrossApiError( + { + message, + status: HttpErrorToStatusCode.BAD_GATEWAY, + code: AcrossErrorCode.UPSTREAM_HTTP_ERROR, + }, + { cause: err } + ); + } + } + throw err; + } } export const providerCache: Record = {}; diff --git a/api/swap.ts b/api/swap.ts index 2dc0cc8a1..f28fc3ec9 100644 --- a/api/swap.ts +++ b/api/swap.ts @@ -12,9 +12,14 @@ import { boolStr, getTokenByAddress, } from "./_utils"; -import { AMOUNT_TYPE, getCrossSwapQuotes } from "./_dexes/cross-swap"; +import { + AMOUNT_TYPE, + buildCrossSwapTx, + getCrossSwapQuotes, +} from "./_dexes/cross-swap"; import { Token } from "./_dexes/types"; import { InvalidParamError, MissingParamError } from "./_errors"; +import { isValidIntegratorId } from "./_integrator-id"; const SwapQueryParamsSchema = type({ minOutputAmount: optional(positiveIntStr()), @@ -23,8 +28,9 @@ const SwapQueryParamsSchema = type({ outputToken: validAddress(), originChainId: positiveIntStr(), destinationChainId: positiveIntStr(), - recipient: validAddress(), - integratorId: string(), + depositor: validAddress(), + recipient: optional(validAddress()), + integratorId: optional(string()), refundAddress: optional(validAddress()), refundOnOrigin: optional(boolStr()), slippageTolerance: optional(positiveFloatStr(50)), // max. 50% slippage @@ -53,10 +59,11 @@ const handler = async ( originChainId: _originChainId, destinationChainId: _destinationChainId, recipient, + depositor, integratorId, refundAddress, refundOnOrigin: _refundOnOrigin = "true", - slippageTolerance = "0.5", // Default to 0.5% slippage + slippageTolerance = "1", // Default to 1% slippage } = query; const originChainId = Number(_originChainId); @@ -78,6 +85,13 @@ const handler = async ( }); } + if (integratorId && !isValidIntegratorId(integratorId)) { + throw new InvalidParamError({ + param: "integratorId", + message: "Invalid integrator ID. Needs to be 2 bytes hex string.", + }); + } + const amountType = _minOutputAmount ? AMOUNT_TYPE.MIN_OUTPUT : AMOUNT_TYPE.EXACT_INPUT; @@ -132,22 +146,33 @@ const handler = async ( amount, inputToken, outputToken, - recipient, + depositor, + recipient: recipient || depositor, slippageTolerance: Number(slippageTolerance), type: amountType, refundOnOrigin, refundAddress, }); - // 3. Build tx and return - // @TODO + // 3. Build cross swap tx + const crossSwapTx = await buildCrossSwapTx(crossSwapQuotes, integratorId); + + const responseJson = { + tx: { + to: crossSwapTx.to, + data: crossSwapTx.data, + value: crossSwapTx.value?.toString(), + gas: crossSwapTx.gas?.toString(), + gasPrice: crossSwapTx.gasPrice?.toString(), + }, + }; logger.debug({ at: "Swap", message: "Response data", - responseJson: crossSwapQuotes, + responseJson, }); - response.status(200).json(crossSwapQuotes); + response.status(200).json(responseJson); } catch (error: unknown) { return handleErrorCondition("swap", response, logger, error); } diff --git a/scripts/tests/swap.ts b/scripts/tests/swap.ts index c2d185abb..e3f40fd34 100644 --- a/scripts/tests/swap.ts +++ b/scripts/tests/swap.ts @@ -1,6 +1,9 @@ import axios from "axios"; import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { ethers } from "ethers"; +import { ethers, Wallet } from "ethers"; +import dotenv from "dotenv"; +import { getProvider } from "../../api/_utils"; +dotenv.config(); /** * Manual test script for the swap API. Should be converted to a proper test suite. @@ -10,52 +13,74 @@ const depositor = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; const MIN_OUTPUT_CASES = [ // B2B { - minOutputAmount: ethers.utils.parseUnits("100", 6).toString(), + label: "B2B", + minOutputAmount: ethers.utils.parseUnits("1", 6).toString(), inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], originChainId: CHAIN_IDs.BASE, outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.ARBITRUM], destinationChainId: CHAIN_IDs.ARBITRUM, - recipient: depositor, - integratorId: "test", + depositor, }, // B2A { - minOutputAmount: ethers.utils.parseUnits("0.001", 18).toString(), + label: "B2A", + minOutputAmount: ethers.utils.parseUnits("1", 18).toString(), inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], originChainId: CHAIN_IDs.BASE, - outputToken: TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.ARBITRUM], + outputToken: "0x74885b4D524d497261259B38900f54e6dbAd2210", // APE Coin destinationChainId: CHAIN_IDs.ARBITRUM, - recipient: depositor, - integratorId: "test", + depositor, }, // A2B { - minOutputAmount: ethers.utils.parseUnits("10", 6).toString(), + label: "A2B", + minOutputAmount: ethers.utils.parseUnits("1", 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, - recipient: depositor, - integratorId: "test", + depositor, }, // A2A { + label: "A2A", 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, - recipient: depositor, - integratorId: "test", + depositor, + slippageTolerance: 1, }, ]; async function swap() { for (const testCase of MIN_OUTPUT_CASES) { + console.log("\nTest case:", testCase.label); const response = await axios.get(`http://localhost:3000/api/swap`, { params: testCase, }); console.log(response.data); + + if (process.env.DEV_WALLET_PK) { + const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( + getProvider(testCase.originChainId) + ); + try { + const tx = await wallet.sendTransaction({ + to: response.data.tx.to, + data: response.data.tx.data, + value: response.data.tx.value, + gasLimit: response.data.tx.gas, + gasPrice: response.data.tx.gasPrice, + }); + console.log("Tx hash: ", tx.hash); + await tx.wait(); + console.log("Tx mined"); + } catch (e) { + console.error("Tx reverted", e); + } + } } } From 3f23d874d91f9f2e24968fbfc9d8a7904e501c70 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Thu, 14 Nov 2024 13:58:24 +0700 Subject: [PATCH 02/15] fixup --- api/_dexes/cross-swap.ts | 1 - api/_dexes/uniswap.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index 462ccab5a..b7ca236c1 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -187,7 +187,6 @@ export async function buildCrossSwapTx( deposit ); toAddress = swapAndBridge.address; - console.log(tx, toAddress); } else { const spokePool = getSpokePool( crossSwapQuotes.crossSwap.inputToken.chainId diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index b9a82a148..531000575 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -249,10 +249,10 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( TradeType.EXACT_INPUT ); - if (adjOriginSwapQuote.minAmountOut.lt(crossSwap.amount)) { + if (adjOriginSwapQuote.minAmountOut.lt(bridgeQuote.inputAmount)) { throw new Error( `Origin swap quote min. output amount ${adjOriginSwapQuote.minAmountOut.toString()} ` + - `is less than targeted min. output amount ${crossSwap.amount.toString()}` + `is less than required bridge input amount ${bridgeQuote.inputAmount.toString()}` ); } From 4ed90e337a1ed12fc4edbd7dcdffeb3b7884eaec Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Thu, 14 Nov 2024 14:22:42 +0700 Subject: [PATCH 03/15] fixup --- api/_dexes/uniswap.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index 531000575..1eca7ae3f 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -44,9 +44,9 @@ export const SWAP_ROUTER_02_ADDRESS = { [CHAIN_IDs.ZORA]: "0x7De04c96BE5159c3b5CeffC82aa176dc81281557", }; -// Maps testnet chain IDs to their main counterparts. Used to get the mainnet token +// Maps testnet chain IDs to their prod counterparts. Used to get the prod token // info for testnet tokens. -const TESTNET_TO_MAINNET = { +const TESTNET_TO_PROD = { [CHAIN_IDs.SEPOLIA]: CHAIN_IDs.MAINNET, [CHAIN_IDs.BASE_SEPOLIA]: CHAIN_IDs.BASE, [CHAIN_IDs.OPTIMISM_SEPOLIA]: CHAIN_IDs.OPTIMISM, @@ -66,8 +66,8 @@ export async function getUniswapQuoteForOriginSwapExactInput( const initialTokenOut = { ...swap.tokenOut }; // Always use mainnet tokens for retrieving quote, so that we can get equivalent quotes // for testnet tokens. - swap.tokenIn = getMainnetToken(swap.tokenIn); - swap.tokenOut = getMainnetToken(swap.tokenOut); + swap.tokenIn = getProdToken(swap.tokenIn); + swap.tokenOut = getProdToken(swap.tokenOut); const { swapTx, minAmountOut } = await getUniswapQuote( { @@ -548,23 +548,23 @@ function floatToPercent(value: number) { ); } -function getMainnetToken(token: AcrossToken) { - const mainnetChainId = TESTNET_TO_MAINNET[token.chainId] || token.chainId; +function getProdToken(token: AcrossToken) { + const prodChainId = TESTNET_TO_PROD[token.chainId] || token.chainId; - const mainnetToken = + const prodToken = TOKEN_SYMBOLS_MAP[token.symbol as keyof typeof TOKEN_SYMBOLS_MAP]; - const mainnetTokenAddress = mainnetToken?.addresses[mainnetChainId]; + const prodTokenAddress = prodToken?.addresses[prodChainId]; - if (!mainnetToken || !mainnetTokenAddress) { + if (!prodToken || !prodTokenAddress) { throw new Error( - `Mainnet token not found for ${token.symbol} on chain ${token.chainId}` + `Prod token not found for ${token.symbol} on chain ${token.chainId}` ); } return { - ...mainnetToken, - chainId: mainnetChainId, - address: mainnetTokenAddress, + ...prodToken, + chainId: prodChainId, + address: prodTokenAddress, }; } @@ -582,7 +582,7 @@ function buildDestinationSwapCrossChainMessage({ // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` fallbackRecipient: crossSwap.recipient, actions: [ - // approve bridgeable input token + // approve bridgeable output token { target: bridgeableOutputToken.address, callData: encodeApproveCalldata( @@ -591,17 +591,18 @@ function buildDestinationSwapCrossChainMessage({ ), value: "0", }, - // swap + // swap bridgeable output token -> cross swap output token { target: destinationSwapQuote.swapTx.to, callData: destinationSwapQuote.swapTx.data, value: destinationSwapQuote.swapTx.value, }, - // drain + // drain remaining bridgeable output tokens from MulticallHandler contract { target: getMultiCallHandlerAddress(destinationSwapChainId), callData: encodeDrainCalldata( bridgeableOutputToken.address, + // @TODO: determine whether to use 'depositor' or 'recipient' crossSwap.recipient ), value: "0", From cb41cab1adcb980f5da8c969ab500f6549d73d0f Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Thu, 14 Nov 2024 14:37:37 +0700 Subject: [PATCH 04/15] refactor --- api/_dexes/uniswap.ts | 49 +++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index 1eca7ae3f..a078ab4ea 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -147,7 +147,6 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( amount: crossSwap.amount.toString(), recipient: crossSwap.recipient, slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, }, TradeType.EXACT_OUTPUT ); @@ -221,16 +220,18 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( // @TODO: handle ETH/WETH message generation }); + const originSwap = { + chainId: originSwapChainId, + tokenIn: crossSwap.inputToken, + tokenOut: bridgeableInputToken, + recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), + slippageTolerance: crossSwap.slippageTolerance, + }; // 2.1. Get origin swap quote for any input token -> bridgeable input token const originSwapQuote = await getUniswapQuote( { - chainId: originSwapChainId, - tokenIn: crossSwap.inputToken, - tokenOut: bridgeableInputToken, + ...originSwap, amount: bridgeQuote.inputAmount.toString(), - recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, }, TradeType.EXACT_OUTPUT ); @@ -238,13 +239,8 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( // This prevents leftover tokens in the SwapAndBridge contract. const adjOriginSwapQuote = await getUniswapQuote( { - chainId: originSwapChainId, - tokenIn: crossSwap.inputToken, - tokenOut: bridgeableInputToken, + ...originSwap, amount: originSwapQuote.maximumAmountIn.toString(), - recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.EXACT_INPUT, }, TradeType.EXACT_INPUT ); @@ -384,7 +380,6 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( amount: crossSwap.amount.toString(), recipient: crossSwap.recipient, slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, }, TradeType.EXACT_OUTPUT ); @@ -402,16 +397,18 @@ 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( { - chainId: originSwapChainId, - tokenIn: crossSwap.inputToken, - tokenOut: bridgeableInputToken, + ...originSwap, amount: bridgeQuote.inputAmount.toString(), - recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.MIN_OUTPUT, }, TradeType.EXACT_OUTPUT ); @@ -419,13 +416,8 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( // This prevents leftover tokens in the SwapAndBridge contract. const adjOriginSwapQuote = await getUniswapQuote( { - chainId: originSwapChainId, - tokenIn: crossSwap.inputToken, - tokenOut: bridgeableInputToken, + ...originSwap, amount: originSwapQuote.maximumAmountIn.toString(), - recipient: getSwapAndBridgeAddress("uniswap", originSwapChainId), - slippageTolerance: crossSwap.slippageTolerance, - type: AMOUNT_TYPE.EXACT_INPUT, }, TradeType.EXACT_INPUT ); @@ -439,7 +431,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( } export async function getUniswapQuote( - swap: Swap, + swap: Omit, tradeType: TradeType ): Promise { const { router, options } = getSwapRouterAndOptions(swap); @@ -473,7 +465,8 @@ export async function getUniswapQuote( tokenInSymbol: swap.tokenIn.symbol, tokenOutSymbol: swap.tokenOut.symbol, chainId: swap.chainId, - swapType: swap.type, + swapType: + tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", }); } From fb882e5c9bc30f53ea32ec19d6bca688f7804090 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Thu, 14 Nov 2024 14:46:56 +0700 Subject: [PATCH 05/15] fix --- api/_dexes/uniswap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index a078ab4ea..d018277e7 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -498,7 +498,7 @@ export async function getUniswapQuote( }; console.log("swapQuote", { - type: swap.type, + type: tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", tokenIn: swapQuote.tokenIn.symbol, tokenOut: swapQuote.tokenOut.symbol, chainId: swap.chainId, From 188ccfd156973c01cb6d86e118071d95081e8447 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Fri, 15 Nov 2024 17:43:58 +0700 Subject: [PATCH 06/15] fixup --- api/_dexes/uniswap.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index d018277e7..f5a0765e8 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -29,7 +29,6 @@ import { SwapQuote, } from "./types"; import { getSwapAndBridgeAddress, NoSwapRouteError } from "./utils"; -import { AMOUNT_TYPE } from "./cross-swap"; // Taken from here: https://docs.uniswap.org/contracts/v3/reference/deployments/ export const SWAP_ROUTER_02_ADDRESS = { From 589dc9837b9fa487446b2ded482e1077954bdfd7 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Sat, 16 Nov 2024 14:55:01 +0700 Subject: [PATCH 07/15] fixup --- api/_dexes/uniswap.ts | 2 +- api/_utils.ts | 12 +++++----- api/swap.ts | 53 ++++++++++--------------------------------- 3 files changed, 19 insertions(+), 48 deletions(-) diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index f5a0765e8..f5a9663f1 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -387,7 +387,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( const bridgeQuote = await getBridgeQuoteForMinOutput({ inputToken: bridgeableInputToken, outputToken: bridgeableOutputToken, - minOutputAmount: destinationSwapQuote.maximumAmountIn, + minOutputAmount: destinationSwapQuote.expectedAmountIn, recipient: getMultiCallHandlerAddress(destinationSwapChainId), message: buildDestinationSwapCrossChainMessage({ crossSwap, diff --git a/api/_utils.ts b/api/_utils.ts index 6858bc375..7b43e9b08 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -187,7 +187,6 @@ export const getLogger = (): LoggingUtility => { * @returns A valid URL of the current endpoint in vercel */ export const resolveVercelEndpoint = () => { - return "https://app.across.to"; const url = process.env.VERCEL_URL ?? "across.to"; const env = process.env.VERCEL_ENV ?? "development"; switch (env) { @@ -2221,11 +2220,10 @@ export async function getCachedTokenInfo(params: TokenOptions) { } // find decimals and symbol for any token address on any chain we support -export async function getTokenInfo({ - chainId, - address, -}: TokenOptions): Promise< - Pick +export async function getTokenInfo({ chainId, address }: TokenOptions): Promise< + Pick & { + chainId: number; + } > { try { if (!ethers.utils.isAddress(address)) { @@ -2255,6 +2253,7 @@ export async function getTokenInfo({ symbol: token.symbol, address: token.addresses[chainId], name: token.name, + chainId, }; } @@ -2291,6 +2290,7 @@ export async function getTokenInfo({ decimals, symbol, name, + chainId, }; } catch (error) { throw new TokenNotFoundError({ diff --git a/api/swap.ts b/api/swap.ts index f28fc3ec9..a59bc9387 100644 --- a/api/swap.ts +++ b/api/swap.ts @@ -10,14 +10,13 @@ import { positiveIntStr, validAddress, boolStr, - getTokenByAddress, + getCachedTokenInfo, } from "./_utils"; import { AMOUNT_TYPE, buildCrossSwapTx, getCrossSwapQuotes, } from "./_dexes/cross-swap"; -import { Token } from "./_dexes/types"; import { InvalidParamError, MissingParamError } from "./_errors"; import { isValidIntegratorId } from "./_integrator-id"; @@ -101,45 +100,17 @@ const handler = async ( : _minOutputAmount ); - // 1. Get auxiliary data - // - Token details - // - Token prices - const knownInputToken = getTokenByAddress( - _inputTokenAddress, - originChainId - ); - const inputToken: Token = knownInputToken - ? { - address: knownInputToken.addresses[originChainId]!, - decimals: knownInputToken.decimals, - symbol: knownInputToken.symbol, - chainId: originChainId, - } - : // @FIXME: fetch dynamic token details. using hardcoded values for now - { - address: _inputTokenAddress, - decimals: 18, - symbol: "UNKNOWN", - chainId: originChainId, - }; - const knownOutputToken = getTokenByAddress( - _outputTokenAddress, - destinationChainId - ); - const outputToken: Token = knownOutputToken - ? { - address: knownOutputToken.addresses[destinationChainId]!, - decimals: knownOutputToken.decimals, - symbol: knownOutputToken.symbol, - chainId: destinationChainId, - } - : // @FIXME: fetch dynamic token details. using hardcoded values for now - { - address: _outputTokenAddress, - decimals: 18, - symbol: "UNKNOWN", - chainId: destinationChainId, - }; + // 1. Get token details + const [inputToken, outputToken] = await Promise.all([ + getCachedTokenInfo({ + address: _inputTokenAddress, + chainId: originChainId, + }), + getCachedTokenInfo({ + address: _outputTokenAddress, + chainId: destinationChainId, + }), + ]); // 2. Get swap quotes and calldata based on the swap type const crossSwapQuotes = await getCrossSwapQuotes({ From 8ac1e952dee670245ffd52501e469f6b6b5e2c49 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Mon, 18 Nov 2024 18:01:31 +0700 Subject: [PATCH 08/15] feat: add leftover handling type --- api/_dexes/cross-swap.ts | 7 +++ api/_dexes/types.ts | 4 +- api/_dexes/uniswap.ts | 105 ++++++++++++++++++++++++++++++--------- api/swap.ts | 2 + 4 files changed, 93 insertions(+), 25 deletions(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index b7ca236c1..be903bba2 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -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", @@ -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( diff --git a/api/_dexes/types.ts b/api/_dexes/types.ts index f6b85472b..6e00f04ce 100644 --- a/api/_dexes/types.ts +++ b/api/_dexes/types.ts @@ -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 }; @@ -19,6 +19,7 @@ export type Swap = { recipient: string; slippageTolerance: number; type: AmountType; + leftoverType?: LeftoverType; }; export type CrossSwap = { @@ -29,6 +30,7 @@ export type CrossSwap = { recipient: string; slippageTolerance: number; type: AmountType; + leftoverType?: LeftoverType; refundOnOrigin: boolean; refundAddress?: string; }; diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index f5a9663f1..530207eb1 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -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 = { @@ -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({ @@ -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({ @@ -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( { @@ -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( @@ -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()}` + ); + } +} diff --git a/api/swap.ts b/api/swap.ts index a59bc9387..d4e2ac1a7 100644 --- a/api/swap.ts +++ b/api/swap.ts @@ -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 From 8958fad868ae1236221b249618726d99a6553096 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Mon, 18 Nov 2024 22:26:45 +0700 Subject: [PATCH 09/15] fixup --- api/_dexes/uniswap.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index 530207eb1..7987d92eb 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -617,7 +617,7 @@ function buildDestinationSwapCrossChainMessage({ const destinationSwapChainId = destinationSwapQuote.tokenOut.chainId; return buildMulticallHandlerMessage({ // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` - fallbackRecipient: crossSwap.recipient, + fallbackRecipient: crossSwap.depositor, actions: [ // approve bridgeable output token { @@ -639,8 +639,7 @@ function buildDestinationSwapCrossChainMessage({ target: getMultiCallHandlerAddress(destinationSwapChainId), callData: encodeDrainCalldata( bridgeableOutputToken.address, - // @TODO: determine whether to use 'depositor' or 'recipient' - crossSwap.recipient + crossSwap.depositor ), value: "0", }, From 30c38f56ff35d907d8260195a7ecedc89824b38f Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 19 Nov 2024 09:08:56 +0700 Subject: [PATCH 10/15] fixup --- api/swap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/swap.ts b/api/swap.ts index d4e2ac1a7..93b98e949 100644 --- a/api/swap.ts +++ b/api/swap.ts @@ -124,7 +124,7 @@ const handler = async ( refundOnOrigin, refundAddress, // @TODO: Make this configurable via env var or query param - leftoverType: "outputToken", + leftoverType: "bridgeableToken", }); // 3. Build cross swap tx From 356885b70bb266e98e9248f02a28d1452225ccd5 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 19 Nov 2024 19:32:31 +0700 Subject: [PATCH 11/15] feat: support native eth as input/output --- api/_dexes/cross-swap.ts | 23 +++++++++++----- api/_dexes/types.ts | 4 +++ api/_dexes/uniswap.ts | 48 ++++++++++++++++++++------------- api/_dexes/utils.ts | 57 +++++++++++++++++++++++++++++++++++++++ api/_multicall-handler.ts | 31 +++++++++++++++++++++ api/_utils.ts | 8 ++++++ api/swap.ts | 24 ++++++++++++++--- 7 files changed, 166 insertions(+), 29 deletions(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index be903bba2..a62c155e0 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -14,7 +14,7 @@ import { getBestUniswapCrossSwapQuotesForMinOutputA2A, } from "./uniswap"; import { CrossSwap, CrossSwapQuotes } from "./types"; -import { getSwapAndBridge } from "./utils"; +import { buildExactOutputBridgeTokenMessage, getSwapAndBridge } from "./utils"; import { tagIntegratorId } from "../_integrator-id"; import { PopulatedTransaction } from "ethers"; import { getMultiCallHandlerAddress } from "../_multicall-handler"; @@ -97,8 +97,8 @@ export async function getCrossSwapQuotesForMinOutputB2B(crossSwap: CrossSwap) { inputToken: crossSwap.inputToken, outputToken: crossSwap.outputToken, minOutputAmount: crossSwap.amount, - // @TODO: handle ETH/WETH message generation - message: "0x", + recipient: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), + message: buildExactOutputBridgeTokenMessage(crossSwap), }); return { crossSwap, @@ -163,9 +163,7 @@ export async function buildCrossSwapTx( const spokePool = getSpokePool(originChainId); const deposit = { depositor: crossSwapQuotes.crossSwap.recipient, - recipient: crossSwapQuotes.destinationSwapQuote - ? getMultiCallHandlerAddress(destinationChainId) - : crossSwapQuotes.crossSwap.recipient, + recipient: getMultiCallHandlerAddress(destinationChainId), inputToken: crossSwapQuotes.bridgeQuote.inputToken.address, outputToken: crossSwapQuotes.bridgeQuote.outputToken.address, inputAmount: crossSwapQuotes.bridgeQuote.inputAmount, @@ -192,6 +190,12 @@ export async function buildCrossSwapTx( crossSwapQuotes.originSwapQuote.maximumAmountIn, crossSwapQuotes.originSwapQuote.minAmountOut, deposit + // @FIXME: This needs a redeployed version of the contract + // { + // value: crossSwapQuotes.crossSwap.isInputNative + // ? deposit.inputAmount + // : 0, + // } ); toAddress = swapAndBridge.address; } else { @@ -210,7 +214,12 @@ export async function buildCrossSwapTx( deposit.quoteTimestamp, deposit.fillDeadline, deposit.exclusivityDeadline, - deposit.message + deposit.message, + { + value: crossSwapQuotes.crossSwap.isInputNative + ? deposit.inputAmount + : 0, + } ); toAddress = spokePool.address; } diff --git a/api/_dexes/types.ts b/api/_dexes/types.ts index 6e00f04ce..c8d1973d1 100644 --- a/api/_dexes/types.ts +++ b/api/_dexes/types.ts @@ -20,6 +20,8 @@ export type Swap = { slippageTolerance: number; type: AmountType; leftoverType?: LeftoverType; + isInputNative?: boolean; + isOutputNative?: boolean; }; export type CrossSwap = { @@ -33,6 +35,8 @@ export type CrossSwap = { leftoverType?: LeftoverType; refundOnOrigin: boolean; refundAddress?: string; + isInputNative?: boolean; + isOutputNative?: boolean; }; export type SupportedDex = "1inch" | "uniswap"; diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index 7987d92eb..a1afe9e64 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -19,6 +19,9 @@ import { import { TOKEN_SYMBOLS_MAP } from "../_constants"; import { buildMulticallHandlerMessage, + encodeApproveCalldata, + encodeDrainCalldata, + encodeWethWithdrawCalldata, getMultiCallHandlerAddress, } from "../_multicall-handler"; import { @@ -28,7 +31,11 @@ import { CrossSwap, SwapQuote, } from "./types"; -import { getSwapAndBridgeAddress, NoSwapRouteError } from "./utils"; +import { + buildExactOutputBridgeTokenMessage, + getSwapAndBridgeAddress, + NoSwapRouteError, +} from "./utils"; import { LEFTOVER_TYPE } from "./cross-swap"; // Taken from here: https://docs.uniswap.org/contracts/v3/reference/deployments/ @@ -141,7 +148,9 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( chainId: destinationSwapChainId, tokenIn: bridgeableOutputToken, tokenOut: crossSwap.outputToken, - recipient: crossSwap.recipient, + recipient: crossSwap.isOutputNative + ? getMultiCallHandlerAddress(destinationSwapChainId) + : crossSwap.recipient, slippageTolerance: crossSwap.slippageTolerance, }; // 1.1. Get destination swap quote for bridgeable output token -> any token @@ -239,7 +248,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( inputToken: bridgeableInputToken, outputToken: crossSwap.outputToken, minOutputAmount: crossSwap.amount, - // @TODO: handle ETH/WETH message generation + message: buildExactOutputBridgeTokenMessage(crossSwap), }); const originSwap = { @@ -615,6 +624,22 @@ function buildDestinationSwapCrossChainMessage({ destinationSwapQuote: SwapQuote; }) { const destinationSwapChainId = destinationSwapQuote.tokenOut.chainId; + const nativeEthActions = crossSwap.isOutputNative + ? // If output token is native, we need to unwrap WETH before sending it to the + // recipient. This is because we only handle WETH in the destination swap. + [ + { + target: crossSwap.outputToken.address, + callData: encodeWethWithdrawCalldata(crossSwap.amount), + value: "0", + }, + { + target: crossSwap.recipient, + callData: "0x", + value: crossSwap.amount.toString(), + }, + ] + : []; return buildMulticallHandlerMessage({ // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` fallbackRecipient: crossSwap.depositor, @@ -634,6 +659,7 @@ function buildDestinationSwapCrossChainMessage({ callData: destinationSwapQuote.swapTx.data, value: destinationSwapQuote.swapTx.value, }, + ...nativeEthActions, // drain remaining bridgeable output tokens from MultiCallHandler contract { target: getMultiCallHandlerAddress(destinationSwapChainId), @@ -647,22 +673,6 @@ function buildDestinationSwapCrossChainMessage({ }); } -function encodeApproveCalldata(spender: string, value: ethers.BigNumber) { - const approveFunction = "function approve(address spender, uint256 value)"; - const erc20Interface = new ethers.utils.Interface([approveFunction]); - return erc20Interface.encodeFunctionData("approve", [spender, value]); -} - -function encodeDrainCalldata(token: string, destination: string) { - const drainFunction = - "function drainLeftoverTokens(address token, address payable destination)"; - const multicallHandlerInterface = new ethers.utils.Interface([drainFunction]); - return multicallHandlerInterface.encodeFunctionData("drainLeftoverTokens", [ - token, - destination, - ]); -} - function assertMinOutputAmount( amountOut: BigNumber, expectedMinAmountOut: BigNumber diff --git a/api/_dexes/utils.ts b/api/_dexes/utils.ts index 06cb97ece..8108c89c7 100644 --- a/api/_dexes/utils.ts +++ b/api/_dexes/utils.ts @@ -1,6 +1,14 @@ import { UniversalSwapAndBridge__factory } from "@across-protocol/contracts/dist/typechain"; import { ENABLED_ROUTES, getProvider } from "../_utils"; +import { + buildMulticallHandlerMessage, + encodeDrainCalldata, + encodeTransferCalldata, + encodeWethWithdrawCalldata, + getMultiCallHandlerAddress, +} from "../_multicall-handler"; +import { CrossSwap } from "./types"; export class UnsupportedDex extends Error { constructor(dex: string) { @@ -60,3 +68,52 @@ function _isDexSupported( ): dex is keyof typeof ENABLED_ROUTES.swapAndBridgeAddresses { return swapAndBridgeDexes.includes(dex); } + +/** + * This builds a cross-chain message for a (any/bridgeable)-to-bridgeable cross swap + * with a specific amount of output tokens that the recipient will receive. Excess + * tokens are refunded to the depositor. + */ +export function buildExactOutputBridgeTokenMessage(crossSwap: CrossSwap) { + const transferActions = crossSwap.isOutputNative + ? // WETH unwrap to ETH + [ + { + target: crossSwap.outputToken.address, + callData: encodeWethWithdrawCalldata(crossSwap.amount), + value: "0", + }, + { + target: crossSwap.recipient, + callData: "0x", + value: crossSwap.amount.toString(), + }, + ] + : // ERC-20 token transfer + [ + { + target: crossSwap.outputToken.address, + callData: encodeTransferCalldata( + crossSwap.recipient, + crossSwap.amount + ), + value: "0", + }, + ]; + return buildMulticallHandlerMessage({ + // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` + fallbackRecipient: crossSwap.depositor, + actions: [ + ...transferActions, + // drain remaining bridgeable output tokens from MultiCallHandler contract + { + target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.depositor + ), + value: "0", + }, + ], + }); +} diff --git a/api/_multicall-handler.ts b/api/_multicall-handler.ts index 9bcb86a7b..a4c8ef6c1 100644 --- a/api/_multicall-handler.ts +++ b/api/_multicall-handler.ts @@ -46,3 +46,34 @@ export function buildMulticallHandlerMessage(params: { ] ); } + +export function encodeWethWithdrawCalldata(value: ethers.BigNumber) { + const withdrawFunction = "function withdraw(uint256 wad)"; + const wethInterface = new ethers.utils.Interface([withdrawFunction]); + return wethInterface.encodeFunctionData("withdraw", [value]); +} + +export function encodeApproveCalldata( + spender: string, + value: ethers.BigNumber +) { + const approveFunction = "function approve(address spender, uint256 value)"; + const erc20Interface = new ethers.utils.Interface([approveFunction]); + return erc20Interface.encodeFunctionData("approve", [spender, value]); +} + +export function encodeTransferCalldata(to: string, value: ethers.BigNumber) { + const approveFunction = "function transfer(address to, uint256 value)"; + const erc20Interface = new ethers.utils.Interface([approveFunction]); + return erc20Interface.encodeFunctionData("transfer", [to, value]); +} + +export function encodeDrainCalldata(token: string, destination: string) { + const drainFunction = + "function drainLeftoverTokens(address token, address payable destination)"; + const multicallHandlerInterface = new ethers.utils.Interface([drainFunction]); + return multicallHandlerInterface.encodeFunctionData("drainLeftoverTokens", [ + token, + destination, + ]); +} diff --git a/api/_utils.ts b/api/_utils.ts index 7b43e9b08..3cb83e825 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -346,6 +346,14 @@ function getStaticIsContract(chainId: number, address: string) { return !!deployedAcrossContract; } +export function getWrappedNativeTokenAddress(chainId: number) { + if (chainId === CHAIN_IDs.POLYGON || chainId === CHAIN_IDs.POLYGON_AMOY) { + return TOKEN_SYMBOLS_MAP.WMATIC.addresses[chainId]; + } + + return TOKEN_SYMBOLS_MAP.WETH.addresses[chainId]; +} + /** * Utility function to resolve route details based on given `inputTokenAddress` and `destinationChainId`. * The optional parameter `originChainId` can be omitted if the `inputTokenAddress` is unique across all diff --git a/api/swap.ts b/api/swap.ts index 93b98e949..e8db5eaa7 100644 --- a/api/swap.ts +++ b/api/swap.ts @@ -1,6 +1,6 @@ import { VercelResponse } from "@vercel/node"; import { assert, Infer, type, string, optional } from "superstruct"; -import { BigNumber } from "ethers"; +import { BigNumber, constants, utils } from "ethers"; import { TypedVercelRequest } from "./_types"; import { @@ -11,6 +11,7 @@ import { validAddress, boolStr, getCachedTokenInfo, + getWrappedNativeTokenAddress, } from "./_utils"; import { AMOUNT_TYPE, @@ -68,6 +69,14 @@ const handler = async ( const originChainId = Number(_originChainId); const destinationChainId = Number(_destinationChainId); const refundOnOrigin = _refundOnOrigin === "true"; + const isInputNative = _inputTokenAddress === constants.AddressZero; + const isOutputNative = _outputTokenAddress === constants.AddressZero; + const inputTokenAddress = isInputNative + ? getWrappedNativeTokenAddress(originChainId) + : utils.getAddress(_inputTokenAddress); + const outputTokenAddress = isOutputNative + ? getWrappedNativeTokenAddress(destinationChainId) + : utils.getAddress(_outputTokenAddress); if (!_minOutputAmount && !_exactInputAmount) { throw new MissingParamError({ @@ -91,6 +100,13 @@ const handler = async ( }); } + if (!inputTokenAddress || !outputTokenAddress) { + throw new InvalidParamError({ + param: "inputToken, outputToken", + message: "Invalid input or output token address", + }); + } + const amountType = _minOutputAmount ? AMOUNT_TYPE.MIN_OUTPUT : AMOUNT_TYPE.EXACT_INPUT; @@ -103,11 +119,11 @@ const handler = async ( // 1. Get token details const [inputToken, outputToken] = await Promise.all([ getCachedTokenInfo({ - address: _inputTokenAddress, + address: inputTokenAddress, chainId: originChainId, }), getCachedTokenInfo({ - address: _outputTokenAddress, + address: outputTokenAddress, chainId: destinationChainId, }), ]); @@ -125,6 +141,8 @@ const handler = async ( refundAddress, // @TODO: Make this configurable via env var or query param leftoverType: "bridgeableToken", + isInputNative, + isOutputNative, }); // 3. Build cross swap tx From 484314e987aa4b4c8667e0ac06221b30e839ae3f Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 20 Nov 2024 10:13:00 +0700 Subject: [PATCH 12/15] review requests --- api/_dexes/cross-swap.ts | 4 ++-- api/_dexes/uniswap.ts | 33 +++++++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index be903bba2..8599baa3d 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -162,7 +162,7 @@ export async function buildCrossSwapTx( const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId; const spokePool = getSpokePool(originChainId); const deposit = { - depositor: crossSwapQuotes.crossSwap.recipient, + depositor: crossSwapQuotes.crossSwap.depositor, recipient: crossSwapQuotes.destinationSwapQuote ? getMultiCallHandlerAddress(destinationChainId) : crossSwapQuotes.crossSwap.recipient, @@ -177,7 +177,7 @@ export async function buildCrossSwapTx( fillDeadline: await getFillDeadline(spokePool), exclusivityDeadline: crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline, - message: crossSwapQuotes.bridgeQuote?.message || "0x", + message: crossSwapQuotes.bridgeQuote.message || "0x", }; let tx: PopulatedTransaction; diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index 7987d92eb..b658edfc8 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -159,14 +159,10 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( destinationSwapQuote = await getUniswapQuote( { ...destinationSwap, - amount: destinationSwapQuote.maximumAmountIn - .mul( - ethers.utils.parseEther( - (1 + Number(crossSwap.slippageTolerance) / 100).toString() - ) - ) - .div(utils.fixedPointAdjustment) - .toString(), + amount: addSlippageToAmount( + destinationSwapQuote.maximumAmountIn, + crossSwap.slippageTolerance.toString() + ), }, TradeType.EXACT_INPUT ); @@ -421,14 +417,10 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( destinationSwapQuote = await getUniswapQuote( { ...destinationSwap, - amount: destinationSwapQuote.maximumAmountIn - .mul( - ethers.utils.parseEther( - (1 + Number(crossSwap.slippageTolerance) / 100).toString() - ) - ) - .div(utils.fixedPointAdjustment) - .toString(), + amount: addSlippageToAmount( + destinationSwapQuote.maximumAmountIn, + crossSwap.slippageTolerance.toString() + ), }, TradeType.EXACT_INPUT ); @@ -674,3 +666,12 @@ function assertMinOutputAmount( ); } } + +function addSlippageToAmount(amount: BigNumber, slippageTolerance: string) { + return amount + .mul( + ethers.utils.parseEther((1 + Number(slippageTolerance) / 100).toString()) + ) + .div(utils.fixedPointAdjustment) + .toString(); +} From ea1ec678def71396d09b786faa8c7efc43dd17b0 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 20 Nov 2024 17:13:35 +0700 Subject: [PATCH 13/15] refactor leftover type into exact_output and min_output for clarity --- api/_dexes/cross-swap.ts | 64 ++++++++------ api/_dexes/types.ts | 4 +- api/_dexes/uniswap.ts | 67 +++++++++++---- api/_dexes/utils.ts | 45 ++++++++++ api/swap.ts | 27 +++--- scripts/tests/swap.ts | 178 +++++++++++++++++++++++++++++++-------- 6 files changed, 289 insertions(+), 96 deletions(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index f0b0d29ee..8c627e82f 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -9,12 +9,16 @@ import { latestGasPriceCache, } from "../_utils"; import { - getUniswapCrossSwapQuotesForMinOutputB2A, - getUniswapCrossSwapQuotesForMinOutputA2B, - getBestUniswapCrossSwapQuotesForMinOutputA2A, + getUniswapCrossSwapQuotesForOutputB2A, + getUniswapCrossSwapQuotesForOutputA2B, + getBestUniswapCrossSwapQuotesForOutputA2A, } from "./uniswap"; import { CrossSwap, CrossSwapQuotes } from "./types"; -import { buildExactOutputBridgeTokenMessage, getSwapAndBridge } from "./utils"; +import { + buildExactOutputBridgeTokenMessage, + buildMinOutputBridgeTokenMessage, + getSwapAndBridge, +} from "./utils"; import { tagIntegratorId } from "../_integrator-id"; import { PopulatedTransaction } from "ethers"; import { getMultiCallHandlerAddress } from "../_multicall-handler"; @@ -24,10 +28,9 @@ 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", + EXACT_OUTPUT: "exactOutput", MIN_OUTPUT: "minOutput", } as const; @@ -38,11 +41,6 @@ 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( @@ -53,14 +51,17 @@ export async function getCrossSwapQuotes( throw new Error("Not implemented yet"); } - if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT) { - return getCrossSwapQuotesForMinOutput(crossSwap); + if ( + crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT || + crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT + ) { + return getCrossSwapQuotesForOutput(crossSwap); } throw new Error("Invalid amount type"); } -export async function getCrossSwapQuotesForMinOutput(crossSwap: CrossSwap) { +export async function getCrossSwapQuotesForOutput(crossSwap: CrossSwap) { const crossSwapType = getCrossSwapType({ inputToken: crossSwap.inputToken.address, originChainId: crossSwap.inputToken.chainId, @@ -69,19 +70,19 @@ export async function getCrossSwapQuotesForMinOutput(crossSwap: CrossSwap) { }); if (crossSwapType === CROSS_SWAP_TYPE.BRIDGEABLE_TO_BRIDGEABLE) { - return getCrossSwapQuotesForMinOutputB2B(crossSwap); + return getCrossSwapQuotesForOutputB2B(crossSwap); } if (crossSwapType === CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY) { - return getCrossSwapQuotesForMinOutputB2A(crossSwap); + return getCrossSwapQuotesForOutputB2A(crossSwap); } if (crossSwapType === CROSS_SWAP_TYPE.ANY_TO_BRIDGEABLE) { - return getCrossSwapQuotesForMinOutputA2B(crossSwap); + return getCrossSwapQuotesForOutputA2B(crossSwap); } if (crossSwapType === CROSS_SWAP_TYPE.ANY_TO_ANY) { - return getCrossSwapQuotesForMinOutputA2A(crossSwap); + return getCrossSwapQuotesForOutputA2A(crossSwap); } throw new Error("Invalid cross swap type"); @@ -92,14 +93,25 @@ export async function getCrossSwapQuotesForExactInput(crossSwap: CrossSwap) { throw new Error("Not implemented yet"); } -export async function getCrossSwapQuotesForMinOutputB2B(crossSwap: CrossSwap) { +export async function getCrossSwapQuotesForOutputB2B(crossSwap: CrossSwap) { const bridgeQuote = await getBridgeQuoteForMinOutput({ inputToken: crossSwap.inputToken, outputToken: crossSwap.outputToken, minOutputAmount: crossSwap.amount, recipient: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), - message: buildExactOutputBridgeTokenMessage(crossSwap), + message: + crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT + ? buildExactOutputBridgeTokenMessage(crossSwap) + : buildMinOutputBridgeTokenMessage(crossSwap), }); + + if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT) { + bridgeQuote.message = buildMinOutputBridgeTokenMessage( + crossSwap, + bridgeQuote.outputAmount + ); + } + return { crossSwap, destinationSwapQuote: undefined, @@ -108,19 +120,19 @@ export async function getCrossSwapQuotesForMinOutputB2B(crossSwap: CrossSwap) { }; } -export async function getCrossSwapQuotesForMinOutputB2A(crossSwap: CrossSwap) { +export async function getCrossSwapQuotesForOutputB2A(crossSwap: CrossSwap) { // @TODO: Add support for other DEXes / aggregators - return getUniswapCrossSwapQuotesForMinOutputB2A(crossSwap); + return getUniswapCrossSwapQuotesForOutputB2A(crossSwap); } -export async function getCrossSwapQuotesForMinOutputA2B(crossSwap: CrossSwap) { +export async function getCrossSwapQuotesForOutputA2B(crossSwap: CrossSwap) { // @TODO: Add support for other DEXes / aggregators - return getUniswapCrossSwapQuotesForMinOutputA2B(crossSwap); + return getUniswapCrossSwapQuotesForOutputA2B(crossSwap); } -export async function getCrossSwapQuotesForMinOutputA2A(crossSwap: CrossSwap) { +export async function getCrossSwapQuotesForOutputA2A(crossSwap: CrossSwap) { // @TODO: Add support for other DEXes / aggregators - return getBestUniswapCrossSwapQuotesForMinOutputA2A(crossSwap, { + return getBestUniswapCrossSwapQuotesForOutputA2A(crossSwap, { preferredBridgeTokens: PREFERRED_BRIDGE_TOKENS, bridgeRoutesLimit: 2, }); diff --git a/api/_dexes/types.ts b/api/_dexes/types.ts index c8d1973d1..adbc5733e 100644 --- a/api/_dexes/types.ts +++ b/api/_dexes/types.ts @@ -1,6 +1,6 @@ import { BigNumber } from "ethers"; import { getSuggestedFees } from "../_utils"; -import { AmountType, CrossSwapType, LeftoverType } from "./cross-swap"; +import { AmountType, CrossSwapType } from "./cross-swap"; export type { AmountType, CrossSwapType }; @@ -19,7 +19,6 @@ export type Swap = { recipient: string; slippageTolerance: number; type: AmountType; - leftoverType?: LeftoverType; isInputNative?: boolean; isOutputNative?: boolean; }; @@ -32,7 +31,6 @@ export type CrossSwap = { recipient: string; slippageTolerance: number; type: AmountType; - leftoverType?: LeftoverType; refundOnOrigin: boolean; refundAddress?: string; isInputNative?: boolean; diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap.ts index 821b01dbe..02f46cdc4 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap.ts @@ -21,6 +21,7 @@ import { buildMulticallHandlerMessage, encodeApproveCalldata, encodeDrainCalldata, + encodeTransferCalldata, encodeWethWithdrawCalldata, getMultiCallHandlerAddress, } from "../_multicall-handler"; @@ -33,10 +34,11 @@ import { } from "./types"; import { buildExactOutputBridgeTokenMessage, + buildMinOutputBridgeTokenMessage, getSwapAndBridgeAddress, NoSwapRouteError, } from "./utils"; -import { LEFTOVER_TYPE } from "./cross-swap"; +import { AMOUNT_TYPE } from "./cross-swap"; // Taken from here: https://docs.uniswap.org/contracts/v3/reference/deployments/ export const SWAP_ROUTER_02_ADDRESS = { @@ -110,7 +112,7 @@ export async function getUniswapQuoteForOriginSwapExactInput( * 1. Get destination swap quote for bridgeable output token -> any token * 2. Get bridge quote for bridgeable input token -> bridgeable output token */ -export async function getUniswapCrossSwapQuotesForMinOutputB2A( +export async function getUniswapCrossSwapQuotesForOutputB2A( crossSwap: CrossSwap ) { const destinationSwapChainId = crossSwap.outputToken.chainId; @@ -148,9 +150,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( chainId: destinationSwapChainId, tokenIn: bridgeableOutputToken, tokenOut: crossSwap.outputToken, - recipient: crossSwap.isOutputNative - ? getMultiCallHandlerAddress(destinationSwapChainId) - : crossSwap.recipient, + recipient: getMultiCallHandlerAddress(destinationSwapChainId), slippageTolerance: crossSwap.slippageTolerance, }; // 1.1. Get destination swap quote for bridgeable output token -> any token @@ -163,8 +163,8 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( 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) { + // should be sent to receiver. + if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT) { destinationSwapQuote = await getUniswapQuote( { ...destinationSwap, @@ -205,10 +205,11 @@ export async function getUniswapCrossSwapQuotesForMinOutputB2A( * 1. Get bridge quote for bridgeable input token -> bridgeable output token * 2. Get origin swap quote for any input token -> bridgeable input token */ -export async function getUniswapCrossSwapQuotesForMinOutputA2B( +export async function getUniswapCrossSwapQuotesForOutputA2B( crossSwap: CrossSwap ) { const originSwapChainId = crossSwap.inputToken.chainId; + const destinationChainId = crossSwap.outputToken.chainId; const bridgeRoute = getRouteByOutputTokenAndOriginChain( crossSwap.outputToken.address, originSwapChainId @@ -244,8 +245,16 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( inputToken: bridgeableInputToken, outputToken: crossSwap.outputToken, minOutputAmount: crossSwap.amount, + recipient: getMultiCallHandlerAddress(destinationChainId), message: buildExactOutputBridgeTokenMessage(crossSwap), }); + // 1.1. Update bridge quote message for min. output amount + if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT && crossSwap.isOutputNative) { + bridgeQuote.message = buildMinOutputBridgeTokenMessage( + crossSwap, + bridgeQuote.outputAmount + ); + } const originSwap = { chainId: originSwapChainId, @@ -295,7 +304,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2B( * @param crossSwap * @param opts */ -export async function getBestUniswapCrossSwapQuotesForMinOutputA2A( +export async function getBestUniswapCrossSwapQuotesForOutputA2A( crossSwap: CrossSwap, opts: { preferredBridgeTokens: string[]; @@ -330,7 +339,7 @@ export async function getBestUniswapCrossSwapQuotesForMinOutputA2A( const crossSwapQuotes = await Promise.all( bridgeRoutesToCompare.map((bridgeRoute) => - getUniswapCrossSwapQuotesForMinOutputA2A(crossSwap, bridgeRoute) + getUniswapCrossSwapQuotesForOutputA2A(crossSwap, bridgeRoute) ) ); @@ -351,7 +360,7 @@ export async function getBestUniswapCrossSwapQuotesForMinOutputA2A( * @param crossSwap * @param bridgeRoute */ -export async function getUniswapCrossSwapQuotesForMinOutputA2A( +export async function getUniswapCrossSwapQuotesForOutputA2A( crossSwap: CrossSwap, bridgeRoute: { fromTokenAddress: string; @@ -407,7 +416,7 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( chainId: destinationSwapChainId, tokenIn: bridgeableOutputToken, tokenOut: crossSwap.outputToken, - recipient: crossSwap.recipient, + recipient: getMultiCallHandlerAddress(destinationSwapChainId), slippageTolerance: crossSwap.slippageTolerance, }; @@ -421,8 +430,8 @@ export async function getUniswapCrossSwapQuotesForMinOutputA2A( 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) { + // should be sent to receiver. + if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT) { destinationSwapQuote = await getUniswapQuote( { ...destinationSwap, @@ -616,7 +625,7 @@ function buildDestinationSwapCrossChainMessage({ destinationSwapQuote: SwapQuote; }) { const destinationSwapChainId = destinationSwapQuote.tokenOut.chainId; - const nativeEthActions = crossSwap.isOutputNative + const transferActions = crossSwap.isOutputNative ? // If output token is native, we need to unwrap WETH before sending it to the // recipient. This is because we only handle WETH in the destination swap. [ @@ -631,7 +640,26 @@ function buildDestinationSwapCrossChainMessage({ value: crossSwap.amount.toString(), }, ] - : []; + : [ + { + target: crossSwap.outputToken.address, + callData: encodeTransferCalldata( + crossSwap.recipient, + crossSwap.amount + ), + value: "0", + }, + { + target: getMultiCallHandlerAddress(destinationSwapChainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT + ? crossSwap.depositor + : crossSwap.recipient + ), + value: "0", + }, + ]; return buildMulticallHandlerMessage({ // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` fallbackRecipient: crossSwap.depositor, @@ -651,13 +679,16 @@ function buildDestinationSwapCrossChainMessage({ callData: destinationSwapQuote.swapTx.data, value: destinationSwapQuote.swapTx.value, }, - ...nativeEthActions, + // transfer output tokens to recipient + ...transferActions, // drain remaining bridgeable output tokens from MultiCallHandler contract { target: getMultiCallHandlerAddress(destinationSwapChainId), callData: encodeDrainCalldata( bridgeableOutputToken.address, - crossSwap.depositor + crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT + ? crossSwap.depositor + : crossSwap.recipient ), value: "0", }, diff --git a/api/_dexes/utils.ts b/api/_dexes/utils.ts index 8108c89c7..faa365442 100644 --- a/api/_dexes/utils.ts +++ b/api/_dexes/utils.ts @@ -1,4 +1,5 @@ import { UniversalSwapAndBridge__factory } from "@across-protocol/contracts/dist/typechain"; +import { BigNumber } from "ethers"; import { ENABLED_ROUTES, getProvider } from "../_utils"; import { @@ -117,3 +118,47 @@ export function buildExactOutputBridgeTokenMessage(crossSwap: CrossSwap) { ], }); } + +/** + * This builds a cross-chain message for a (any/bridgeable)-to-bridgeable cross swap + * with a min. amount of output tokens that the recipient will receive. + */ +export function buildMinOutputBridgeTokenMessage( + crossSwap: CrossSwap, + unwrapAmount?: BigNumber +) { + const transferActions = crossSwap.isOutputNative + ? // WETH unwrap to ETH + [ + { + target: crossSwap.outputToken.address, + callData: encodeWethWithdrawCalldata( + unwrapAmount || crossSwap.amount + ), + value: "0", + }, + { + target: crossSwap.recipient, + callData: "0x", + value: crossSwap.amount.toString(), + }, + ] + : // ERC-20 token transfer + []; + return buildMulticallHandlerMessage({ + // @TODO: handle fallback recipient for params `refundOnOrigin` and `refundAddress` + fallbackRecipient: crossSwap.depositor, + actions: [ + ...transferActions, + // drain remaining bridgeable output tokens from MultiCallHandler contract + { + target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.recipient + ), + value: "0", + }, + ], + }); +} diff --git a/api/swap.ts b/api/swap.ts index e8db5eaa7..f8bc3934b 100644 --- a/api/swap.ts +++ b/api/swap.ts @@ -23,6 +23,7 @@ import { isValidIntegratorId } from "./_integrator-id"; const SwapQueryParamsSchema = type({ minOutputAmount: optional(positiveIntStr()), + exactOutputAmount: optional(positiveIntStr()), exactInputAmount: optional(positiveIntStr()), inputToken: validAddress(), outputToken: validAddress(), @@ -56,6 +57,7 @@ const handler = async ( outputToken: _outputTokenAddress, exactInputAmount: _exactInputAmount, minOutputAmount: _minOutputAmount, + exactOutputAmount: _exactOutputAmount, originChainId: _originChainId, destinationChainId: _destinationChainId, recipient, @@ -78,18 +80,11 @@ const handler = async ( ? getWrappedNativeTokenAddress(destinationChainId) : utils.getAddress(_outputTokenAddress); - if (!_minOutputAmount && !_exactInputAmount) { + if (!_minOutputAmount && !_exactInputAmount && !_exactOutputAmount) { throw new MissingParamError({ - param: "minOutputAmount, exactInputAmount", - message: "One of 'minOutputAmount' or 'exactInputAmount' is required", - }); - } - - if (_minOutputAmount && _exactInputAmount) { - throw new InvalidParamError({ - param: "minOutputAmount, exactInputAmount", + param: "minOutputAmount, exactInputAmount, exactOutputAmount", message: - "Only one of 'minOutputAmount' or 'exactInputAmount' is allowed", + "One of 'minOutputAmount', 'exactInputAmount' or 'exactOutputAmount' is required", }); } @@ -109,11 +104,11 @@ const handler = async ( const amountType = _minOutputAmount ? AMOUNT_TYPE.MIN_OUTPUT - : AMOUNT_TYPE.EXACT_INPUT; + : _exactInputAmount + ? AMOUNT_TYPE.EXACT_INPUT + : AMOUNT_TYPE.EXACT_OUTPUT; const amount = BigNumber.from( - amountType === AMOUNT_TYPE.EXACT_INPUT - ? _exactInputAmount - : _minOutputAmount + _minOutputAmount || _exactInputAmount || _exactOutputAmount ); // 1. Get token details @@ -139,8 +134,6 @@ const handler = async ( type: amountType, refundOnOrigin, refundAddress, - // @TODO: Make this configurable via env var or query param - leftoverType: "bridgeableToken", isInputNative, isOutputNative, }); @@ -170,3 +163,5 @@ const handler = async ( }; export default handler; + +function getAmountType() {} diff --git a/scripts/tests/swap.ts b/scripts/tests/swap.ts index e3f40fd34..e7a01ef20 100644 --- a/scripts/tests/swap.ts +++ b/scripts/tests/swap.ts @@ -13,58 +13,170 @@ const depositor = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; const MIN_OUTPUT_CASES = [ // B2B { - label: "B2B", - minOutputAmount: ethers.utils.parseUnits("1", 6).toString(), - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], - originChainId: CHAIN_IDs.BASE, - outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.ARBITRUM], - destinationChainId: CHAIN_IDs.ARBITRUM, - depositor, + labels: ["B2B", "MIN_OUTPUT", "Base USDC", "Arbitrum USDC"], + params: { + minOutputAmount: ethers.utils.parseUnits("1", 6).toString(), + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + originChainId: CHAIN_IDs.BASE, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.ARBITRUM], + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, + }, + { + labels: ["B2B", "MIN_OUTPUT", "Base USDC", "Arbitrum ETH"], + params: { + minOutputAmount: ethers.utils.parseUnits("0.001", 18).toString(), + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + originChainId: CHAIN_IDs.BASE, + outputToken: ethers.constants.AddressZero, + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, }, // B2A { - label: "B2A", - minOutputAmount: ethers.utils.parseUnits("1", 18).toString(), - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], - originChainId: CHAIN_IDs.BASE, - outputToken: "0x74885b4D524d497261259B38900f54e6dbAd2210", // APE Coin - destinationChainId: CHAIN_IDs.ARBITRUM, - depositor, + labels: ["B2A", "MIN_OUTPUT", "Base USDC", "Arbitrum WETH"], + params: { + 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], + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, + }, + { + labels: ["B2A", "MIN_OUTPUT", "Base USDC", "Arbitrum ETH"], + params: { + minOutputAmount: ethers.utils.parseUnits("0.001", 18).toString(), + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + originChainId: CHAIN_IDs.BASE, + outputToken: ethers.constants.AddressZero, + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, + }, + // A2B + { + labels: ["A2B", "MIN_OUTPUT", "Base USDbC", "Arbitrum USDC"], + params: { + minOutputAmount: ethers.utils.parseUnits("1", 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, + depositor, + }, + }, + // A2A + { + labels: ["A2A", "MIN_OUTPUT", "Base USDbC", "Arbitrum APE"], + params: { + 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, + depositor, + }, + }, +]; +const EXACT_OUTPUT_CASES = [ + // B2B + { + labels: ["B2B", "EXACT_OUTPUT", "Base USDC", "Arbitrum USDC"], + params: { + exactOutputAmount: ethers.utils.parseUnits("1", 6).toString(), + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + originChainId: CHAIN_IDs.BASE, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.ARBITRUM], + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, + }, + { + labels: ["B2B", "EXACT_OUTPUT", "Base USDC", "Arbitrum ETH"], + params: { + exactOutputAmount: ethers.utils.parseUnits("0.001", 18).toString(), + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + originChainId: CHAIN_IDs.BASE, + outputToken: ethers.constants.AddressZero, + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, + }, + // B2A + { + labels: ["B2A", "EXACT_OUTPUT", "Base USDC", "Arbitrum WETH"], + params: { + exactOutputAmount: 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], + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, + }, + { + labels: ["B2A", "EXACT_OUTPUT", "Base USDC", "Arbitrum ETH"], + params: { + exactOutputAmount: ethers.utils.parseUnits("0.001", 18).toString(), + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + originChainId: CHAIN_IDs.BASE, + outputToken: ethers.constants.AddressZero, + destinationChainId: CHAIN_IDs.ARBITRUM, + depositor, + }, }, // A2B { - label: "A2B", - minOutputAmount: ethers.utils.parseUnits("1", 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, - depositor, + labels: ["A2B", "EXACT_OUTPUT", "Base USDbC", "Arbitrum USDC"], + params: { + minOutputAmount: ethers.utils.parseUnits("1", 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, + depositor, + }, }, // A2A { - label: "A2A", - 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, - depositor, - slippageTolerance: 1, + labels: ["A2A", "EXACT_OUTPUT", "Base USDbC", "Arbitrum APE"], + params: { + 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, + depositor, + }, }, ]; async function swap() { - for (const testCase of MIN_OUTPUT_CASES) { - console.log("\nTest case:", testCase.label); + const filterString = process.argv[2]; + const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; + const labelsToFilter = filterString ? filterString.split(",") : []; + const filteredTestCases = testCases.filter((testCase) => { + const matches = labelsToFilter.filter((label) => + testCase.labels + .map((label) => label.toLowerCase()) + .includes(label.toLowerCase()) + ); + return matches.length === labelsToFilter.length; + }); + for (const testCase of filteredTestCases) { + console.log("\nTest case:", testCase.labels.join(" ")); const response = await axios.get(`http://localhost:3000/api/swap`, { - params: testCase, + params: testCase.params, }); console.log(response.data); if (process.env.DEV_WALLET_PK) { const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( - getProvider(testCase.originChainId) + getProvider(testCase.params.originChainId) ); try { const tx = await wallet.sendTransaction({ From 35315b1872ab214d9183e0ccf99145c74b6796ef Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 20 Nov 2024 18:14:50 +0700 Subject: [PATCH 14/15] add temp swap and bridge typechain binding and use for testing --- api/_dexes/cross-swap.ts | 13 +- api/_dexes/utils.ts | 2 +- .../SwapAndBridge.sol/SwapAndBridge.ts | 345 ++++++++ .../SwapAndBridge.sol/SwapAndBridgeBase.ts | 211 +++++ .../UniversalSwapAndBridge.ts | 671 +++++++++++++++ api/_typechain/SwapAndBridge.sol/index.ts | 6 + .../SwapAndBridgeBase__factory.ts | 153 ++++ .../SwapAndBridge__factory.ts | 367 +++++++++ .../UniversalSwapAndBridge__factory.ts | 777 ++++++++++++++++++ .../factories/SwapAndBridge.sol/index.ts | 6 + scripts/generate-routes.ts | 8 +- ...6fA914353c44b2E33eBE05f21846F1048bEda.json | 8 +- 12 files changed, 2551 insertions(+), 16 deletions(-) create mode 100644 api/_typechain/SwapAndBridge.sol/SwapAndBridge.ts create mode 100644 api/_typechain/SwapAndBridge.sol/SwapAndBridgeBase.ts create mode 100644 api/_typechain/SwapAndBridge.sol/UniversalSwapAndBridge.ts create mode 100644 api/_typechain/SwapAndBridge.sol/index.ts create mode 100644 api/_typechain/factories/SwapAndBridge.sol/SwapAndBridgeBase__factory.ts create mode 100644 api/_typechain/factories/SwapAndBridge.sol/SwapAndBridge__factory.ts create mode 100644 api/_typechain/factories/SwapAndBridge.sol/UniversalSwapAndBridge__factory.ts create mode 100644 api/_typechain/factories/SwapAndBridge.sol/index.ts diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index 8c627e82f..734b82e4c 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -201,13 +201,12 @@ export async function buildCrossSwapTx( crossSwapQuotes.originSwapQuote.swapTx.data, crossSwapQuotes.originSwapQuote.maximumAmountIn, crossSwapQuotes.originSwapQuote.minAmountOut, - deposit - // @FIXME: This needs a redeployed version of the contract - // { - // value: crossSwapQuotes.crossSwap.isInputNative - // ? deposit.inputAmount - // : 0, - // } + deposit, + { + value: crossSwapQuotes.crossSwap.isInputNative + ? deposit.inputAmount + : 0, + } ); toAddress = swapAndBridge.address; } else { diff --git a/api/_dexes/utils.ts b/api/_dexes/utils.ts index faa365442..3702417dd 100644 --- a/api/_dexes/utils.ts +++ b/api/_dexes/utils.ts @@ -1,4 +1,4 @@ -import { UniversalSwapAndBridge__factory } from "@across-protocol/contracts/dist/typechain"; +import { UniversalSwapAndBridge__factory } from "../_typechain/factories/SwapAndBridge.sol"; import { BigNumber } from "ethers"; import { ENABLED_ROUTES, getProvider } from "../_utils"; diff --git a/api/_typechain/SwapAndBridge.sol/SwapAndBridge.ts b/api/_typechain/SwapAndBridge.sol/SwapAndBridge.ts new file mode 100644 index 000000000..8909d98ba --- /dev/null +++ b/api/_typechain/SwapAndBridge.sol/SwapAndBridge.ts @@ -0,0 +1,345 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { + FunctionFragment, + Result, + EventFragment, +} from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, +} from "@across-protocol/contracts/dist/typechain/common"; + +export declare namespace SwapAndBridgeBase { + export type DepositDataStruct = { + outputToken: string; + outputAmount: BigNumberish; + depositor: string; + recipient: string; + destinationChainid: BigNumberish; + exclusiveRelayer: string; + quoteTimestamp: BigNumberish; + fillDeadline: BigNumberish; + exclusivityDeadline: BigNumberish; + message: BytesLike; + }; + + export type DepositDataStructOutput = [ + string, + BigNumber, + string, + string, + BigNumber, + string, + number, + number, + number, + string, + ] & { + outputToken: string; + outputAmount: BigNumber; + depositor: string; + recipient: string; + destinationChainid: BigNumber; + exclusiveRelayer: string; + quoteTimestamp: number; + fillDeadline: number; + exclusivityDeadline: number; + message: string; + }; +} + +export interface SwapAndBridgeInterface extends utils.Interface { + functions: { + "ACROSS_INPUT_TOKEN()": FunctionFragment; + "EXCHANGE()": FunctionFragment; + "SPOKE_POOL()": FunctionFragment; + "SWAP_TOKEN()": FunctionFragment; + "allowedSelectors(bytes4)": FunctionFragment; + "multicall(bytes[])": FunctionFragment; + "swapAndBridge(bytes,uint256,uint256,(address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes))": FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | "ACROSS_INPUT_TOKEN" + | "EXCHANGE" + | "SPOKE_POOL" + | "SWAP_TOKEN" + | "allowedSelectors" + | "multicall" + | "swapAndBridge" + ): FunctionFragment; + + encodeFunctionData( + functionFragment: "ACROSS_INPUT_TOKEN", + values?: undefined + ): string; + encodeFunctionData(functionFragment: "EXCHANGE", values?: undefined): string; + encodeFunctionData( + functionFragment: "SPOKE_POOL", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "SWAP_TOKEN", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "allowedSelectors", + values: [BytesLike] + ): string; + encodeFunctionData( + functionFragment: "multicall", + values: [BytesLike[]] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridge", + values: [ + BytesLike, + BigNumberish, + BigNumberish, + SwapAndBridgeBase.DepositDataStruct, + ] + ): string; + + decodeFunctionResult( + functionFragment: "ACROSS_INPUT_TOKEN", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "EXCHANGE", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "SPOKE_POOL", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "SWAP_TOKEN", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "allowedSelectors", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "multicall", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "swapAndBridge", + data: BytesLike + ): Result; + + events: { + "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)": EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: "SwapBeforeBridge"): EventFragment; +} + +export interface SwapBeforeBridgeEventObject { + exchange: string; + swapToken: string; + acrossInputToken: string; + swapTokenAmount: BigNumber; + acrossInputAmount: BigNumber; + acrossOutputToken: string; + acrossOutputAmount: BigNumber; +} +export type SwapBeforeBridgeEvent = TypedEvent< + [string, string, string, BigNumber, BigNumber, string, BigNumber], + SwapBeforeBridgeEventObject +>; + +export type SwapBeforeBridgeEventFilter = + TypedEventFilter; + +export interface SwapAndBridge extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: SwapAndBridgeInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners( + eventFilter: TypedEventFilter + ): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + ACROSS_INPUT_TOKEN(overrides?: CallOverrides): Promise<[string]>; + + EXCHANGE(overrides?: CallOverrides): Promise<[string]>; + + SPOKE_POOL(overrides?: CallOverrides): Promise<[string]>; + + SWAP_TOKEN(overrides?: CallOverrides): Promise<[string]>; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise<[boolean]>; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + ACROSS_INPUT_TOKEN(overrides?: CallOverrides): Promise; + + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + SWAP_TOKEN(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + + callStatic: { + ACROSS_INPUT_TOKEN(overrides?: CallOverrides): Promise; + + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + SWAP_TOKEN(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall(data: BytesLike[], overrides?: CallOverrides): Promise; + + swapAndBridge( + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: CallOverrides + ): Promise; + }; + + filters: { + "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)"( + exchange?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + SwapBeforeBridge( + exchange?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + }; + + estimateGas: { + ACROSS_INPUT_TOKEN(overrides?: CallOverrides): Promise; + + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + SWAP_TOKEN(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + populateTransaction: { + ACROSS_INPUT_TOKEN( + overrides?: CallOverrides + ): Promise; + + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + SWAP_TOKEN(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + }; +} diff --git a/api/_typechain/SwapAndBridge.sol/SwapAndBridgeBase.ts b/api/_typechain/SwapAndBridge.sol/SwapAndBridgeBase.ts new file mode 100644 index 000000000..bbbdf9bdb --- /dev/null +++ b/api/_typechain/SwapAndBridge.sol/SwapAndBridgeBase.ts @@ -0,0 +1,211 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +import type { + BaseContract, + BigNumber, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { + FunctionFragment, + Result, + EventFragment, +} from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, +} from "@across-protocol/contracts/dist/typechain/common"; + +export interface SwapAndBridgeBaseInterface extends utils.Interface { + functions: { + "EXCHANGE()": FunctionFragment; + "SPOKE_POOL()": FunctionFragment; + "allowedSelectors(bytes4)": FunctionFragment; + "multicall(bytes[])": FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | "EXCHANGE" + | "SPOKE_POOL" + | "allowedSelectors" + | "multicall" + ): FunctionFragment; + + encodeFunctionData(functionFragment: "EXCHANGE", values?: undefined): string; + encodeFunctionData( + functionFragment: "SPOKE_POOL", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "allowedSelectors", + values: [BytesLike] + ): string; + encodeFunctionData( + functionFragment: "multicall", + values: [BytesLike[]] + ): string; + + decodeFunctionResult(functionFragment: "EXCHANGE", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "SPOKE_POOL", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "allowedSelectors", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "multicall", data: BytesLike): Result; + + events: { + "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)": EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: "SwapBeforeBridge"): EventFragment; +} + +export interface SwapBeforeBridgeEventObject { + exchange: string; + swapToken: string; + acrossInputToken: string; + swapTokenAmount: BigNumber; + acrossInputAmount: BigNumber; + acrossOutputToken: string; + acrossOutputAmount: BigNumber; +} +export type SwapBeforeBridgeEvent = TypedEvent< + [string, string, string, BigNumber, BigNumber, string, BigNumber], + SwapBeforeBridgeEventObject +>; + +export type SwapBeforeBridgeEventFilter = + TypedEventFilter; + +export interface SwapAndBridgeBase extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: SwapAndBridgeBaseInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners( + eventFilter: TypedEventFilter + ): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + EXCHANGE(overrides?: CallOverrides): Promise<[string]>; + + SPOKE_POOL(overrides?: CallOverrides): Promise<[string]>; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise<[boolean]>; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + }; + + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + callStatic: { + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall(data: BytesLike[], overrides?: CallOverrides): Promise; + }; + + filters: { + "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)"( + exchange?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + SwapBeforeBridge( + exchange?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + }; + + estimateGas: { + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + }; + + populateTransaction: { + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + }; +} diff --git a/api/_typechain/SwapAndBridge.sol/UniversalSwapAndBridge.ts b/api/_typechain/SwapAndBridge.sol/UniversalSwapAndBridge.ts new file mode 100644 index 000000000..1612a08c3 --- /dev/null +++ b/api/_typechain/SwapAndBridge.sol/UniversalSwapAndBridge.ts @@ -0,0 +1,671 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PayableOverrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { + FunctionFragment, + Result, + EventFragment, +} from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, +} from "@across-protocol/contracts/dist/typechain/common"; + +export declare namespace SwapAndBridgeBase { + export type DepositDataStruct = { + outputToken: string; + outputAmount: BigNumberish; + depositor: string; + recipient: string; + destinationChainid: BigNumberish; + exclusiveRelayer: string; + quoteTimestamp: BigNumberish; + fillDeadline: BigNumberish; + exclusivityDeadline: BigNumberish; + message: BytesLike; + }; + + export type DepositDataStructOutput = [ + string, + BigNumber, + string, + string, + BigNumber, + string, + number, + number, + number, + string, + ] & { + outputToken: string; + outputAmount: BigNumber; + depositor: string; + recipient: string; + destinationChainid: BigNumber; + exclusiveRelayer: string; + quoteTimestamp: number; + fillDeadline: number; + exclusivityDeadline: number; + message: string; + }; +} + +export interface UniversalSwapAndBridgeInterface extends utils.Interface { + functions: { + "EXCHANGE()": FunctionFragment; + "SPOKE_POOL()": FunctionFragment; + "allowedSelectors(bytes4)": FunctionFragment; + "depositWithAuthorization(address,uint256,(address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256,uint256,bytes32,uint8,bytes32,bytes32)": FunctionFragment; + "depositWithPermit(address,uint256,(address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256,uint8,bytes32,bytes32)": FunctionFragment; + "multicall(bytes[])": FunctionFragment; + "swapAndBridge(address,address,bytes,uint256,uint256,(address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes))": FunctionFragment; + "swapAndBridgeWithAuthorization(address,address,bytes,uint256,uint256,(address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256,uint256,bytes32,uint8,bytes32,bytes32)": FunctionFragment; + "swapAndBridgeWithPermit(address,address,bytes,uint256,uint256,(address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256,uint8,bytes32,bytes32)": FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | "EXCHANGE" + | "SPOKE_POOL" + | "allowedSelectors" + | "depositWithAuthorization" + | "depositWithPermit" + | "multicall" + | "swapAndBridge" + | "swapAndBridgeWithAuthorization" + | "swapAndBridgeWithPermit" + ): FunctionFragment; + + encodeFunctionData(functionFragment: "EXCHANGE", values?: undefined): string; + encodeFunctionData( + functionFragment: "SPOKE_POOL", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "allowedSelectors", + values: [BytesLike] + ): string; + encodeFunctionData( + functionFragment: "depositWithAuthorization", + values: [ + string, + BigNumberish, + SwapAndBridgeBase.DepositDataStruct, + BigNumberish, + BigNumberish, + BytesLike, + BigNumberish, + BytesLike, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "depositWithPermit", + values: [ + string, + BigNumberish, + SwapAndBridgeBase.DepositDataStruct, + BigNumberish, + BigNumberish, + BytesLike, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "multicall", + values: [BytesLike[]] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridge", + values: [ + string, + string, + BytesLike, + BigNumberish, + BigNumberish, + SwapAndBridgeBase.DepositDataStruct, + ] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridgeWithAuthorization", + values: [ + string, + string, + BytesLike, + BigNumberish, + BigNumberish, + SwapAndBridgeBase.DepositDataStruct, + BigNumberish, + BigNumberish, + BytesLike, + BigNumberish, + BytesLike, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridgeWithPermit", + values: [ + string, + string, + BytesLike, + BigNumberish, + BigNumberish, + SwapAndBridgeBase.DepositDataStruct, + BigNumberish, + BigNumberish, + BytesLike, + BytesLike, + ] + ): string; + + decodeFunctionResult(functionFragment: "EXCHANGE", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "SPOKE_POOL", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "allowedSelectors", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "depositWithAuthorization", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "depositWithPermit", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "multicall", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "swapAndBridge", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "swapAndBridgeWithAuthorization", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "swapAndBridgeWithPermit", + data: BytesLike + ): Result; + + events: { + "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)": EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: "SwapBeforeBridge"): EventFragment; +} + +export interface SwapBeforeBridgeEventObject { + exchange: string; + swapToken: string; + acrossInputToken: string; + swapTokenAmount: BigNumber; + acrossInputAmount: BigNumber; + acrossOutputToken: string; + acrossOutputAmount: BigNumber; +} +export type SwapBeforeBridgeEvent = TypedEvent< + [string, string, string, BigNumber, BigNumber, string, BigNumber], + SwapBeforeBridgeEventObject +>; + +export type SwapBeforeBridgeEventFilter = + TypedEventFilter; + +export interface UniversalSwapAndBridge extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: UniversalSwapAndBridgeInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners( + eventFilter: TypedEventFilter + ): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + EXCHANGE(overrides?: CallOverrides): Promise<[string]>; + + SPOKE_POOL(overrides?: CallOverrides): Promise<[string]>; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise<[boolean]>; + + depositWithAuthorization( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithAuthorization( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + callStatic: { + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithAuthorization( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithPermit( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: CallOverrides + ): Promise; + + multicall(data: BytesLike[], overrides?: CallOverrides): Promise; + + swapAndBridge( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: CallOverrides + ): Promise; + + swapAndBridgeWithAuthorization( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: CallOverrides + ): Promise; + + swapAndBridgeWithPermit( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: CallOverrides + ): Promise; + }; + + filters: { + "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)"( + exchange?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + SwapBeforeBridge( + exchange?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + }; + + estimateGas: { + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithAuthorization( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + populateTransaction: { + EXCHANGE(overrides?: CallOverrides): Promise; + + SPOKE_POOL(overrides?: CallOverrides): Promise; + + allowedSelectors( + arg0: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithAuthorization( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + acrossInputToken: string, + acrossInputAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + swapToken: string, + acrossInputToken: string, + routerCalldata: BytesLike, + swapTokenAmount: BigNumberish, + minExpectedInputTokenAmount: BigNumberish, + depositData: SwapAndBridgeBase.DepositDataStruct, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + }; +} diff --git a/api/_typechain/SwapAndBridge.sol/index.ts b/api/_typechain/SwapAndBridge.sol/index.ts new file mode 100644 index 000000000..c2a19dda2 --- /dev/null +++ b/api/_typechain/SwapAndBridge.sol/index.ts @@ -0,0 +1,6 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +export type { SwapAndBridge } from "./SwapAndBridge"; +export type { SwapAndBridgeBase } from "./SwapAndBridgeBase"; +export type { UniversalSwapAndBridge } from "./UniversalSwapAndBridge"; diff --git a/api/_typechain/factories/SwapAndBridge.sol/SwapAndBridgeBase__factory.ts b/api/_typechain/factories/SwapAndBridge.sol/SwapAndBridgeBase__factory.ts new file mode 100644 index 000000000..39c7908d8 --- /dev/null +++ b/api/_typechain/factories/SwapAndBridge.sol/SwapAndBridgeBase__factory.ts @@ -0,0 +1,153 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +import { Contract, Signer, utils } from "ethers"; +import type { Provider } from "@ethersproject/providers"; +import type { + SwapAndBridgeBase, + SwapAndBridgeBaseInterface, +} from "../../SwapAndBridge.sol/SwapAndBridgeBase"; + +const _abi = [ + { + inputs: [], + name: "InvalidFunctionSelector", + type: "error", + }, + { + inputs: [], + name: "LeftoverSrcTokens", + type: "error", + }, + { + inputs: [], + name: "MinimumExpectedInputAmount", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "exchange", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "swapToken", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "acrossInputToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "acrossInputAmount", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "acrossOutputToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "acrossOutputAmount", + type: "uint256", + }, + ], + name: "SwapBeforeBridge", + type: "event", + }, + { + inputs: [], + name: "EXCHANGE", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SPOKE_POOL", + outputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + name: "allowedSelectors", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes[]", + name: "data", + type: "bytes[]", + }, + ], + name: "multicall", + outputs: [ + { + internalType: "bytes[]", + name: "results", + type: "bytes[]", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +export class SwapAndBridgeBase__factory { + static readonly abi = _abi; + static createInterface(): SwapAndBridgeBaseInterface { + return new utils.Interface(_abi) as SwapAndBridgeBaseInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): SwapAndBridgeBase { + return new Contract(address, _abi, signerOrProvider) as SwapAndBridgeBase; + } +} diff --git a/api/_typechain/factories/SwapAndBridge.sol/SwapAndBridge__factory.ts b/api/_typechain/factories/SwapAndBridge.sol/SwapAndBridge__factory.ts new file mode 100644 index 000000000..770f17c74 --- /dev/null +++ b/api/_typechain/factories/SwapAndBridge.sol/SwapAndBridge__factory.ts @@ -0,0 +1,367 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +import { + Signer, + utils, + Contract, + ContractFactory, + BytesLike, + Overrides, +} from "ethers"; +import type { Provider, TransactionRequest } from "@ethersproject/providers"; +import type { + SwapAndBridge, + SwapAndBridgeInterface, +} from "../../SwapAndBridge.sol/SwapAndBridge"; + +const _abi = [ + { + inputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "_spokePool", + type: "address", + }, + { + internalType: "contract WETH9Interface", + name: "_wrappedNativeToken", + type: "address", + }, + { + internalType: "address", + name: "_exchange", + type: "address", + }, + { + internalType: "bytes4[]", + name: "_allowedSelectors", + type: "bytes4[]", + }, + { + internalType: "contract IERC20", + name: "_swapToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "_acrossInputToken", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "InvalidFunctionSelector", + type: "error", + }, + { + inputs: [], + name: "LeftoverSrcTokens", + type: "error", + }, + { + inputs: [], + name: "MinimumExpectedInputAmount", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "exchange", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "swapToken", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "acrossInputToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "acrossInputAmount", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "acrossOutputToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "acrossOutputAmount", + type: "uint256", + }, + ], + name: "SwapBeforeBridge", + type: "event", + }, + { + inputs: [], + name: "ACROSS_INPUT_TOKEN", + outputs: [ + { + internalType: "contract IERC20", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "EXCHANGE", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SPOKE_POOL", + outputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SWAP_TOKEN", + outputs: [ + { + internalType: "contract IERC20", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + name: "allowedSelectors", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes[]", + name: "data", + type: "bytes[]", + }, + ], + name: "multicall", + outputs: [ + { + internalType: "bytes[]", + name: "results", + type: "bytes[]", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "address", + name: "outputToken", + type: "address", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + internalType: "address", + name: "depositor", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "destinationChainid", + type: "uint256", + }, + { + internalType: "address", + name: "exclusiveRelayer", + type: "address", + }, + { + internalType: "uint32", + name: "quoteTimestamp", + type: "uint32", + }, + { + internalType: "uint32", + name: "fillDeadline", + type: "uint32", + }, + { + internalType: "uint32", + name: "exclusivityDeadline", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: "struct SwapAndBridgeBase.DepositData", + name: "depositData", + type: "tuple", + }, + ], + name: "swapAndBridge", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +const _bytecode = + "0x604061012060405234620001d2576200133d803803806200002081620001ea565b92833981019160c082840312620001d25781516001600160a01b03908181168103620001d25760209283850151938385168503620001d25760408601519384168403620001d25760608601516001600160401b039790888111620001d25787019080601f83011215620001d2578151988911620001d6576005918960051b908480620000ae818501620001ea565b809d81520192820101928311620001d2578401905b828210620001b057505050620000ea60a0620000e260808a0162000210565b980162000210565b97600196879660ff19966001885f5416175f5560805260a05260c0525f955b62000174575b888860e05261010090815260405161111791826200022683396080518281816101da0152610faf015260a05182818161041c015281816109600152610d18015260c05182505060e05182818160c9015261082c01525181818161016c015261084e0152f35b8051861015620001aa5786809663ffffffff60e01b8582861b85010151165f52818552855f208288825416179055019562000109565b6200010f565b81516001600160e01b031981168103620001d2578152908401908401620000c3565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6040519190601f01601f191682016001600160401b03811183821017620001d657604052565b51906001600160a01b0382168203620001d25756fe60806040526004361015610011575f80fd5b5f3560e01c8063393bb94314610084578063652fa4b91461007f5780638021fef71461007a57806385f168eb14610075578063ac9650d814610070578063b50e44b81461006b5763e65ae3ae14610066575f80fd5b61044f565b6103d2565b61034a565b6101fe565b610190565b610122565b34610114575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101145773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166080527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8060a0016080f35b5f80fd5b5f91031261011457565b34610114575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011457602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610114575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011457602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101145760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610114576004357fffffffff000000000000000000000000000000000000000000000000000000008116809103610114575f526001602052602060ff60405f2054166040519015158152f35b5f5b8381106102865750505f910152565b8181015183820152602001610277565b602080820190808352835180925260408301928160408460051b8301019501935f915b8483106102c95750505050505090565b909192939495848080837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8c5161033681518092818752878088019101610275565b0116010198019301930191949392906102b9565b346101145760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101145767ffffffffffffffff6004358181116101145736602382011215610114578060040135918211610114573660248360051b83010111610114576103ce9160246103c2920161070a565b60405191829182610296565b0390f35b34610114575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011457602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b90816101409103126101145790565b346101145760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101145767ffffffffffffffff600435818111610114573660238201121561011457806004013582811161011457366024828401011161011457606435928311610114576104cf6104df933690600401610440565b91604435916024803592016107f1565b005b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161052257604052565b6104e1565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761052257604052565b67ffffffffffffffff81116105225760051b60200190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610114570180359067ffffffffffffffff82116101145760200191813603831361011457565b90821015610619576106159160051b8101906105ad565b9091565b610580565b908092918237015f815290565b67ffffffffffffffff811161052257601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b3d1561068f573d906106768261062b565b916106846040519384610527565b82523d5f602084013e565b606090565b6020818303126101145780519067ffffffffffffffff8211610114570181601f820112156101145780516106c78161062b565b926106d56040519485610527565b81845260208284010111610114576106f39160208085019101610275565b90565b80518210156106195760209160051b010190565b91909161071683610568565b9060406107266040519384610527565b8483527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061075386610568565b015f5b8181106107e057505082945f5b818110610771575050505050565b5f8061077e8385886105fe565b9061078d87518093819361061e565b0390305af461079a610665565b90156107c057906001916107ae82886106f6565b526107b981876106f6565b5001610763565b604481511061011457806004610114920151602480918301019101610694565b806060602080938801015201610756565b9193909260ff5f5416156101145761082a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b7f0000000000000000000000000000000000000000000000000000000000000000927f0000000000000000000000000000000000000000000000000000000000000000946108bc6108b86108b16108818486610a73565b7fffffffff00000000000000000000000000000000000000000000000000000000165f52600160205260405f2090565b5460ff1690565b1590565b610a42576040517f70a082310000000000000000000000000000000000000000000000000000000080825230600483015273ffffffffffffffffffffffffffffffffffffffff98919260209182856024818c8f165afa948515610a1c575f95610a21575b506040519081523060048201529982908b9060249082908d165afa948515610a1c576109ac9a89935f976109dc575b50506109a7925f92838093610986887f00000000000000000000000000000000000000000000000000000000000000008096610afe565b6109956040518094819361061e565b03925af16109a1610665565b50610a6c565b610c29565b6109da60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b565b5f809491819499506109a7969381610a0892903d10610a15575b610a008183610527565b810190610ab7565b989350509281945061094f565b503d6109f6565b610ac6565b83919550610a3b90823d8411610a1557610a008183610527565b9490610920565b60046040517f42868c9b000000000000000000000000000000000000000000000000000000008152fd5b1561011457565b7fffffffff000000000000000000000000000000000000000000000000000000009035818116939260048110610aa857505050565b60040360031b82901b16169150565b90816020910312610114575190565b6040513d5f823e3d90fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b60449192602073ffffffffffffffffffffffffffffffffffffffff604051948580927fdd62ed3e000000000000000000000000000000000000000000000000000000008252306004830152808916602483015286165afa928315610a1c575f93610bda575b508201809211610bd5576040517f095ea7b300000000000000000000000000000000000000000000000000000000602082015273ffffffffffffffffffffffffffffffffffffffff9390931660248401526044808401929092529082526109da9190610bd0606483610527565b610e02565b610ad1565b610bf491935060203d602011610a1557610a008183610527565b915f610b63565b91908203918211610bd557565b3573ffffffffffffffffffffffffffffffffffffffff811681036101145790565b6040517f70a082310000000000000000000000000000000000000000000000000000000080825230600483015273ffffffffffffffffffffffffffffffffffffffff989697959695602095878b16959294939087826024818a5afa8015610a1c57610c9b925f91610de5575b50610bfb565b978810610dbb576040519384523060048501528916928581602481875afa8015610a1c578392610cd1925f92610d9c5750610bfb565b03610d72576109da977f646284e396b68ff4b4f34e0aa97bcdb9c100f5b44a20da5c475f62703985384191610d6a610d088a610c08565b918960405194859416988c0135917f0000000000000000000000000000000000000000000000000000000000000000859094939260609273ffffffffffffffffffffffffffffffffffffffff6080840197168352602083015260408201520152565b0390a4610f96565b60046040517fd6cf42f0000000000000000000000000000000000000000000000000000000008152fd5b610db4919250883d8a11610a1557610a008183610527565b905f610c95565b60046040517f0492ff87000000000000000000000000000000000000000000000000000000008152fd5b610dfc9150893d8b11610a1557610a008183610527565b5f610c95565b73ffffffffffffffffffffffffffffffffffffffff166040516040810181811067ffffffffffffffff82111761052257610e7d937f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656460205f948594604052818152015260208151910182855af1610e77610665565b916110b8565b8051908115918215610e93575b50501561011457565b819250906020918101031261011457602001518015158103610114575f80610e8a565b3563ffffffff811681036101145790565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b989590946106f39d9b9792602095610f889a959c976101809d8d73ffffffffffffffffffffffffffffffffffffffff998a8096818096168452169101521660408d01521660608b015260808a015260a089015260c08801521660e086015263ffffffff8092166101008601521661012084015261014083019063ffffffff169052565b816101608201520191610ec7565b9073ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001692610fdb838583610afe565b610fe760408601610c08565b90610ff460608701610c08565b93610ffe87610c08565b9661100b60a08201610c08565b61101760c08301610eb6565b61102360e08401610eb6565b916110316101008501610eb6565b936110406101208201826105ad565b9790968c3b15610114576040519d8e809d819d7f7b939232000000000000000000000000000000000000000000000000000000008352608087013596602001359516916004019c6110909d610f05565b03815a5f948591f18015610a1c576110a55750565b806110b26109da9261050e565b80610118565b90156110d2578151156110c9575090565b3b156101145790565b50805190811561011457602001fdfea2646970667358221220026a7409d965bcdd82516120169821b365afc3acc0d52608453d28ca5160c46464736f6c63430008170033"; + +type SwapAndBridgeConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: SwapAndBridgeConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class SwapAndBridge__factory extends ContractFactory { + constructor(...args: SwapAndBridgeConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override deploy( + _spokePool: string, + _wrappedNativeToken: string, + _exchange: string, + _allowedSelectors: BytesLike[], + _swapToken: string, + _acrossInputToken: string, + overrides?: Overrides & { from?: string } + ): Promise { + return super.deploy( + _spokePool, + _wrappedNativeToken, + _exchange, + _allowedSelectors, + _swapToken, + _acrossInputToken, + overrides || {} + ) as Promise; + } + override getDeployTransaction( + _spokePool: string, + _wrappedNativeToken: string, + _exchange: string, + _allowedSelectors: BytesLike[], + _swapToken: string, + _acrossInputToken: string, + overrides?: Overrides & { from?: string } + ): TransactionRequest { + return super.getDeployTransaction( + _spokePool, + _wrappedNativeToken, + _exchange, + _allowedSelectors, + _swapToken, + _acrossInputToken, + overrides || {} + ); + } + override attach(address: string): SwapAndBridge { + return super.attach(address) as SwapAndBridge; + } + override connect(signer: Signer): SwapAndBridge__factory { + return super.connect(signer) as SwapAndBridge__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): SwapAndBridgeInterface { + return new utils.Interface(_abi) as SwapAndBridgeInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): SwapAndBridge { + return new Contract(address, _abi, signerOrProvider) as SwapAndBridge; + } +} diff --git a/api/_typechain/factories/SwapAndBridge.sol/UniversalSwapAndBridge__factory.ts b/api/_typechain/factories/SwapAndBridge.sol/UniversalSwapAndBridge__factory.ts new file mode 100644 index 000000000..740fd1bcf --- /dev/null +++ b/api/_typechain/factories/SwapAndBridge.sol/UniversalSwapAndBridge__factory.ts @@ -0,0 +1,777 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +import { + Signer, + utils, + Contract, + ContractFactory, + BytesLike, + Overrides, +} from "ethers"; +import type { Provider, TransactionRequest } from "@ethersproject/providers"; +import type { + UniversalSwapAndBridge, + UniversalSwapAndBridgeInterface, +} from "../../SwapAndBridge.sol/UniversalSwapAndBridge"; + +const _abi = [ + { + inputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "_spokePool", + type: "address", + }, + { + internalType: "contract WETH9Interface", + name: "_wrappedNativeToken", + type: "address", + }, + { + internalType: "address", + name: "_exchange", + type: "address", + }, + { + internalType: "bytes4[]", + name: "_allowedSelectors", + type: "bytes4[]", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "InsufficientSwapValue", + type: "error", + }, + { + inputs: [], + name: "InvalidFunctionSelector", + type: "error", + }, + { + inputs: [], + name: "InvalidSwapToken", + type: "error", + }, + { + inputs: [], + name: "LeftoverSrcTokens", + type: "error", + }, + { + inputs: [], + name: "MinimumExpectedInputAmount", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "exchange", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "swapToken", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "acrossInputToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "acrossInputAmount", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "acrossOutputToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "acrossOutputAmount", + type: "uint256", + }, + ], + name: "SwapBeforeBridge", + type: "event", + }, + { + inputs: [], + name: "EXCHANGE", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SPOKE_POOL", + outputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + name: "allowedSelectors", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IERC20Auth", + name: "acrossInputToken", + type: "address", + }, + { + internalType: "uint256", + name: "acrossInputAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "address", + name: "outputToken", + type: "address", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + internalType: "address", + name: "depositor", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "destinationChainid", + type: "uint256", + }, + { + internalType: "address", + name: "exclusiveRelayer", + type: "address", + }, + { + internalType: "uint32", + name: "quoteTimestamp", + type: "uint32", + }, + { + internalType: "uint32", + name: "fillDeadline", + type: "uint32", + }, + { + internalType: "uint32", + name: "exclusivityDeadline", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: "struct SwapAndBridgeBase.DepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "validAfter", + type: "uint256", + }, + { + internalType: "uint256", + name: "validBefore", + type: "uint256", + }, + { + internalType: "bytes32", + name: "nonce", + type: "bytes32", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "depositWithAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IERC20Permit", + name: "acrossInputToken", + type: "address", + }, + { + internalType: "uint256", + name: "acrossInputAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "address", + name: "outputToken", + type: "address", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + internalType: "address", + name: "depositor", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "destinationChainid", + type: "uint256", + }, + { + internalType: "address", + name: "exclusiveRelayer", + type: "address", + }, + { + internalType: "uint32", + name: "quoteTimestamp", + type: "uint32", + }, + { + internalType: "uint32", + name: "fillDeadline", + type: "uint32", + }, + { + internalType: "uint32", + name: "exclusivityDeadline", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: "struct SwapAndBridgeBase.DepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "depositWithPermit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes[]", + name: "data", + type: "bytes[]", + }, + ], + name: "multicall", + outputs: [ + { + internalType: "bytes[]", + name: "results", + type: "bytes[]", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IERC20", + name: "swapToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "acrossInputToken", + type: "address", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "address", + name: "outputToken", + type: "address", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + internalType: "address", + name: "depositor", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "destinationChainid", + type: "uint256", + }, + { + internalType: "address", + name: "exclusiveRelayer", + type: "address", + }, + { + internalType: "uint32", + name: "quoteTimestamp", + type: "uint32", + }, + { + internalType: "uint32", + name: "fillDeadline", + type: "uint32", + }, + { + internalType: "uint32", + name: "exclusivityDeadline", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: "struct SwapAndBridgeBase.DepositData", + name: "depositData", + type: "tuple", + }, + ], + name: "swapAndBridge", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IERC20Auth", + name: "swapToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "acrossInputToken", + type: "address", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "address", + name: "outputToken", + type: "address", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + internalType: "address", + name: "depositor", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "destinationChainid", + type: "uint256", + }, + { + internalType: "address", + name: "exclusiveRelayer", + type: "address", + }, + { + internalType: "uint32", + name: "quoteTimestamp", + type: "uint32", + }, + { + internalType: "uint32", + name: "fillDeadline", + type: "uint32", + }, + { + internalType: "uint32", + name: "exclusivityDeadline", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: "struct SwapAndBridgeBase.DepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "validAfter", + type: "uint256", + }, + { + internalType: "uint256", + name: "validBefore", + type: "uint256", + }, + { + internalType: "bytes32", + name: "nonce", + type: "bytes32", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "swapAndBridgeWithAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IERC20Permit", + name: "swapToken", + type: "address", + }, + { + internalType: "contract IERC20", + name: "acrossInputToken", + type: "address", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "address", + name: "outputToken", + type: "address", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + internalType: "address", + name: "depositor", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "destinationChainid", + type: "uint256", + }, + { + internalType: "address", + name: "exclusiveRelayer", + type: "address", + }, + { + internalType: "uint32", + name: "quoteTimestamp", + type: "uint32", + }, + { + internalType: "uint32", + name: "fillDeadline", + type: "uint32", + }, + { + internalType: "uint32", + name: "exclusivityDeadline", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: "struct SwapAndBridgeBase.DepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "swapAndBridgeWithPermit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +const _bytecode = + "0x604060e060405234620001905762001b2390813803806200002081620001a8565b9384398201608083820312620001905782516001600160a01b03929091908383168303620001905760209485810151958587168703620001905760408201519586168603620001905760608201516001600160401b03928382116200019057019380601f860112156200019057845192831162000194576005948360051b908380620000ae818501620001a8565b80978152019282010192831162000190578301905b8282106200016e57505050600196879660ff19966001885f5416175f5560805260a05260c0525f955b62000132575b6040516119549081620001cf82396080518181816102fb0152611418015260a05181818161053d015281816111d20152611757015260c051816106c70152f35b8151861015620001685786809663ffffffff60e01b8382881b86010151165f52818352845f2082888254161790550195620000ec565b620000f2565b81516001600160e01b03198116810362000190578152908301908301620000c3565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6040519190601f01601f191682016001600160401b03811183821017620001945760405256fe60806040526004361015610011575f80fd5b5f3560e01c8063038f12ea146100a4578063277deffe1461009f5780638021fef71461009a57806385f168eb14610095578063ac9650d814610090578063b50e44b81461008b578063bdf52ad314610086578063c51e5eb9146100815763fdf152d31461007c575f80fd5b6107f6565b6105ee565b610561565b6104f3565b61046b565b61031f565b6102b1565b6101ff565b34610153576101807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610153576100dc610175565b6100e4610184565b67ffffffffffffffff919060443583811161015357610107903690600401610191565b9260a435948511610153576101236101519536906004016101bf565b9361012c6101ce565b9261016435956101443595610104359460e4359460c4359460843593606435936109d4565b005b5f80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361015357565b6004359061018282610157565b565b6024359061018282610157565b9181601f840112156101535782359167ffffffffffffffff8311610153576020838186019501011161015357565b90816101409103126101535790565b610124359060ff8216820361015357565b60e4359060ff8216820361015357565b6084359060ff8216820361015357565b34610153576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101535760043561023b81610157565b610243610184565b67ffffffffffffffff919060443583811161015357610266903690600401610191565b919060a435948511610153576102836101519536906004016101bf565b61028b6101df565b926101243595610104359560c435946084359360643593610b86565b5f91031261015357565b34610153575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015357602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101535760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610153576004357fffffffff000000000000000000000000000000000000000000000000000000008116809103610153575f526001602052602060ff60405f2054166040519015158152f35b5f5b8381106103a75750505f910152565b8181015183820152602001610398565b602080820190808352835180925260408301928160408460051b8301019501935f915b8483106103ea5750505050505090565b909192939495848080837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8c5161045781518092818752878088019101610396565b0116010198019301930191949392906103da565b346101535760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101535767ffffffffffffffff6004358181116101535736602382011215610153578060040135918211610153573660248360051b83010111610153576104ef9160246104e39201610eab565b604051918291826103b7565b0390f35b34610153575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015357602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610153576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101535760043561059d81610157565b60443567ffffffffffffffff8111610153576105bd9036906004016101bf565b9060c4359160ff831683036101535761015192610104359260e4359260a43591608435916064359160243590610f92565b60c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610153576004803561062581610157565b6024359061063282610157565b67ffffffffffffffff604435818111610153576106529036908601610191565b916064359060a4359081116101535761066e90369088016101bf565b92610677611089565b6106a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b34156107e0578134036107b75773ffffffffffffffffffffffffffffffffffffffff807f000000000000000000000000000000000000000000000000000000000000000016809187160361078e57803b15610153575f90604051988980927fd0e30db000000000000000000000000000000000000000000000000000000000825234905af19687156107895761074297610770575b505b608435926110d9565b61015160017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b8061077d61078392610b21565b806102a7565b5f610737565b610b7b565b876040517f3539a701000000000000000000000000000000000000000000000000000000008152fd5b866040517ff143e0da000000000000000000000000000000000000000000000000000000008152fd5b61074296506107f182303388611299565b610739565b346101535760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101535760043561083181610157565b60243560443567ffffffffffffffff8111610153576108549036906004016101bf565b9073ffffffffffffffffffffffffffffffffffffffff6108726101ef565b9361087b611089565b6108a67fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b6040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152911690602081604481855afa80156107895783915f916109a5575b5010610910575b610742935061090b82303384611299565b6113ff565b803b15610153576040517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606480359082015260ff94909416608485015260a480359085015260c48035908501525f8460e48183855af19384156107895761074294610992575b506108fa565b8061077d61099f92610b21565b5f61098c565b6109c7915060203d6020116109cd575b6109bf8183610b3a565b810190610cfa565b5f6108f3565b503d6109b5565b73ffffffffffffffffffffffffffffffffffffffff909c9a919b94979295989396999c6109ff611089565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f5516998a3b15610153576040517fef55bec6000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018990526064810193909352608483019390935260a482019b909b5260ff909a1660c48b015260e48a01919091526101048901525f8861012481838a5af197881561078957610ab798610ae5575b506110d9565b61018260017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b610aee90610b21565b5f610ab1565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff8111610b3557604052565b610af4565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610b3557604052565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff909a9293949596979891999a610baf611089565b610bda7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b6040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152336004820152306024820152911698906020816044818d5afa80156107895787915f91610cdb575b5010610c48575b505050610ab79750610c4383303389611299565b6110d9565b883b15610153576040517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101879052606481019190915260ff91909116608482015260a481019990995260c48901525f8860e481838a5af197881561078957610ab798610cc8575b8080610c2f565b8061077d610cd592610b21565b5f610cc1565b610cf4915060203d6020116109cd576109bf8183610b3a565b5f610c28565b90816020910312610153575190565b67ffffffffffffffff8111610b355760051b60200190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610153570180359067ffffffffffffffff82116101535760200191813603831361015357565b90821015610dba57610db69160051b810190610d4e565b9091565b610d21565b908092918237015f815290565b67ffffffffffffffff8111610b3557601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b3d15610e30573d90610e1782610dcc565b91610e256040519384610b3a565b82523d5f602084013e565b606090565b6020818303126101535780519067ffffffffffffffff8211610153570181601f82011215610153578051610e6881610dcc565b92610e766040519485610b3a565b8184526020828401011161015357610e949160208085019101610396565b90565b8051821015610dba5760209160051b010190565b919091610eb783610d09565b906040610ec76040519384610b3a565b8483527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0610ef486610d09565b015f5b818110610f8157505082945f5b818110610f12575050505050565b5f80610f1f838588610d9f565b90610f2e875180938193610dbf565b0390305af4610f3b610e06565b9015610f615790600191610f4f8288610e97565b52610f5a8187610e97565b5001610f04565b604481511061015357806004610153920151602480918301019101610e35565b806060602080938801015201610ef7565b73ffffffffffffffffffffffffffffffffffffffff909895989794939297969196610fbb611089565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f551694853b15610153576040517fef55bec6000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018890526064810193909352608483019390935260a482019790975260ff90961660c487015260e48601919091526101048501525f846101248183855af193841561078957610ab794611073575b506113ff565b61107c90610b21565b5f61106d565b1561015357565b60ff5f54161561015357565b7fffffffff0000000000000000000000000000000000000000000000000000000090358181169392600481106110ca57505050565b60040360031b82901b16169150565b9095949392919561112e61112a6111236110f38486611095565b7fffffffff00000000000000000000000000000000000000000000000000000000165f52600160205260405f2090565b5460ff1690565b1590565b61126f576040517f70a082310000000000000000000000000000000000000000000000000000000080825230600483015273ffffffffffffffffffffffffffffffffffffffff98919260209182856024818c8f165afa948515610789575f9561124e575b506040519081523060048201529982908b9060249082908d165afa948515610789576101829a89935f9761121e575b5050611219925f928380936111f8887f00000000000000000000000000000000000000000000000000000000000000008096611548565b61120760405180948193610dbf565b03925af1611213610e06565b50611082565b611668565b5f8094918194995061121996938161124192903d106109cd576109bf8183610b3a565b98935050928194506111c1565b8391955061126890823d84116109cd576109bf8183610b3a565b9490611192565b60046040517f42868c9b000000000000000000000000000000000000000000000000000000008152fd5b9290604051927f23b872dd00000000000000000000000000000000000000000000000000000000602085015273ffffffffffffffffffffffffffffffffffffffff809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff841117610b355761018292604052611841565b35610e9481610157565b3563ffffffff811681036101535790565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b98959094610e949d9b97926020956113f19a959c976101809d8d73ffffffffffffffffffffffffffffffffffffffff998a8096818096168452169101521660408d01521660608b015260808a015260a089015260c08801521660e086015263ffffffff8092166101008601521661012084015261014083019063ffffffff169052565b816101608201520191611330565b9073ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001692611444838583611548565b61145060408601611315565b9061145d60608701611315565b9361146787611315565b9661147460a08201611315565b61148060c0830161131f565b61148c60e0840161131f565b9161149a610100850161131f565b936114a9610120820182610d4e565b9790968c3b15610153576040519d8e809d819d7f7b939232000000000000000000000000000000000000000000000000000000008352608087013596602001359516916004019c6114f99d61136e565b03815a5f948591f180156107895761150e5750565b8061077d61018292610b21565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8316602482015291926020838060448101038173ffffffffffffffffffffffffffffffffffffffff86165afa928315610789575f9361163a575b508201809211611635576040517f095ea7b300000000000000000000000000000000000000000000000000000000602082015273ffffffffffffffffffffffffffffffffffffffff9390931660248401526044808401929092529082526101829190611630606483610b3a565b611841565b61151b565b61165491935060203d6020116109cd576109bf8183610b3a565b915f6115c3565b9190820391821161163557565b6040517f70a082310000000000000000000000000000000000000000000000000000000080825230600483015273ffffffffffffffffffffffffffffffffffffffff989697959695602095878b16959294939087826024818a5afa8015610789576116da925f91611824575b5061165b565b9788106117fa576040519384523060048501528916928581602481875afa8015610789578392611710925f926117db575061165b565b036117b157610182977f646284e396b68ff4b4f34e0aa97bcdb9c100f5b44a20da5c475f627039853841916117a96117478a611315565b918960405194859416988c0135917f0000000000000000000000000000000000000000000000000000000000000000859094939260609273ffffffffffffffffffffffffffffffffffffffff6080840197168352602083015260408201520152565b0390a46113ff565b60046040517fd6cf42f0000000000000000000000000000000000000000000000000000000008152fd5b6117f3919250883d8a116109cd576109bf8183610b3a565b905f6116d4565b60046040517f0492ff87000000000000000000000000000000000000000000000000000000008152fd5b61183b9150893d8b116109cd576109bf8183610b3a565b5f6116d4565b73ffffffffffffffffffffffffffffffffffffffff166040516040810181811067ffffffffffffffff821117610b35576118bc937f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656460205f948594604052818152015260208151910182855af16118b6610e06565b916118f5565b80519081159182156118d2575b50501561015357565b819250906020918101031261015357602001518015158103610153575f806118c9565b901561190f57815115611906575090565b3b156101535790565b50805190811561015357602001fdfea264697066735822122066b7197a155308805fbedee5c26980493cce360e5c829be686db3534152c74bf64736f6c63430008170033"; + +type UniversalSwapAndBridgeConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: UniversalSwapAndBridgeConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class UniversalSwapAndBridge__factory extends ContractFactory { + constructor(...args: UniversalSwapAndBridgeConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override deploy( + _spokePool: string, + _wrappedNativeToken: string, + _exchange: string, + _allowedSelectors: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise { + return super.deploy( + _spokePool, + _wrappedNativeToken, + _exchange, + _allowedSelectors, + overrides || {} + ) as Promise; + } + override getDeployTransaction( + _spokePool: string, + _wrappedNativeToken: string, + _exchange: string, + _allowedSelectors: BytesLike[], + overrides?: Overrides & { from?: string } + ): TransactionRequest { + return super.getDeployTransaction( + _spokePool, + _wrappedNativeToken, + _exchange, + _allowedSelectors, + overrides || {} + ); + } + override attach(address: string): UniversalSwapAndBridge { + return super.attach(address) as UniversalSwapAndBridge; + } + override connect(signer: Signer): UniversalSwapAndBridge__factory { + return super.connect(signer) as UniversalSwapAndBridge__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): UniversalSwapAndBridgeInterface { + return new utils.Interface(_abi) as UniversalSwapAndBridgeInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): UniversalSwapAndBridge { + return new Contract( + address, + _abi, + signerOrProvider + ) as UniversalSwapAndBridge; + } +} diff --git a/api/_typechain/factories/SwapAndBridge.sol/index.ts b/api/_typechain/factories/SwapAndBridge.sol/index.ts new file mode 100644 index 000000000..74ba6aa0d --- /dev/null +++ b/api/_typechain/factories/SwapAndBridge.sol/index.ts @@ -0,0 +1,6 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +export { SwapAndBridge__factory } from "./SwapAndBridge__factory"; +export { SwapAndBridgeBase__factory } from "./SwapAndBridgeBase__factory"; +export { UniversalSwapAndBridge__factory } from "./UniversalSwapAndBridge__factory"; diff --git a/scripts/generate-routes.ts b/scripts/generate-routes.ts index af2bf0da3..fb6354cc2 100644 --- a/scripts/generate-routes.ts +++ b/scripts/generate-routes.ts @@ -97,10 +97,10 @@ const enabledRoutes = { [CHAIN_IDs.BASE]: "0x98285D11B9F7aFec2d475805E5255f26B4490167", }, uniswap: { - [CHAIN_IDs.POLYGON]: "0xa55490E20057BD4775618D0FC8D51F59f602FED0", - [CHAIN_IDs.OPTIMISM]: "0x04989eaF03547E6583f9d9e42aeD11D2b78A808b", - [CHAIN_IDs.ARBITRUM]: "0x2414A759d4EFF700Ad81e257Ab5187d07eCeEbAb", - [CHAIN_IDs.BASE]: "0xed8b9c9aE7aCEf12eb4650d26Eb876005a4752d2", + [CHAIN_IDs.POLYGON]: "0xC2dCB88873E00c9d401De2CBBa4C6A28f8A6e2c2", + [CHAIN_IDs.OPTIMISM]: "0x28249CD61170Ed200Db8988D7f69e21331AFFBD0", + [CHAIN_IDs.ARBITRUM]: "0x07b64BE6906A78fac5FaB47cD58d59D0A093B15e", + [CHAIN_IDs.BASE]: "0xef5a54BEeBDB4Ee98E2906e30e65AD53dA8D2b17", }, }, routes: transformChainConfigs(enabledMainnetChainConfigs), diff --git a/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json b/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json index fe47510b2..0ca1aaec2 100644 --- a/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json +++ b/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json @@ -15,10 +15,10 @@ "42161": "0x81C7601ac0c5825e89F967f9222B977CCD78aD77" }, "uniswap": { - "10": "0x04989eaF03547E6583f9d9e42aeD11D2b78A808b", - "137": "0xa55490E20057BD4775618D0FC8D51F59f602FED0", - "8453": "0xed8b9c9aE7aCEf12eb4650d26Eb876005a4752d2", - "42161": "0x2414A759d4EFF700Ad81e257Ab5187d07eCeEbAb" + "10": "0x28249CD61170Ed200Db8988D7f69e21331AFFBD0", + "137": "0xC2dCB88873E00c9d401De2CBBa4C6A28f8A6e2c2", + "8453": "0xef5a54BEeBDB4Ee98E2906e30e65AD53dA8D2b17", + "42161": "0x07b64BE6906A78fac5FaB47cD58d59D0A093B15e" } }, "routes": [ From 5f87ffc46fdf48ba6dfb7018e00e11c65f101e68 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 20 Nov 2024 18:17:03 +0700 Subject: [PATCH 15/15] fixup --- api/_dexes/cross-swap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index 734b82e4c..c048b77f5 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -1,4 +1,5 @@ import { SpokePool } from "@across-protocol/contracts/dist/typechain"; +import { PopulatedTransaction } from "ethers"; import { isRouteEnabled, @@ -20,7 +21,6 @@ import { getSwapAndBridge, } from "./utils"; import { tagIntegratorId } from "../_integrator-id"; -import { PopulatedTransaction } from "ethers"; import { getMultiCallHandlerAddress } from "../_multicall-handler"; export type CrossSwapType =